A SECS-II Item Header Can Address 16 MB. My Socket Died at 65,537 Bytes.
A SECS-II item header can address 16 MB of PPBODY. The socket died at a 65,537-byte frame. Where the real recipe size ceiling sits, in bytes.
I pushed a 70 KB recipe at a tool in one S7F3. send() reported all 70,031 bytes written. Then nothing came back. The next send() broke with EPIPE before T3 had a chance to fire, and the equipment's own packet log had no record of the message at all — no Reject.req, no S9F7, no entry.
The item header was not the problem. A SECS-II (E5) item header declares one, two or three length bytes, and three of them can write a length up to 16,777,215 bytes. 16 MB. What dies first is the HSMS frame carrying that item. On this listener the boundary is a message length field of 65,536, and at 65,537 the socket was reset. The ceiling you can write and the ceiling that can arrive are two different numbers.
The length byte count is declared, not derived
The first byte of an E5 item header splits in two. The upper six bits are the format code; the lower two say how many length bytes follow. A binary item's format code is 8, so that first byte reads 21 with one length byte, 22 with two, 23 with three. The declared number of big-endian length bytes follows, and their value is the item's body length in bytes.
The part host developers skip: the count is declared, not computed from the length. You may write a 400-byte item with two length bytes or with three. Both are legal and both work. Captures 2 and 3 below check that on one socket.
Capture — same payload, different length byte counts
I drove the listener from outside over three real sockets. The hex is what my host framed and received, cross-checked against the equipment's own packet log.
1. Select.req → Select.rsp, then S1F13 → S1F14
H→E 00 00 00 0A 00 0B 00 00 00 01 00 00 01 01
E→H 00 00 00 0A 00 0B 00 00 00 02 00 00 01 01
H→E 00 00 00 0C 00 0B 81 0D 00 00 00 00 01 02 01 00
E→H 00 00 00 37 00 0B 01 0E 00 00 00 00 01 02 01 02
21 01 00 01 02 41 15 ...
Setup. Skip the S1F13 and this listener answers a data message with SxF0 and nothing else — that is the subject of the E30 communication gate, not this one.
2. PPBODY 400 bytes, two length bytes → S7F4
H→E 00 00 01 AA 00 0B 87 03 00 00 00 00 01 03
01 02 41 09 45 54 43 48 5F 42 41 53 45 22 01 90 ... (430 bytes on the wire)
E→H 00 00 00 0D 00 0B 07 04 00 00 00 00 01 03 21 01 00
The length field 00 00 01 AA is 426 = 10 header + 416 body. The body is L[2] in 2 bytes, PPID A "ETCH_BASE" in 11, the PPBODY item header 22 01 90 in 3, then 400 bytes of payload. 22 declares two length bytes and 01 90 is 400. The reply is 21 01 00, ACKC7 0.
3. The same 400 bytes, three length bytes → S7F4 again
H→E 00 00 01 AB 00 0B 87 03 00 00 00 00 01 05
01 02 41 09 45 54 43 48 5F 42 41 53 45 23 00 01 90 ... (431 bytes on the wire)
E→H 00 00 00 0D 00 0B 07 04 00 00 00 00 01 05 21 01 00
Same 400-byte payload as capture 2, and the wire frame is one byte longer. The item header grew from 22 01 90 to 23 00 01 90; that is the whole difference. The answer is the same ACKC7 0. A decoder does not guess the header width from the length — it reads the declaration.
4. The boundary: a length field of 65,536 passes
H→E 00 01 00 00 00 0B 87 03 00 00 00 00 01 04
01 02 41 09 45 54 43 48 5F 45 44 47 45 22 FF E6 ... (65,540 bytes on the wire)
E→H 00 00 00 0D 00 0B 07 04 00 00 00 00 01 04 21 01 00
A 65,510-byte PPBODY, length field 00 01 00 00 = 65,536, exactly 64 KiB. It goes through and ACKC7 0 comes back. Note that this body is still under 65,535, so two length bytes (22 FF E6) are enough for it.
5. One byte more: the socket dies at 65,537
H→E 00 01 00 01 00 0B 87 03 00 00 00 00 02 03
01 02 41 09 45 54 43 48 5F 45 44 47 45 22 FF E7 ... (65,541 bytes on the wire)
E→H (nothing — ECONNRESET)
I grew the PPBODY by one byte, to 65,511. The length field reads 65,537. send() reports all 65,541 bytes written and recv() raises ECONNRESET. One more Linktest.req on that socket gets EPIPE. This message never appeared as an RX in the equipment's packet log — the Select.req and S1F13 from the same connection are both there, and only the S7F3 is missing. The listener drops the socket the moment it has read the 4-byte length prefix, before the body arrives, so there is no message to record.
6. A 70,000-byte PPBODY with three length bytes → reset as well
H→E 00 01 11 8B 00 0B 87 03 00 00 00 00 03 03
01 02 41 09 45 54 43 48 5F 48 55 47 45 23 01 11 70 ... (70,031 bytes on the wire)
E→H (nothing — ECONNRESET)
This is the first capture where three length bytes were actually needed: 23 01 11 70 is 70,000, written in a header good for 16 MB. The length field is 70,027, so it dies in the same place capture 5 did. On this listener a three-length-byte item can be declared but can never arrive — anything over 65,535 bytes pushes its own frame past 65,536 by itself.
65,536 is this listener's number
Be clear about that. 64 KiB is an implementation's choice, not a SEMI figure. E37 puts a 4-byte length field in front of the message, and the field itself is wide enough for far larger messages. The real ceiling is whatever the equipment vendor wrote into their code, and the only value I verified byte by byte is this listener's: 65,536 accepted, 65,537 reset. Where other equipment cuts it, I have not verified. A tool that came up on SECS-I (E4) has a different ceiling again, and I did not touch that this run.
The ACKC7 0 in captures 2, 3 and 4 deserves the same caution. This listener answers data messages from a fixed reply table (read from its code). So those 21 01 00 bytes prove that the frame was accepted and decoded correctly as stream 7, function 3 — not that real equipment would store the recipe. What each ACKC7 value means has to come from E5's table checked against the vendor document, and where the two disagree the tool is right.
What the host side should do
- Get the maximum message size as a number in the interface spec. "Supports HSMS" is not an answer. If the cutoff is not written down, you will discover it with a recipe on the first day of integration.
- Check the PPBODY length before you send. Over the limit, your host should refuse it, not let the socket die. When the socket dies it takes every other transaction on that connection with it.
- Do not carry large recipes in S7F3. E5 gives a host no standard way to split a long PPBODY across messages. Either the tool accepts the frame or the recipe travels by another route, and that is an interface-spec decision, not something your HSMS stack can fix.
- Log the length field you emitted. "No reply" and "connection reset" are different symptoms. For the second there is no reason to wait out T3, and since the equipment log holds nothing you will be told it was the network. The length field number you sent is the only thing that ends that argument.
Why asking permission with S7F1 before a large PPBODY is cheaper is in recipes do not start at S7F3. S7F1 will not save you from the frame limit, though. That one happens after S7F3 is already on the socket.
To hit the same boundary yourself, start a passive listener on the SECS/GEM simulator and walk the length field up one byte at a time. Watching the ack arrive at 65,536 and the socket vanish at 65,537 takes about a minute.