OPC UA TCP Chunk Limits: Bound Message Assembly Before Large Requests Exhaust RAM
Enforce OPC UA TCP limits at two different boundaries: the encoded MessageChunk before handing it to the secure-channel layer, and the accumulated unencrypted message body during reconstruction. Keep chunk count, request association and partial-message lifetime bounded as well. A legal individual chunk does not imply that an indefinitely growing message fits the device.
This guide pins OPC UA Part 6, Mappings, version 1.05.07, published 15 April 2026, sections 6.7 and 7.1. It addresses UACP/UASC over TCP using a qualified security implementation. An original header-admission and post-security assembly model was executed. It is not an OPC UA SDK, cryptographic implementation or complete wire decoder.
Write a directional Hello/Acknowledge ledger
| Field | Client Hello | Server Acknowledge |
|---|---|---|
| ReceiveBufferSize | Largest chunk client can receive | Largest chunk server can receive; bounded by Hello SendBufferSize |
| SendBufferSize | Largest chunk client will send | Largest chunk server will send; bounded by Hello ReceiveBufferSize |
| MaxMessageSize | Maximum response message body | Maximum request message body |
| MaxChunkCount | Maximum chunks in a response | Maximum chunks in a request |
The message-size limit is calculated from the unencrypted message body. It is not the sum of encrypted chunk sizes including repeated security headers, signatures and padding. Chunk-size checks and message-body accounting therefore need separate counters.
Zero MaxMessageSize or MaxChunkCount means no limit at that advertised boundary. It does not create memory on an embedded receiver. Configure truthful finite local receive limits and retain resource admission controls for connections and outstanding work.

Qualify the negotiated minimum and equality boundary
This release distinguishes a minimum transport buffer of 8,192 bytes for the ordinary non-ECC case from 1,024 bytes when the specified ECC conditions apply. Acknowledge constrains its receive and send buffers against the opposite Hello direction and the corresponding minimum. Do not copy a universal small-buffer constant across SecurityPolicies.
There is a wording edge worth testing: section 7.1.2.2 specifies MessageSize strictly less than negotiated ReceiveBufferSize, while the Hello/Acknowledge tables describe the largest chunk and the error table speaks of exceeding the limit. The local fixture deliberately follows the strict gate and rejects equality. Record this conservative choice and qualify the selected SDK’s conformance/interoperability behavior rather than hiding the boundary.
Reject an oversized prefix before accumulating its body
The UACP prefix is eight bytes: a three-byte message type, one further byte and a UInt32 MessageSize. MessageSize includes the header. Verify the supported type, minimum structural size and receive cap before accepting an advertised large body.
For secured conversation messages, MSG permits intermediate C, final F and abort A. OPN and CLO use F in this version. Keep those rules separate from the reserved byte of Hello, Acknowledge and other connection-protocol messages.
The executed prefix model supports only MSG. A sixteen-byte synthetic header-plus-body fixture passed all fifteen single split positions and a coalesced two-frame input. Sizes below eight, equal to its 8,192-byte cap and at UInt32 maximum were rejected after eight bytes. This fixture is not a complete valid secured UASC message.
Associate verified chunks before joining them
Part 6 requires chunks belonging to one message to be sent sequentially. RequestId identifies the request and its associated response across their chunks. SequenceNumber belongs to each chunk on the SecureChannel, with security-policy-dependent rollover rules; it is not interchangeable with RequestId.
Use the supported stack to verify channel, token, sequence and security. Then check that each accepted continuation belongs to the active assembly. Do not concatenate a different request merely because it arrived next on the same TCP socket.
Before appending, check the new body span against remaining capacity and the next chunk against the count limit. Stage partial data privately. Only a valid final chunk may expose the complete message to service decoding and application dispatch.
Treat abort as a verified disposal instruction
An A chunk reports an aborted message and tells the receiver to discard earlier chunks. The receiver must check its security before acting on it. A successfully verified abort discards that message without closing the SecureChannel; its error is reported appropriately to the requesting application.
Do not let an unverified abort erase arbitrary assembly state as though it were trusted peer evidence. Security failures follow the stack’s failure path. Also keep an abort body, containing its error information, out of the interrupted service payload.
The local post-security model accepts a supplied verification result. It does not verify a signature. One case assembled a partial request, processed a verified abort, then completed another request on the still-open channel. A failed-verification abort instead entered the model’s fault path without dispatch.
Bound partial lifetime and total memory
A peer can stay below every size limit while withholding a final chunk. Define a supported incomplete-message deadline and connection cleanup policy so partial state cannot remain forever. Expiry is a local resource decision, not a fabricated successful service response.
The memory ledger should include active chunk buffers, decrypted data, assembly storage, decoder allocations and completed requests awaiting workers. Multiplying one message cap by the allowed concurrent connections can reveal a larger requirement than the single-message demonstration suggests.
In the hypothetical assembly fixture, two clear-body spans of four and five bytes complete under a ten-byte limit. Separate cases exceed total body size, exceed a two-chunk limit, interleave request identifiers and expire an unfinished assembly. All reject or clean up without application output.
Release qualification
Repeat prefix partitioning, negotiated limit boundaries, body and chunk-count exhaustion, request interleaving, verified abort and partial-message expiry with the selected SDK and SecurityPolicy. Verify the actual Bad_RequestTooLarge, Bad_ResponseTooLarge or transport-level error path appropriate to sender, direction and failure stage.
The host model establishes bounded policy decisions, not target RAM usage, timing, full sequence validation or interoperability. Acceptance requires a directional limit ledger and evidence that incomplete or rejected chunks cannot accumulate unbounded storage or escape as a partially valid service request.