TT Lab
开始
学习 学习路径 课程

构建 EAI 中间层

切分、校验并翻转标准报文头

在 TT Lab 中继续学习

目标

理解 LH-STD 标准报文头(80 字节)直到偏移量,并亲自实现解析、校验、响应生成、GUID 签发和流拆分。

为什么重要

中继层只看报文头来做判断。报文头的切分位置只要差一个字节,路由、跟踪和防重复看到的就全是错误的值。而且报文是在 TCP 流上到来的,如果没有信赖长度字段并按字节切分的习惯,就会在生产环境中第一次遇到只在较长报文上才出现的 bug。不猜测、而是以 E102 拒绝格式错误的报文,也是这一层的职责。

步骤

  1. 阅读定义书 /opt/lab/fixtures/eaimw/header/SPEC.md,创建 /root/eaimw/header/layout.csv。表头为 field,offset,length,按顺序写出报文头的 9 个字段,偏移量是从 0 开始计数的字节位置。
  2. 对收件箱 /opt/lab/fixtures/eaimw/header/inbox/ 中的所有文件,比较报文长度字段(前 4 个字节)与实际字节数(文件大小 - 4),创建 /root/eaimw/header/lencheck.csv。表头为 file,declared,actual,result,按文件名排序,result 为 OK 或 LEN_MISMATCH。
  3. 创建 /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 节中的格式错误,就在第一行输出 E102 <사유>(占位符为原因),并以退出码 2 结束。
  4. 创建 /root/eaimw/header/reply.py。python3 reply.py <요청파일> <응답코드4자리>(占位符依次为请求文件、4 位响应码)把响应报文输出到标准输出(字节)——保持交易码和 GUID,互换收发机构,标志设为 R,填入响应码,发送时间为现在(韩国时间),没有正文,重新计算报文长度。如果响应码不是 4 位,就以非 0 的退出码结束。
  5. 创建 /root/eaimw/header/guid.py。每次运行都输出一行 32 位小写十六进制(禁止全 0)的 GUID。不要用可预测的 random,而要用 secrets 或 uuid 来签发。
  6. 创建 /root/eaimw/header/split.py。接收多个报文首尾相连的文件(即通过 TCP 收到的原始字节),为每个报文输出一行 거래코드 GUID 본문바이트수(占位符依次为交易码、GUID、正文字节数),并以 0 结束。如果最后一个报文被截断,就先输出完整的那些,再输出 INCOMPLETE <남은바이트수>(占位符为剩余字节数),并以退出码 3 结束。
  7. 对整个收件箱做出判定,创建 /root/eaimw/header/report.csv。表头为 file,tx_code,guid,result,按文件名排序。正常的为 OK,被拒绝的报文,将 tx_code 和 guid 留空,写 E102。

参考

把定义书转换为偏移量

把 /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,格式错误时,以第一行 E102 和退出码 2 拒绝。

用 open(f,'rb') 读取,并按偏移量切分 bytes。长度核对、交易码正则、GUID 小写十六进制、机构 3 位数字、Q/R、用 datetime.strptime 校验日期——与定义书第 5 节的清单完全一致。

翻转请求来生成响应

让 /root/eaimw/header/reply.py <请求文件> <响应码> 把响应报文输出到标准输出。

引入第 3 步的 parse 来使用(from hdr import parse)。输出字节时不是用 print,而是用 sys.stdout.buffer.write。报文长度要在其余部分都生成之后,用 len 来计算。

签发 GUID

让 /root/eaimw/header/guid.py 每次运行都输出一行 32 位小写十六进制的 GUID(禁止全 0,使用 secrets 或 uuid)。

secrets.token_hex(16) 会把 16 字节的随机数给成 32 位十六进制。uuid.uuid4().hex 也是同样的形态。如果用时刻来生成,会在同一毫秒重复。

把 TCP 流切分为报文

让 /root/eaimw/header/split.py 按长度字段把首尾相连的报文切开并逐行输出,对被截断的尾部,以 INCOMPLETE 和退出码 3 告知。

让 pos 从 0 开始,把 buf[pos:pos+4] 读作长度,并把直到 pos+4+长度 的部分切为一个报文。如果剩余字节比长度还少,那就是被截断的尾部。

收件箱判定报告

对整个收件箱做出判定,创建 /root/eaimw/header/report.csv(file,tx_code,guid,result——OK 或 E102)。

对每个文件运行第 3 步的 hdr.py,并根据退出码来区分。不要勉强去读被拒绝报文的交易码并写上——格式错误的报文,其字段是不可信的。