Modbus FIFO Reads: Validate the Queue Response Before Treating It as a Register Block
Modbus function 24, hexadecimal 0x18, returns a two-byte Byte Count, a separate two-byte FIFO Count and up to thirty-one queued register values. It is not a holding-register response with a one-byte count. Validate both counts and the exact PDU length before exposing values, and do not interpret a successful read as a dequeue operation.
This guide pins Modbus Application Protocol V1.1b3, 26 April 2012, section 6.18. The original complete-PDU validator described below was executed offline. Device FIFO contents, timing, atomicity and firmware behavior were not tested.
Identify the FIFO contract first
The request contains the function byte followed by a two-byte FIFO Pointer Address. It does not carry the requested quantity field used by ordinary register reads. Confirm that the device supports function 24 and document which pointer addresses select meaningful queues.
The protocol specifies that reading returns queue contents without clearing them. A product may provide a separate application mechanism for consuming or acknowledging entries, but that mechanism must come from its documented map. Do not invent a follow-up write to the pointer register or assume repeated reads automatically advance the queue.
Also establish the meaning of each returned register: event code, sample value, part of a larger record or another device-defined field. A FIFO Count of four means four data registers, not necessarily four complete application events.

Write the response length ledger
| Field | Width | Meaning |
|---|---|---|
| Function | 1 byte | 0x18 for a normal response |
| Byte Count B | 2 bytes | Bytes following it: FIFO Count plus data values |
| FIFO Count N | 2 bytes | Number of queued data registers, at most 31 |
| FIFO values | 2N bytes | N register values in protocol byte order |
For a normal response, B=2+2N and complete PDU length=3+B=5+2N. The empty case therefore contains five PDU bytes, not a missing response. At the maximum count, B is sixty-four and the PDU contains sixty-seven bytes.
The specification’s statement that up to thirty-two registers can be read includes the count register itself. It does not permit thirty-two queued data registers. Section 6.18 specifies exception code 03 when the queue count exceeds thirty-one.
Validate before allocation or partial delivery
- Associate the complete response with the outstanding FIFO pointer request through the transport’s transaction rules.
- Recognize function 0x98 as the exception path and validate its exception structure separately.
- For normal function 0x18, require enough bytes for both two-byte count fields.
- Decode N and reject values above thirty-one or above the integration’s admitted local capacity.
- Require B to equal two plus twice N, and require the actual PDU length to equal three plus B.
- Decode all N values into a bounded result, then publish success once the complete result is valid.
Do not allocate a buffer directly from an untrusted two-byte Byte Count. The admitted function-specific size is much smaller than the field’s numeric range. If the local application can hold fewer than thirty-one registers, reject an oversized result explicitly rather than silently truncating it.
A failed later check must not leave the first few decoded values reported as a successful FIFO read. Decode into a temporary result or validate the whole shape before committing output ownership. Keep malformed responses, valid empty queues and valid exception responses as distinct outcomes.
Use boundary fixtures that expose the common mistakes
A synthetic empty normal PDU is 18 00 02 00 00. A hypothetical two-value response containing 0x1234 and 0xABCD is 18 00 06 00 02 12 34 AB CD. These are PDUs only: no server address, RTU CRC or TCP header is included.
A generic register-read parser may treat the first Byte Count byte, zero in these examples, as the whole count. Another parser may include the FIFO Count among returned values and report one extra item. The explicit field ledger catches both defects before application interpretation.
The executed model covered every valid N from zero through thirty-one. It checked the five-byte empty case, nine-byte two-value case and sixty-seven-byte maximum. Eight malformed vectors covered truncation, trailing bytes, inconsistent counts, an excessive FIFO count, wrong function and malformed exception/header shapes. A separate capacity test rejected two values when local capacity was one.
The model also recognized a correctly formed 98 03 exception separately. It did not simulate an RTU stream, TCP reassembly or a real server. Use those additional layers’ own qualified framing checks before presenting a complete PDU to this validator.
Do not turn repeated reads into duplicate consumption
Because the read does not clear the queue, two successful requests can legitimately return the same contents. A hypothetical event collector that emits every returned register as a new event on every poll could create duplicates even with a perfect transport and parser.
Define consumption using the device’s documented sequence, generation, cursor or acknowledgement mechanism if one exists. Payload equality alone is not a safe event identity: two genuine events can have equal values. If the device exposes no way to distinguish observations, report that limit instead of promising complete exactly-once extraction.
Clarify whether updates can occur while the queue is read and whether the device supplies a coherent snapshot. Function 24’s response format does not establish every manufacturer’s producer concurrency or overflow policy. Those are qualification questions for the selected firmware.
Close the integration with device-specific evidence
Use a simulator or documented lab queue to exercise empty, one-value and supported maximum cases, then repeat a read without consuming anything. Verify that the interpretation matches the nondestructive protocol contract. Separately test the documented consumption mechanism, if required, under its operational permissions.
Deliver the FIFO pointer map, register-to-record schema, local capacity, response validator results and consumption policy together. The useful acceptance claim is that every admitted response has a consistent bounded shape and a defined application meaning, not merely that function 24 returned bytes.