호스트 스택을 직접 짜면 이 코드를 한 번은 쓴다. Select.rsp가 오면 거기 실린 Session ID를 받아 두고, 그 다음부터 내가 보내는 데이터 메시지에 그 값을 넣는 것이다. 장비가 알려준 값이니 맞을 거라는 생각이다. 그런데 장비가 00 0C를 실어 보내고 내 설정은 00 0B였다면 어떻게 되나. 아래는 그걸 시뮬레이터 리스너에 실제로 시켜 본 캡처다. 에러는 한 줄도 안 났다. Select.rsp의 byte 3은 00, 즉 accepted였고, System Bytes도 내가 보낸 값 그대로였다. 호스트는 SELECTED로 넘어가고, 그 뒤 데이터 메시지는 장비가 부인하는 Session ID를 달고 나간다. 장비는 그 값을 응답에 그대로 복사해서 돌려준다. 어디서도 안 걸린다.
캡처 세 판
시뮬레이터 EQ1의 passive 리스너(127.0.0.1:5501)에 밖에서 파이썬 소켓으로 붙었다. 아래 hex는 전부 소켓에 실제로 오간 바이트고, 장비 쪽 export에 RX로 남은 것만 적었다. 2026-09-30 캡처. TCP 연결을 세 번 따로 열었고 각각을 A/B/C로 부른다. C에서만 fault wrongSessionId를 켰고, 끝내고 다시 {}로 돌려놓은 것을 확인했다. 원본 export는 content/demos/hsms-reply-session-id-mismatch.json에 있다.
E37은 길이 접두어 4바이트 뒤의 헤더 10바이트를 0부터 센다. byte 0–1이 Session ID고, 이 자리가 E5의 Device ID가 사는 곳이다. 이 장비는 Session ID 11(00 0B), Device ID 1로 설정돼 있다.
먼저 A. 아무것도 틀리지 않은 판이다.
00:03:00.763 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 03 01
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 03 01 4.0 ms
00:03:00.768 TX S1F13 W=1 00 00 00 1A 00 0B 81 0D 00 00 00 00 03 02 01 02 41 07 48 4F 53 54 2D 30 31 41 03 31 2E 30
RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 03 02 01 02 21 01 00 ... 4.7 ms
SType이 01/02인 제어 메시지 두 개, 그 뒤 SType 00의 데이터 메시지. 전부 Session ID가 00 0B다. 여기까지는 볼 것이 없다. S1F14 바디의 21 01 00이 COMMACK 0이고, 이제 COMMUNICATING이다.
제어 메시지 응답은 에코가 아니다
B에서는 Session ID를 일부러 FF FF로 채워 제어 메시지를 보냈다.
00:03:00.784 TX Select.req 00 00 00 0A FF FF 00 00 00 01 00 00 03 11
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 03 11 4.7 ms
00:03:00.786 TX Linktest.req 00 00 00 0A FF FF 00 00 00 05 00 00 03 12
RX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 03 12 2.3 ms
두 응답 모두 00 0B로 왔다. 내가 보낸 FF FF가 아니다. Select 자체는 거절당하지 않았다 — byte 3이 00, accepted다(Select.rsp 읽는 법). System Bytes 00 00 03 11은 그대로 돌아왔다.
이게 이 글의 절반이다. 제어 메시지 응답의 Session ID는 장비가 고른 값이고, 요청에 뭐가 실렸는지는 상관이 없다. 그래서 보낸 값과 받은 값을 비교하는 코드를 제어 메시지에 걸면 정상 동작이 불일치로 잡힌다.
E37의 제어 메시지 표가 Session ID를 어떤 값으로 채우라고 쓰는지는 원문 조항으로 확인하지 못했다. 구현마다 Device ID를 그대로 넣기도 하고 FF FF를 넣기도 하는데, 이 리스너는 자기 값을 쓴다. 다른 장비가 요청 값을 에코할지는 이 캡처로 알 수 없다. 그래서 규칙은 하나로 줄어든다. 제어 응답의 Session ID에 아무것도 기대하지 않는다.
System Bytes는 맞고 Session ID만 틀린 응답
C에서 wrongSessionId를 켰다. A와 완전히 같은 Select.req를 보낸다.
00:03:00.804 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 03 21
RX Select.rsp 00 00 00 0A 00 0C 00 00 00 02 00 00 03 21 4.0 ms
00 0C. 12다. 11이 아니다. 나머지는 전부 정상이다. SType 02, byte 3 00으로 accepted, System Bytes 00 00 03 21은 내가 보낸 그대로. 호스트가 트랜잭션을 System Bytes로만 짝지으면 — 대부분 그렇게 한다 — 이 응답은 완벽하게 맞는 응답이다(System Bytes가 안 맞을 때). 세션은 SELECTED가 된다.
같은 연결에서 이어 보면 더 고약하다.
00:03:00.806 TX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 03 22
RX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 03 22 1.9 ms
00:03:00.807 TX S1F13 W=1 00 00 00 1A 00 0B 81 0D 00 00 00 00 03 23 01 02 41 07 48 4F 53 54 2D 30 31 41 03 31 2E 30
RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 03 23 ... 0.2 ms
Linktest.rsp는 00 0B, S1F14도 00 0B다. 틀린 값을 실은 건 Select.rsp 하나뿐이고, 같은 연결의 다음 응답부터는 다시 맞다. 그래서 이 fault는 로그를 훑는 방식으로는 안 보인다. Select.rsp 한 프레임만 보고 있어야 잡힌다.
그리고 서두의 코드가 여기서 값을 하나 주워 간다. Select.rsp에서 Session ID를 배워 오는 호스트는 이제 12를 들고 다닌다.
데이터 메시지는 보낸 값을 그대로 돌려준다
A로 돌아가서, 같은 연결에서 Session ID만 바꿔 S1F1을 세 번 보냈다.
00:03:00.772 TX S1F1 W=1 00 00 00 0A 00 0B 81 01 00 00 00 00 03 03
RX S1F2 00 00 00 32 00 0B 01 02 00 00 00 00 03 03 01 02 41 15 ... 3.5 ms
00:03:00.775 TX S1F1 W=1 00 00 00 0A FF FF 81 01 00 00 00 00 03 04
RX S1F2 00 00 00 32 FF FF 01 02 00 00 00 00 03 04 01 02 41 15 ... 3.0 ms
00:03:00.777 TX S1F1 W=1 00 00 00 0A 00 63 81 01 00 00 00 00 03 05
RX S1F2 00 00 00 32 00 63 01 02 00 00 00 00 03 05 01 02 41 15 ... 2.2 ms
00 63은 99다. 이 장비 것이 아니다. FF FF도 아니다. 그런데 세 번 다 같은 S1F2가 왔다. 바디도 같다 — MDLN "VX-9000 Plasma Etcher", SOFTREV "SECSGEM-1.4.1". 장비는 받은 Session ID를 응답 헤더에 그대로 복사했다.
E5에는 이 상황에 쓸 메시지가 있다. Device ID를 모르면 S9F1이다. 안 왔다. 이 리스너는 Session ID를 검사하지 않는다. 다른 장비가 S9F1을 보낼지는 장비마다 다르고 이 캡처로 확인된 것도 아니다(stream 9를 기대하면 안 되는 이유).
그래서 C에서 12를 배워 온 호스트는 12로 데이터 메시지를 보내고, 장비는 12를 돌려주고, 응답은 온다. 배선이 잘못됐는데 링크는 초록색이다. 이게 나중에 터지는 자리는 SECS 레벨이 아니다. 게이트웨이나 MES 쪽에서 Device ID로 장비를 구분하는 지점, 또는 한 소켓에 여러 Device ID를 태우는 멀티 디바이스 구성에서 터진다.
호스트 코드에서 볼 자리
- 내가 보내는 Session ID는 설정 파일에서 온다. Select.rsp에서 배워 오지 않는다. 장비가 알려주는 값이 아니고, 협상하는 값도 아니다.
- 제어 메시지 응답의 Session ID는 비교하지 않는다. B에서 보듯 에코가 아니다.
- 데이터 메시지 응답의 Session ID는 비교한다. 여기는 에코니까 다르면 진짜 이상한 것이다. 다만 어차피 에코하는 장비에서는 절대 안 걸린다.
- Select.rsp의 Session ID는 버리지 말고 로그에 남긴다. 설정한 값과 다르면 경고 한 줄. 던지지는 말고 — B 같은 장비를 만나면 그 경고가 매번 뜬다. 판단은 사람이 한다.
- 커미셔닝 체크리스트에 한 줄 넣는다. 데이터 메시지를 일부러 틀린 Session ID로 한 번 보내 본다. 정상적인 응답이 오면, 그 장비는 Session ID를 안 보는 장비다. 내 쪽 설정 오타를 잡아 줄 상대가 아니라는 뜻이다.
위 캡처는 SECS/GEM 시뮬레이터의 passive 리스너를 상대로 만들었다. wrongSessionId fault를 켜고 Select.req 한 개만 보내 보면 00 0C가 그대로 나온다.