Segmentation: The ISO-TP State Machines

A classic CAN frame carries eight bytes; a firmware block or a calibration table does not fit. The transport layer solves that with ISO 15765-2 segmentation. The frame formats, the flow-control fields and the timing parameters are specified in the Protocol Specification §4.1, and the class inventory and storage pattern are in Architecture and Design Decisions §10.1. This chapter covers what neither has: the two state machines, and the failure paths through them.

The layer is optional and orthogonal. A node that attaches it gains multi-frame payloads for the message types that opt in; every other message type continues to travel as a single frame, unaware the transport exists.

1. Channels

A channel is a connection with one peer, made of two identifiers: the one the peer transmits on, which this node receives, and the one this node transmits on. Everything the peer sends arrives on the first, data and flow control alike, and everything this node sends leaves on the second. Each channel owns one sender and one receiver, which are independent: a channel can be transmitting one payload while reassembling another, because the data of one direction and the flow control of the other never share an identifier.

Three properties of that lifecycle matter in integration:

Routing a received frame is a decision the channel makes and reports upward: the protocol layer offers every frame to the transport and continues its own pipeline only if the transport declines. That single boolean is the whole mechanism by which segmentation is transparent.

2. The sender

The sender refuses a transfer up front rather than failing part-way: an empty payload, one beyond the protocol’s length field, one beyond the configured buffer, or a second transfer while the channel’s sender is busy are all refused at the point of asking. What is accepted is then copied into the channel’s own buffer, so the caller’s buffer may be reused immediately — which matters when the source is a stack temporary or a flash read buffer.

N_Bs is armed on entering the wait, not on leaving the driver. The timer starts at the moment the sender decides it is waiting for flow control, before the frame is handed down for transmission. Arming it from the send completion instead would leave a window in which the sender is waiting with no timer at all — and a driver that never reports completion would strand it there permanently, the one failure a timeout exists to catch. It also lets a flow control frame that arrives inside that window cancel a timer that was never started, after which the late completion would arm N_Bs against a transfer that has already moved on to sending, aborting a healthy transfer.

Pacing is the receiver’s to choose. Reassembly is a copy into a preallocated buffer and needs no pacing for its own sake, so can-lite’s receiver asks for none by default. The rate limit is the reason to ask: every frame of a transfer is charged against it, and an unpaced peer can spend a second’s budget within one transfer. A node can therefore configure the block size and separation time its receiver advertises. The sender implements the general case, honouring whatever block size and separation time a peer asks for, so it interoperates with conventional stacks that pace transfers. A pacing request it cannot parse is treated as the slowest legal value, which is the standard’s conservative reading: an unparseable request must not be read as “go as fast as you like”.

Aborts, and the completion that does not run

A transfer’s completion runs on success only. A transfer that aborts reports through the abort path instead. An application that handles only the completion will wait forever. The abort path is the application’s to subscribe to: the protocol layer leaves it alone and keeps the channel open.

The abort reasons and what raises each are listed in the glossary. Two are worth stating here because they are easy to get wrong.

A failure to hand a frame to the bus reports its own reason rather than borrowing the one used for an unparseable frame. The two say different things to an application — a saturated transmit queue is a local condition that will pass, a malformed frame is a statement about the peer — and collapsing them costs the only diagnosis the abort path can offer.

An overflow is likewise a state only the peer can declare. A flow control frame that is too short to decode, or that carries one of the reserved flow statuses, is therefore an unexpected frame and not an overflow: reporting it as an overflow would tell the application the peer ran out of buffer when in fact the frame never parsed. The standard requires the reserved values to abort rather than be read as permission to continue, which is the failure mode that matters — a corrupted nibble must never be mistaken for “carry on”.

A completion that arrives after its transfer has ended, by an abort, a release or a later transfer, is discarded. Each transfer carries its own token, so a driver reporting a stale failure cannot abort the transfer that came after.

3. The receiver

The validation it performs, in order, is where most of the interesting failure behaviour lives, and Chapter 10, §8 tabulates every case. Two asymmetries are worth understanding here rather than there:

A valid single or first frame arriving mid-reassembly means the peer has moved on. The partial transfer is discarded and reported as an unexpected frame, as the standard requires, and the new frame is processed as the start of a new reception.

4. A multi-frame transfer, end to end

The two completions are independent, and neither is an end-to-end acknowledgement: the sender’s says the last frame reached its own controller, the receiver’s says the payload is complete. Confirming that the peer acted on it is the job of the category’s own protocol, one layer up.

5. Wiring segmentation into a category

Three steps, none of which touches the protocol core:

  1. Attach the transport to the server or the client, choosing the payload size and channel count — this is the sizing decision of Chapter 11.
  2. Register the receive channel for the identifier pair the category’s message will use.
  3. Give that message type a segmented handler, alongside its single-frame one.

Because a reassembled payload runs the same pipeline as a single frame, a validated category still finds its sequence number in the first byte and still skips it. The only difference a category sees is that the payload may be longer than eight bytes.

Sending is the application’s half, and it mirrors the receiving one:

  1. Attach a transport to the sender’s protocol object too. Flow control from the receiver arrives as frames, and only an attached transport routes them back to the waiting sender.
  2. Address the payload the way a command is addressed — the category, the message type and the server — on the identifier the server’s channel receives on, and take flow control from the identifier it transmits on.
  3. Supply the sequence number yourself when the category validates it, reserving it from the client and giving it back if the transfer is refused or aborts. A segmented send bypasses the category’s own send path, which is what normally does this.
  4. Subscribe to the abort path. The completion runs on success only (§2).

The integration scenarios run exactly this wiring on both nodes, including a transfer that loses its flow control and one whose frames the driver fails to send (Chapter 12).

6. Deliberate omissions

Beyond the parameters in the specification, four things are simply not implemented, and each has a cost worth knowing before choosing this transport:

Omission Cost
Frames are never padded to full length A peer that requires 8-byte frames will not interoperate; short frames keep a loaded bus shorter
Only normal addressing No address extension, no mixed addressing
Classic CAN only The length field and frame sizes assume 8-byte frames

The first two are the pair worth revisiting first if can-lite ever has to talk to a third-party stack, because they are the ones a conventional diagnostic tester is most likely to insist on.