標準ヘッダーを切り出し、検証し、裏返す
目標
LH-STD標準ヘッダー(80バイト)をオフセットまで理解し、パース・検証・レスポンス生成・GUID発行・ストリーム分割を自分で実装します。
なぜ重要なのか
中継層はヘッダーだけを見て判断します。ヘッダーを切る位置が1バイトずれるだけで、ルーティング・追跡・重複防止のすべてがでたらめな値を見ます。また電文はTCPストリーム上で届くため、長さフィールドを信じてバイトで切る習慣がないと、長い電文でだけ壊れるバグに本番で初めて出会います。形式が誤った電文を推測で処理せず、E102で拒否することも、この層の責任です。
ステップ
- インターフェース仕様書
/opt/lab/fixtures/eaimw/header/SPEC.mdを読み、/root/eaimw/header/layout.csvを作成してください。見出し行はfield,offset,length、ヘッダーの9フィールドを順に書き、オフセットは0から数えたバイト位置にします。 - 受信箱
/opt/lab/fixtures/eaimw/header/inbox/のすべてのファイルについて、電文長フィールド(先頭4バイト)と実際のバイト数(ファイルサイズ-4)を比較し、/root/eaimw/header/lencheck.csvを作成してください。見出し行はfile,declared,actual,result、ファイル名順で、resultはOKまたはLEN_MISMATCHです。 /root/eaimw/header/hdr.pyを作成してください。python3 hdr.py <파일>(プレースホルダーはファイルです)がヘッダーをJSONで出力し(キー: MSG_LEN・TX_CODE・GUID・SND_ORG・RCV_ORG・MSG_TYPE・RSP_CODE・SEND_TS・BODY_LEN、値は前後の空白を除去、MSG_LENとBODY_LENは整数)、終了コード0で終わるようにします。インターフェース仕様書の5節にある形式エラーの場合は、1行目にE102 <사유>(プレースホルダーは理由です)を出力し、終了コード2で終わります。/root/eaimw/header/reply.pyを作成してください。python3 reply.py <요청파일> <응답코드4자리>(プレースホルダーはリクエストファイルと4桁の応答コードです)がレスポンス電文を標準出力(バイト)へ出します。取引コードとGUIDは維持し、送受信機関は入れ替え、区分はR、応答コードを埋め、送信日時は現在(韓国時間)、本文なし、電文長は再計算します。応答コードが4桁でなければ、0以外のコードで終わります。/root/eaimw/header/guid.pyを作成してください。実行のたびに、小文字16進数32文字(すべて0は禁止)のGUIDを1行出力します。予測可能なrandomではなく、secretsかuuidで発行してください。/root/eaimw/header/split.pyを作成してください。複数の電文が連結されたファイル(TCPで受け取ったバイトそのまま)を受け取り、電文ごとに거래코드 GUID 본문바이트수(プレースホルダーは取引コード、GUID、本文のバイト数です)を1行ずつ出力して0で終わります。最後の電文が途切れている場合は、完全なものを先に出力してからINCOMPLETE <남은바이트수>(プレースホルダーは残りのバイト数です)を出力し、終了コード3で終わります。- 受信箱全体を判定して
/root/eaimw/header/report.csvを作成してください。見出し行はfile,tx_code,guid,result、ファイル名順です。正常はOK、拒否した電文はtx_codeとguidを空にしてE102とします。
参考
- バイト列として扱う: Pythonは
open(f, "rb").read()で読み、b[4:12].decode("ascii")で切り出します。シェルはhead -c 4・stat -c %sを使います。 - ハングル1文字は、EUC-KRでは2バイト、UTF-8では3バイトです。
len("김하늘".encode("euc_kr"))は6です。 - よくある間違い: 文字列にデコードしたあとで長さを数えてしまうことです。電文長はバイト数です。
- よくある間違い: 形式が誤った電文を推測で処理してしまうことです。拒否して理由を残します。
- 社内共通ライブラリ
/opt/lab/fixtures/eaimw/lib/lhstd.pyはモジュール2から使います。このラボでは自分で作ります。
インターフェース仕様書をオフセットに書き写す
/opt/lab/fixtures/eaimw/header/SPEC.mdのヘッダー9フィールドを/root/eaimw/header/layout.csvに書き写してください(field,offset,length。オフセットは0から)。
オフセットは、前のフィールドの長さの合計です。MSG_LENがオフセット0・長さ4なら、TX_CODEは4から始まります。最後のフィールドまで足すと80になるはずです。
電文長をバイトで照合する
/opt/lab/fixtures/eaimw/header/inbox/のすべてのファイルについて、宣言された長さと実際の長さを比較し、/root/eaimw/header/lencheck.csvを作成してください。
宣言された長さはhead -c 4、実際の長さはstat -c %sの結果から4を引いた値です。ハングルを含むファイルをエディターで開いて数えると、文字数が出てしまいます。必要なのはバイト数です。
パーサーを作り、誤りは拒否する
/root/eaimw/header/hdr.pyがヘッダーをJSONで出力し、形式エラーなら1行目のE102と終了コード2で拒否するようにしてください。
open(f,'rb')で読み、bytesをオフセットどおりに切ります。検証項目は、長さの照合・取引コードの正規表現・GUIDが小文字16進数であること・機関コードが数字3桁であること・Q/R・datetime.strptimeによる日付検証で、インターフェース仕様書の5節の一覧のとおりです。
リクエストを裏返してレスポンスを作る
/root/eaimw/header/reply.py <リクエストファイル> <応答コード>がレスポンス電文を標準出力へ出すようにしてください。
ステップ3のparseをimportして使います(from hdr import parse)。バイト列を出力するときはprintではなくsys.stdout.buffer.writeを使います。電文長は、残りをすべて作ってからlenで計算してください。
GUIDを発行する
/root/eaimw/header/guid.pyが、実行のたびに小文字16進数32文字のGUIDを1行出力するようにしてください(すべて0は禁止、secretsまたはuuid)。
secrets.token_hex(16)は、16バイトの乱数を16進数32文字で返します。uuid.uuid4().hexも同じ形です。時刻で作ると、同じミリ秒に値が重なります。
TCPストリームを電文に切り分ける
/root/eaimw/header/split.pyが、連結された電文を長さフィールドで切って1行ずつ出力し、途切れた末尾はINCOMPLETE と終了コード3で知らせるようにしてください。
posを0から始めてbuf[pos:pos+4]を長さとして読み、pos+4+長さまでを1つの電文として切ります。残りのバイト数が長さに足りなければ、それが途切れた末尾です。
受信箱の判定レポート
受信箱全体を判定して/root/eaimw/header/report.csvを作成してください(file,tx_code,guid,result。OKまたはE102)。
ステップ3のhdr.pyをファイルごとに実行し、終了コードで振り分けます。拒否された電文の取引コードを無理に読んで書かないでください。形式が誤った電文のフィールドは信用できません。