A Tool That Answers Linktest But Never Completes Select
In HSMS, alive and connected are different states. A real capture of Linktest and S1F13 sent before Select ever goes out, the Reject.req reason 4 that SEMI E37 asks for there, and why the timer that fires first is T6, not T7.
The host monitoring screen is green. Linktest comes back every 15 seconds, so the connection is alive. And not one message will go out.
In HSMS, alive and connected are different states. SEMI E37's connection state machine has NOT CONNECTED and CONNECTED, and CONNECTED holds two sub-states of its own: NOT SELECTED and SELECTED. A socket that comes up puts you in CONNECTED / NOT SELECTED. Data messages (SType 00) only flow in SELECTED. If Select never completed, the state is NOT SELECTED no matter how many Linktests come back. A health check that watches Linktest alone gets fooled exactly here.
The capture: Linktest answers before Select is ever sent
Taken 2026-09-11 with a plain Python socket thrown at the simulator's passive listener (127.0.0.1:5501) from outside. The hex is what the equipment side logged as RX/TX; the times are measured at the host. Session ID is 00 0B (11). The raw export is committed at content/demos/hsms-linktest-alive-but-not-selected.json.
No Select.req has gone out yet. The state is NOT SELECTED.
06:03:17.592 TX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 0D 01
06:03:17.592 RX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 0D 01 0.5 ms
The first four bytes are the length prefix (0A = 10; a control message has no body, so it is always 10). The ten that follow are the header. Bytes 0–1 are the session ID, bytes 2 and 3 are both 00 because this is a control message, byte 4 is the PType (00 = SECS-II), byte 5 is the SType (05 Linktest.req, 06 Linktest.rsp), bytes 6–9 are the SystemBytes 00 00 0D 01, handed back unchanged. 0.5 ms. The socket and the process are both fine.
One byte here does not match the standard. E37 defines the session ID on Linktest.req and Linktest.rsp as FF FF — linktest checks the link itself rather than a session, so there is no session number to put there. The capture shows 00 0B, an ordinary session number. My client sent it that way and this listener echoes what it receives. Plenty of real equipment behaves the same, but a host that matches Linktest on session ID breaks the first time it meets a peer that uses FF FF. Match on SystemBytes.
Next, a data message down the same socket. Still before Select.
06:03:18.793 TX S1F13 W=1 00 00 00 1A 00 0B 81 0D 00 00 00 00 0D 02
01 02 41 06 48 4F 53 54 49 44 41 04 31 2E 30 30
06:03:18.794 RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 0D 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 1.1 ms
A data message, so the SType is 00, byte 2 81 is the W-bit on top of stream 1 in the low seven bits, and byte 3 0D is function 13. The reply's byte 2 is 01 (W-bit down), byte 3 0E is function 14, and the SystemBytes 00 00 0D 02 are unchanged. The body is 01 02 (L[2]) holding 21 01 00 (COMMACK 0) and A[21] VX-9000 Plasma Etcher, A[13] SECSGEM-1.4.1.
This violates E37. Data messages are only allowed in SELECTED. A peer that receives SType 00 while NOT SELECTED is supposed to run E37's reject procedure and return Reject.req (SType 07) — Reason Code 04, "Entity not selected", in header byte 3, with the rejected message's SType in byte 2. This listener never looked at the state; there is no state check in its SType 00 branch. How to read a Reject.req header is in Deselect.req and the STypes that draw silence.
Worth writing down, because a simulator is not a substitute for the standard. An S1F14 coming back here does not mean real equipment will do the same. Read it the other way instead: if an S1F13 you sent before Select drew an answer in the field, that tool is skipping the same check. From the host side the rule is simpler. Do not write data messages to the socket while NOT SELECTED. A peer that answers is doing you a favour, not honouring a contract.
1.2 seconds later I sent Select.req. Only now does the session open.
06:03:19.994 TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 0D 03
06:03:19.994 RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 0D 03 0.7 ms
Byte 3 of Select.rsp is the Select Status. Here it is 00, success — the same header slot a data message spends on its function number.
Swallow the Select and Linktest still answers
I set the equipment's silentAfterSelect fault and reversed the order.
06:02:54.271 RX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 0B 01
06:02:54.271 FAULT Select.req ignored (silentAfterSelect)
(no reply, gave up after 4004 ms)
06:02:58.275 RX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 0B 02
06:02:58.275 TX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 0B 02 0.5 ms
The point is the RX line above the FAULT line. Select.req arrived at the equipment. It cleared the parser and it is in the log. And nothing comes back. Four seconds of nothing, and the Linktest sent straight after it returns in 0.5 ms. Same equipment, same connection.
TCP is up. The process is running and has capacity to answer. The session simply isn't open. A monitoring screen calling this equipment healthy isn't lying — it's asking the wrong question.
In the field this shape usually means one of three things: the equipment isn't in a state to service a Select (initialising, or tearing down a session with another host), it doesn't recognise the session ID, or it's holding a session it hasn't released. In all three, Linktest keeps answering. In E37 the linktest procedure checks the link rather than the session, and it is available once TCP is up, NOT SELECTED included. Which is why a Linktest round trip tells you nothing at all about SELECTED.
For the last two of those three — unknown session ID, or a session already held — the silence is itself a spec violation. E37 says a refused Select is still answered with a Select.rsp, carrying a non-zero Select Status in byte 3. No answer at all is an equipment bug, or a packet dying somewhere upstream of it. Putting that sentence in the vendor ticket changes the reply you get.
The mirror image — selects fine, swallows the keepalive — is captured for real under ignoreLinktest in which HSMS timer just fired: Linktest.req shows up in the RX log, only the reply disappears, and T6 expires 5.005 s later. That gets reported as "works, then disconnects every so often", and from the log alone it's indistinguishable from a flaky network.
The timer that fires first is not T7
What separates these two in a host log is which timer expired. The values and ranges E37 defines:
| Timer | E37 name | Default (range) | What it measures |
|---|---|---|---|
| T3 | Reply Timeout | 45 s (1–120) | Waiting for the reply to a data message with the W-bit set |
| T6 | Control Transaction Timeout | 5 s (1–240) | How long a Select, Deselect or Linktest transaction stays open |
| T7 | NOT SELECTED Timeout | 10 s (1–240) | How long the TCP connection sits in NOT SELECTED |
In the silentAfterSelect capture above, the timer that fires first is T6, not T7. The side that sent Select.req has an open control transaction, and T6 is what measures it — 5 seconds against T7's 10, on the defaults. T7 is the backstop behind it, and the side actually running T7 is usually the passive equipment waiting for a Select.req that never comes. If your logging flattens all three into timeout, fix the logging first. And a T3 logged while the state is NOT SELECTED is not a timer problem, it's a state-machine bug.
What happens on expiry differs too: T6 and T7 are failures of the connection, so the TCP connection closes; T3 fails one transaction and leaves the connection alone.
The 4004 ms above is my client's own patience, not a value of T6 or T7. The defaults are a starting point rather than an agreement — read the real ones out of the interface spec instead of borrowing a number.
Fixing the health check is faster
Change what you watch from "does Linktest come back" to "is the HSMS state SELECTED" and the first shape surfaces immediately. Most host stacks expose session state through their API. Monitoring that only watches keepalive round-trips is still common, and it can't see this.
There's a temptation to substitute an S1F13 probe, and the capture above is why that doesn't work either: an answer can come back while NOT SELECTED. The opposite case — SELECTED reached, S1F13 unanswered — is SELECTED is not the same as COMMUNICATING.
The second shape is the one keepalive monitoring does catch, but only after the drop. While you're there, check that the reconnect loop honours T5 (Connect Separation Timeout, 10 s default), because a loop that doesn't will pile connection attempts onto equipment that hasn't recovered yet.
Reproducing it
The second capture came from setting the equipment's silentAfterSelect fault; it was reset to {} as soon as the capture was taken, and the reset confirmed with a normal Select round trip. The first capture used no fault at all — send Linktest.req and S1F13 before you send Select.req and it reproduces as it stands.
Point your own host stack at it and watch what your monitoring says about each — whether it goes green — and you'll know within a minute whether the health check needs work. If your problem is that Select never completes at all, three failures that look identical to the host covers the other two causes. Every byte above comes out of one socket against the passive listener in the SECS/GEM simulator.