WiresharkでUSBパケットを解析するときのニッチな要求に応えたときのメモ
WiresharkでUSBパケットをキャプチャしたとき、実際に転送されてるデータの中身が見たいというニッチな要求があった。
(送ったデータがちゃんとUSBバスに出てるよね~、という確認がしたいなどの用途)
Wiresharkのセーブデータ渡して「Wiresharkで見てね~」と言ったら「見方が分からん!」と…
で、Excelで一覧表にしてあげようと思い、エクスポートできんかな~と探してみるも、適当な機能が見つからず…
しかたなく、変換スクリプトかましてCSVに出力してExcelで読み込んでみよう、と試した時のメモ。
今回はisochronous転送のデータの中身をダンプしている。
これでJSONファイルが保存される。
さぁ、このJSONファイルを読み込んで必要な部分を取り出すスクリプトを書けばOKじゃん?と思ったが、
そうは問屋がおろしてくれない😢
じつはWiresharkがエクスポートするJSONファイルはJSONファイルの文法からはずれた部分があるのだ…
具体的には、/_source/layers/usb
の配下に複数のキーusb
があって、すべてのUSBデータを読み込めない。
(JSONの仕様では同一階層に複数の同じキーの存在を許さない。pythonのJSONモジュールでは複数あるデータのうち、一つだけが読み込まれる)
ここが配列になってればOKだと気が付いて、チカラワザで変換する処理を追加してみた。
で、pyshonで書いたスクリプトがこちら。
Windows/RaspberryPi どっちでも大丈夫と思うけど、Windowsでしか試してない。
pythonは3.6以降が必要。3.7.7で動作確認。
json_read.py
import sys
import os
import json
import datetime
# テンポラリファイル名
tmp_file = 'tmp.json'
def usage() :
print( '==== USAGE =========================')
print(f' {sys.argv[0]} <JSON file> <CSV file>')
print( '====================================')
if len(sys.argv) == 3 :
json_file = sys.argv[1]
csv_file = sys.argv[2]
else :
usage()
sys.exit(1)
if not os.path.isfile(json_file) :
print( 'Error: JSON file not exist!!')
usage()
sys.exit(1)
# "usb" キーが複数あるので、これをリストに変換したJSONファイルを作成する
# かなり力技...
def modify_json(json_file, tmp_file) :
with open(json_file) as f_json, open(tmp_file, 'w') as f_tmp:
line_number = 0;
line = f_json.readline()
find_flag = False
while line:
line_number = line_number + 1 # 行番号
# line = line.rstrip('\r\n') # CRLFを削除
if find_flag :
line = line.replace('"usb": ', '') # key名称 "usb"を削除
if first_flag :
# print(f'START: {line_number} {line}')
f_tmp.write(f' "usb_data": [\n')
first_flag = False
brackets = brackets + line.count('{')
brackets = brackets - line.count('}')
if brackets < 0 :
# print(f'END: {line_number} {line}')
f_tmp.write(f' ]\n')
find_flag = False
else :
if line.startswith(' "usb.iso.numdesc":') :
f_pos = f_json.tell()
next_line = f_json.readline() # 次の行を読み込んで
f_json.seek(f_pos) # ファイル位置を戻す
if next_line.startswith(' "usb":') :
find_flag = True
first_flag = True
brackets = 0 # 括弧の数
f_tmp.write(line)
line = f_json.readline()
# JSONファイルの修正
modify_json(json_file, tmp_file)
# パケット解析用変数
stream_number = 0
frame_number = 0
# JSONファイルの読み込み
with open(tmp_file) as f:
json_load = json.load(f)
# print(json_load)
with open(csv_file, 'w') as f_csv :
# ヘッダの出力
f_csv.write('PacketNo,Date,Relative_time,Delta_time,Packet Size,Video Stream Size,Video Stream offset,Frame No,Stream No,Stream Data\n')
for json_data in json_load :
# フレームデータ
frame_data = json_data["_source"]["layers"]["frame"]
frame_index = frame_data["frame.number"]
frame_time_epoc = frame_data["frame.time_epoch"]
frame_time_delta = frame_data["frame.time_delta_displayed"]
frame_time_relative = frame_data["frame.time_relative"]
frame_time_dt = datetime.datetime.fromtimestamp(float(frame_time_epoc))
frame_time = str(frame_time_dt)
frame_len = frame_data["frame.len"]
# print(f'{frame_index},"\'{frame_time}",{frame_len},', end='')
f_csv.write(f'{frame_index},"\'{frame_time}",{frame_time_relative},{frame_time_delta},{frame_len},')
# USBデータ
usb_datas = json_data["_source"]["layers"]["usb"]["usb_data"]
first_flag = True
for usb_data in usb_datas :
if first_flag :
first_flag = False
else :
# print(f',,,,,', end='')
f_csv.write(f',,,,,')
iso_len = int(usb_data['usb.iso.iso_len'])
iso_off = usb_data['usb.iso.iso_off']
if (iso_len > 0) :
iso_data = usb_data["usb.iso.data"]
iso_data = '0x'+iso_data.replace(':', ',0x')
if stream_number == 0 :
frame_number = frame_number + 1
frame_number_str = str(frame_number)
else :
frame_number_str = ''
# print(f'{iso_len},{iso_off},{stream_number},{iso_data}')
f_csv.write(f'{iso_len},{iso_off},{frame_number_str},{stream_number},{iso_data}\n')
if iso_data.startswith('0x02,0x02') or iso_data.startswith('0x02,0x03') :
stream_number = 0
else :
stream_number = stream_number + 1
else :
# print(f'{iso_len},{iso_off},,')
f_csv.write(f'{iso_len},{iso_off},,\n')
# テンポラリファイルの削除
os.remove(tmp_file)
以下のコマンドで実行する。(RaspberryPiだとpython3
にしてちょ)
python json_read.py «入力JSONファイル» «出力CSVファイル»
やっつけスクリプトなので、エラーチェックはかなりいい加減…
関数modify_json()
が 前述のJSONファイルの不具合をチカラワザで修正する処理。
テンポラリファイルとしてカレントディレクトリにtmp.json
を作成するので、注意。
ファイル名を変更したければ、8行目のtmp_file
を変更。
このファイルはスクリプトの最後で削除している。
作成したテンポラリファイルを残しておきたければ最後のos.remove(tmp_file)
をコメントアウト。
71~72行目で修正したJSONファイルを読み込み。
75行目で書き出すCSVファイルをオープン。
78行目~のforループで各JSONレコードを読み込みながら処理。
82,85~86行目でframe.time_epochから時刻文字列を作成。
時刻はframe.time
を使用する手もあるが、ここはプラットフォームによって変化するらしいので同じ表示にするためにエポックタイムから生成している。
その他時刻関連データでは、frame.time_delta_displayed
で「前のパケットからの相対時間」、
frame.time_relative
で「最初のパケットからの相対時間」を取得している。
94行目~のforループがデータを取り出す部分。
isochronous転送のデータではないデータを取り出したい場合は、所望のデータのキーに置き換えて取り出せば良い。
frame_number
とstream_number
は私の解析用の補助データなので気にしないでネ。
あとは、エクスポートされたJSONファイルとスクリプトを見比べてちゃぶだい。(^^ゞ
あとは、csvファイルをExcelで読み込むなり、pandasとかを使った別のスクリプトで加工するなりしてちょ。