← 전체 글
SECS/GEM/약 8분 읽기/— 조회

S2F33 리스트 길이 바이트가 0이면 정의가 아니라 전체 삭제다

S2F33의 report 목록 길이 바이트가 0이면 정의가 아니라 설비 전체 report 삭제 요청이다. 실제 byte 캡처로 두 frame 차이를 본다.

SECS/GEMMESSCADA문제 해결체크리스트

재접속할 때마다 report를 다시 정의하는 host를 만들었다. 접속하고, S1F13 보내고, 설정 테이블을 돌면서 S2F33을 보낸다. 그런데 어느 날 테이블이 비어 있었다. 설정 파일을 못 읽었거나, 필터 조건에 걸리는 항목이 하나도 없었거나. 코드는 멈추지 않았다. 빈 목록으로 S2F33을 보냈고 설비는 DRACK 0을 돌려줬다. host 로그에는 "report 정의 완료" 한 줄이 남는다. 그날부터 S6F11이 한 개도 안 온다.

S2F33 body 10바이트: L,2 다음에 U4 DATAID가 오고, 마지막 두 바이트가 report 목록이다. 길이 바이트가 0이면 전체 삭제 요청이 된다 S2F33 body — 10바이트 L,2 DATAID report 목록 0102 B10400 000001 01 00 L길이 리스트 길이 바이트

두 frame은 끝 한 바이트에서 갈린다

S2F33 Define Report의 body는 SEMI E5에서 L,2 { DATAID, L,n { L,2 { RPTID, L,m { VID } } } }다. 바깥 리스트의 두 번째 항목이 report 목록이고, 그 목록의 길이가 n이다.

E5는 이 n이 0인 경우에 따로 의미를 준다. report 목록이 비어 있으면 그 설비에 정의된 report를 전부 지우라는 요청이다. 같은 식으로 report 항목 하나의 VID 목록이 비어 있으면 그 RPTID 하나만 지운다. 정의, 개별 삭제, 전체 삭제가 모두 S2F33 하나로 들어가 있고, 구분은 리스트 길이 바이트뿐이다.

문제는 이 바이트가 host 코드에서 보통 변수라는 점이다. len(reports)를 그대로 쓴다. 테이블이 비면 0이 되고, 0이면 frame은 여전히 완벽하게 유효한 S2F33이다. 길이가 틀린 것도, item header의 길이 바이트 개수가 틀린 것도 아니다. 형식이 맞는 다른 명령이 된다. parser는 아무 불만이 없다.

캡처 — 정의 하나, 그리고 전체 삭제

SECS/GEM 시뮬레이터의 EQ1 passive listener(127.0.0.1:5501, SessionID 11, fault 없음)에 socket client로 붙어서 잡은 byte다. Select와 S1F13으로 COMMUNICATING까지 먼저 갔다 — SELECTED는 COMMUNICATING이 아니다.

RPTID 500에 VID 101, 102를 묶는 정상적인 정의부터.

TX S2F33 W   00 00 00 2A 00 0B 82 21 00 00 00 00 70 03
             01 02 B1 04 00 00 00 01
                   01 01 01 02 B1 04 00 00 01 F4 01 02 B1 04 00 00 00 65 B1 04 00 00 00 66
RX S2F34     00 00 00 0D 00 0B 02 22 00 00 00 00 70 03  21 01 00

Body는 <L [2] <U4 1> <L [1] <L [2] <U4 500> <L [2] <U4 101> <U4 102>>>>>다. 전체 frame은 길이 prefix 4바이트를 빼고 0x2A = 42바이트. 01 01이 report 목록이고 길이가 1이다.

이제 같은 host, 같은 socket, 바뀐 건 report 목록뿐이다.

TX S2F33 W   00 00 00 14 00 0B 82 21 00 00 00 00 70 06
             01 02 B1 04 00 00 00 01
                   01 00
RX S2F34     00 00 00 0D 00 0B 02 22 00 00 00 00 70 06  21 01 00

01 00. List item 하나, 길이 0. frame 길이는 0x14 = 20바이트로 줄었다. 그리고 두 S2F34는 SystemBytes 70 03과 70 06만 다르고 나머지 13바이트가 완전히 같다. 21 01 00 — binary item 1바이트, DRACK 0.

여기서 멈춰야 하는 결론이 있다. 이 listener는 S2F33을 구조대로 처리하지 않는다. 순서를 뒤집어도 ack가 전부 0으로 오는 그 관대한 listener와 같은 것이고, report 저장소도 밖으로 보여 주지 않는다. 그래서 전체 삭제가 실제로 일어났는지는 확인하지 못했다. 내가 byte로 확인한 것은 두 가지다. 빈 목록 형태의 S2F33이 wire에서 유효한 frame이라는 것, 그리고 이 설비의 응답만으로는 정의와 전체 삭제를 구분할 수 없다는 것. 삭제 의미 자체는 E5 문서 쪽이고, 실제 설비에서는 따로 확인해야 한다.

host 쪽에서 막는 자리

  • 보내기 전에 길이를 검사한다. report 목록이 비었으면 S2F33을 만들지 말고 거기서 멈춘다. 전체 삭제는 운영자가 명시적으로 누르는 동작이어야 하고, 설정 로더의 실패 경로가 아니다.
  • DRACK 0을 "내 정의가 들어갔다"로 읽지 않는다. 0은 설비가 요청을 받아들였다는 뜻이고, 그 요청이 삭제였을 수도 있다. E5에는 0 말고 거절 코드들도 있지만 이 listener는 전부 0만 돌려주므로 이 글에서는 확인하지 못했다.
  • 정의 뒤에 읽어서 맞춘다. S2F35로 link가 걸리는지, 그리고 실제로 S6F11이 오는지가 유일하게 믿을 수 있는 확인이다. ack는 확인이 아니다.
  • 설비의 report 정의는 host 하나만의 것이 아니다. 전체 삭제는 다른 host나 엔지니어가 설정한 report까지 같이 지운다. 그래서 이 사고는 조용하다 — 터지는 건 남의 데이터 수집이다.

Report 정의를 지우는 코드를 아예 안 짰더라도 len()이 0을 반환하는 경로는 이미 있다. 그 경로를 한 번 따라가 보는 게 이 글의 전부다. byte는 시뮬레이터로 직접 다시 잡을 수 있다.