시뮬레이터 상대로는 호스트 코드가 전부 통과했다. 그런데 현장 장비에 처음 붙인 날, 첫 S1F13이 답을 못 받고 T3로 죽는다. 장비 로그에는 Reject가 한 줄 찍혀 있거나 아예 연결이 끊긴 기록만 있다. 이런 경우 호스트 코드 쪽에서 의심할 자리는 한 군데다. Select.rsp를 기다리지 않고 데이터 메시지를 먼저 보냈는가. 아래는 그걸 일부러 해 본 캡처다. 소켓을 열자마자 Select를 건너뛰고 Linktest.req, S1F13, S1F1을 차례로 던졌고, 세 개 다 답이 왔다. 마지막에 보낸 Select.req는 Select.rsp를 받았다. 즉 그 앞의 세 번은 전부 NOT SELECTED 상태에서 오간 것이다.
캡처
소켓 하나, 위에서 아래로 보낸 순서 그대로다. 장비 쪽이 RX로 찍은 것만 옮겼다.
TX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 02 01
RX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 02 01
길이 10, SessionID 00 0B(11), 바이트 2와 3은 0, PType 0, SType 05가 Linktest.req다. 답은 SType 06에 SystemBytes 00 00 02 01을 그대로 돌려준다. 여기까지는 정상 동작처럼 보이는데, 이 소켓은 아직 Select를 한 번도 거치지 않았다.
TX S1F13 W=1 00 00 00 0C 00 0B 81 0D 00 00 00 00 02 02 01 00
RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 02 02
01 02 21 01 00 01 02
41 15 56 58 2D 39 30 30 30 20 50 6C 61 73 6D 61 20 45 74 63 68 65 72
41 0D 53 45 43 53 47 45 4D 2D 31 2E 34 2E 31
보낸 쪽 바이트 2가 81이다. 최상위 비트가 W-bit, 나머지 7비트가 Stream 1. 바이트 3 0D이 Function 13. 본문 01 00은 길이 0짜리 List다. 돌아온 S1F14의 본문을 풀면 01 02(원소 2개 List) 안에 21 01 00, 즉 1바이트 Binary 값 0이 들어 있다. COMMACK 0이다. 그 뒤는 MDLN VX-9000 Plasma Etcher와 SOFTREV SECSGEM-1.4.1.
TX S1F1 W=1 00 00 00 0C 00 0B 81 01 00 00 00 00 02 03 01 00
RX S1F2 00 00 00 32 00 0B 01 02 00 00 00 00 02 03
01 02 41 15 ... 41 0D ...
S1F1도 통과했다. 바이트 2가 01로 돌아온 건 응답이라 W-bit가 꺼져서다.
TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 02 04
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 02 04
이게 마지막이다. SType 01에 SType 02가 답으로 왔고 바이트 3은 00, Select Status 0. 이 줄이 찍히기 전까지 세션은 SELECTED가 아니었다. 앞의 여섯 줄은 전부 그 전에 오간 바이트다.
E37이 허용하는 구간
SEMI E37은 HSMS 연결을 NOT CONNECTED와 CONNECTED로 나누고, CONNECTED 안을 다시 NOT SELECTED와 SELECTED 두 substate로 쪼갠다. TCP가 붙으면 NOT SELECTED다. Select.req를 보내고 Select Status 0인 Select.rsp를 받아야 SELECTED로 넘어간다. HSMS-SS에서 데이터 메시지가 오갈 수 있는 구간은 SELECTED 하나뿐이고, Linktest도 마찬가지다.
NOT SELECTED 상태에서 데이터 메시지가 들어오면 E37은 서비스하지 말라고 한다. 거절 수단으로 정의된 것이 Reject.req(SType 7)이고, 바이트 3에 거절 사유가 실린다. 다만 "엔티티가 선택되지 않음"에 해당하는 사유 번호를 나는 도구로 확인하지 못했다 — 위 캡처의 장비는 Reject.req를 아예 보내지 않기 때문이다. 숫자가 필요하면 E37 원문을 직접 보는 편이 맞다.
NOT SELECTED가 무한정 유지되지도 않는다. E37에는 T7(Not Selected Timeout)이 있고, TCP는 붙었는데 그 시간 안에 Select가 끝나지 않으면 장비가 연결을 끊는다. 호스트 쪽 "붙자마자 연결이 끊긴다" 증상의 흔한 범인이다.
그래서 위 캡처는 틀린 건가
장비 쪽이 관대한 것이다. 저 리스너는 SType 0 프레임을 받으면 HSMS state를 보지 않고 바로 응답 테이블을 탄다. 검증 도구로서는 나쁘지 않다. 바이트를 보고 싶은 사람이 Select 핸드셰이크부터 맞추지 않아도 S1F13 하나를 던져 볼 수 있다. 문제는 그 관대함에 기대어 작성된 호스트 코드가 현장에서 처음 깨진다는 점이다. Select.rsp를 기다리지 않고 다음 메시지를 쓰는 코드는 시뮬레이터에서 영원히 녹색이다.
여기서 하나 더 갈라진다. E30의 COMMUNICATING과 E37의 SELECTED는 다른 상태 기계다. 위 캡처는 SELECTED 없이 COMMUNICATING으로 올라간 셈인데, 반대 방향 — SELECTED인데 COMMUNICATING이 아니라서 S1F3이 S1F0으로 돌아오는 경우 — 는 SELECTED인데 S1F3이 S1F0으로 돌아오는 이유에서 바이트로 따로 다뤘다.
호스트 코드에서 확인할 것
- Select.rsp를 받고 나서 데이터 메시지를 큐에서 꺼내는가.
connect()콜백에서 바로 S1F13을 쓰는 코드가 제일 흔하다. - Select.rsp의 바이트 3이 0인지 읽는가. 0이 아니면 거절이고, 그 소켓으로 더 보내면 안 된다.
- Select.rsp의 SystemBytes가 내가 보낸 Select.req 것과 같은지 비교하는가. 다르면 내 응답이 아니다.
- Reject.req(SType 7)가 돌아왔을 때 코드가 그걸 "알 수 없는 프레임"으로 버리지 않고 로그에 남기는가. 이 한 줄이 반나절을 아낀다.
- 연결 직후 아무 응답 없이 끊기면 T7을 의심하라. 재시도 간격을 줄이는 걸로는 안 고쳐진다.
위 캡처는 공개된 시뮬레이터로 그대로 재현할 수 있다. 소켓을 열고 Select를 건너뛴 다음 S1F13을 던지면 된다: SECS/GEM 시뮬레이터.