Building an EAI Middleware Layer
Slice, Validate and Flip the Standard Header
Goal
Understand the LH-STD standard header (80 bytes) down to the offsets, and implement parsing, validation, response generation, GUID issuance and stream splitting yourself.
Why it matters
The relay layer decides by looking only at the header. If the position where you cut the header is off by even one byte, routing, tracing and duplicate prevention all see wrong values. And because messages arrive over a TCP stream, without the habit of trusting the length field and cutting by bytes, you will meet a bug that breaks only with long messages for the first time in production. Rejecting a message with a wrong format with E102 instead of guessing and processing it is also this layer's responsibility.
Steps
- Read the specification
/opt/lab/fixtures/eaimw/header/SPEC.mdand create/root/eaimw/header/layout.csv. Headerfield,offset,length, the 9 header fields in order, and offsets are byte positions counted from 0. - For every file in the inbox
/opt/lab/fixtures/eaimw/header/inbox/, compare the message length field (the first 4 bytes) with the actual number of bytes (file size - 4) and create/root/eaimw/header/lencheck.csv. Headerfile,declared,actual,result, in file name order, and result isOKorLEN_MISMATCH. - Create
/root/eaimw/header/hdr.py.python3 hdr.py <파일>(file) prints the header as JSON (keys: MSG_LEN, TX_CODE, GUID, SND_ORG, RCV_ORG, MSG_TYPE, RSP_CODE, SEND_TS, BODY_LEN; values with leading and trailing whitespace removed; MSG_LEN and BODY_LEN as integers) and exits with 0. For a format error listed in section 5 of the specification, it printsE102 <사유>(E102 followed by the reason) on the first line and ends with exit code 2. - Create
/root/eaimw/header/reply.py.python3 reply.py <요청파일> <응답코드4자리>(request file, 4-digit response code) outputs a response message to standard output (as bytes) — keep the transaction code and GUID, swap the sending and receiving institutions, set the indicator to R, fill in the response code, set the transmission time to now (Korean time), no body, recalculate the message length. If the response code is not 4 digits, it ends with a non-zero code. - Create
/root/eaimw/header/guid.py. Each time it runs, it prints one line with a GUID of 32 lowercase hexadecimal characters (all zeros forbidden). Do not use the predictablerandom; issue it withsecretsoruuid. - Create
/root/eaimw/header/split.py. It takes a file in which several messages are joined together (the bytes exactly as received over TCP) and prints one line per message,거래코드 GUID 본문바이트수(transaction code, GUID, body byte count), and exits with 0. If the last message is cut off, it first prints the complete ones and then printsINCOMPLETE <남은바이트수>(INCOMPLETE followed by the number of remaining bytes) and ends with exit code 3. - Judge the whole inbox and create
/root/eaimw/header/report.csv. Headerfile,tx_code,guid,result, in file name order. A normal one isOK, and for a rejected message, leave tx_code and guid empty and writeE102.
Notes
- Working in bytes: in Python read with
open(f, "rb").read()and cut withb[4:12].decode("ascii"). In the shell usehead -c 4andstat -c %s. - One Hangul character is 2 bytes in EUC-KR and 3 bytes in UTF-8.
len("김하늘".encode("euc_kr"))is 6. - Common mistake: counting the length after decoding to a string. The message length is a number of bytes.
- Common mistake: guessing and processing a message whose format is wrong. Reject it and leave the reason.
- The in-house common library
/opt/lab/fixtures/eaimw/lib/lhstd.pyis used from module 2. In this lab you build it yourself.
Transcribe the specification into offsets
Transfer the 9 header fields of /opt/lab/fixtures/eaimw/header/SPEC.md into /root/eaimw/header/layout.csv (field,offset,length; offsets start from 0).
An offset is the sum of the lengths of the preceding fields. If MSG_LEN is at offset 0 with length 4, TX_CODE starts at 4. Adding up to the last field must give 80.
Compare the message length by bytes
For every file in /opt/lab/fixtures/eaimw/header/inbox/, compare the declared length with the actual length and create /root/eaimw/header/lencheck.csv.
The declared length comes from head -c 4, and the actual length is the value of stat -c %s minus 4. If you open a file containing Hangul in an editor and count, you get the number of characters — what you need is the number of bytes.
Build the parser — reject if wrong
/root/eaimw/header/hdr.py prints the header as JSON, and for a format error rejects it with E102 on the first line and exit code 2.
Read with open(f,'rb') and cut the bytes at the offsets. Length comparison, a regular expression for the transaction code, GUID lowercase hexadecimal, a 3-digit institution number, Q/R, and date validation with datetime.strptime — exactly the list in section 5 of the specification.
Turn the request around to make a response
/root/eaimw/header/reply.py outputs a response message to standard output.
Import and use the parse from step 3 (from hdr import parse). To send out bytes, use sys.stdout.buffer.write, not print. Calculate the message length with len after building everything else.
Issue a GUID
/root/eaimw/header/guid.py prints a GUID of 32 lowercase hexadecimal characters on one line each time it runs (all zeros forbidden, secrets or uuid).
secrets.token_hex(16) gives a 16-byte random number as 32 hexadecimal characters. uuid.uuid4().hex has the same shape. If you make it from the time, it collides within the same millisecond.
Cut the TCP stream into messages
/root/eaimw/header/split.py cuts joined messages by the length field and prints one line each, and reports a cut-off tail with INCOMPLETE and exit code 3.
Start pos at 0, read buf[pos:pos+4] as the length, and cut up to pos+4+length as one message. If the remaining bytes are fewer than the length, that is the cut-off tail.
Inbox verdict report
Judge the whole inbox and create /root/eaimw/header/report.csv (file,tx_code,guid,result — OK or E102).
Run the hdr.py from step 3 on each file and branch on the exit code. Do not force yourself to read and write down the transaction code of a rejected message — the fields of a message with a wrong format cannot be trusted.