Corner Cases and Failure Modes

This is the catalogue of what happens when things go wrong: bounds are reached, peers disappear, frames arrive out of order, configurations disagree. Each entry states the condition, the mechanism that handles it, and the observable outcome — what an engineer with a bus analyser and a debugger would actually see.

Entries marked latent are correct today but rest on an assumption worth knowing. Entries marked design limit are deliberate simplifications with a stated cost.

1. Frame level

# Condition Mechanism Observable outcome
1.1 Standard-identifier frame received Identifier-width gate, first in both pipelines Ignored entirely: no answer, no counter, no notification. This is what lets can-lite share a bus with another protocol
1.2 Empty payload on a validated category Emptiness checked before the sequence check Answered “invalid payload”; the sequence counter is untouched
1.3 Empty payload on a non-validated category Reaches the handler, which under-runs while reading The handler’s validity check produces “invalid payload”; a handler that omits the check acts on zeros
1.4 Full eight-byte payload on a validated category The sequence number occupies the first byte Only seven bytes reach the category — a payload designed for eight silently loses its last field
1.5 Payload longer than eight bytes Impossible: the frame type is bounded —
1.6 Driver delivers frames out of order None — ordering is assumed Validated commands draw spurious sequence errors; the client resynchronises each time and progress stalls. Latent: ordering is a driver requirement (Chapter 3, §4)
1.7 Driver completes a send from inside the send call None Queue drain recurses once per queued frame — at most eight deep. Latent: the shipped host adapters defer completions for exactly this reason

2. The outbound queue

# Condition Mechanism Observable outcome
2.1 An ordinary send when six ordinary frames are queued, or all eight slots are taken Refusal, unless a less urgent frame is queued (2.1a) The frame is dropped, counted and never retried. What the caller sees depends on who it was: a category’s send reports failure; a client command reports failure without consuming a sequence number; an acknowledgement is simply lost
2.1a A more urgent send when its share is taken and a less urgent frame is queued The newest frame of the lowest queued priority is displaced The frame is admitted; the displaced frame’s completion reports failure exactly once; the displacement is counted. A displaced heartbeat costs nothing: its timer sends the next one
2.2 An acknowledgement lost to a queue full of frames no less urgent than it Its completion discards the outcome The client’s acknowledgement timeout fires instead. Deliberate: retrying would deepen a queue that is already saturated
2.3 A send completion that itself sends The queue advances before the completion runs Safe: the new frame goes out immediately if the queue emptied, otherwise it takes its place in priority order
2.4 Something other than the protocol object tries to own the send notification Fixed when the transport is constructed Not possible: the transport has no setter, so heartbeat deferral cannot be displaced
2.5 The node’s address is changed with frames already queued None Queued frames keep the identifier they were built with; only later frames carry the new address
2.6 The driver reports a transmission failure The outcome propagates to the caller; the failure is counted Server categories ignore it; a client command gives its sequence number back (5.13); segmentation treats it as an abort. Design limit: there is no bus-off recovery machine in the library
2.7 An emergency frame arrives while ordinary traffic fills its share Two slots are reserved for emergency frames Admitted without disturbing any queued frame, and sent as soon as the frame on the bus completes
2.8 An emergency frame arrives at a completely full queue The newest frame of the lowest queued priority is displaced The emergency frame is admitted; the displaced frame’s completion reports failure exactly once; the displacement is counted
2.9 An emergency frame arrives while eight emergency frames are queued Refusal The frame is dropped and counted as an emergency drop. Nothing lower-priority is left to displace
2.10 The controller fails to send an emergency frame Bounded retransmission, ahead of the queue Sent again up to twice; if every attempt fails, its completion reports failure and the frame is counted as an emergency drop

3. The server’s receive pipeline

# Condition Mechanism Observable outcome
3.1 Frame addressed to another node Address filter Silent — answering would produce noise proportional to the number of servers
3.2 Broadcast frame The filter admits it Accepted and dispatched; this is how the client’s heartbeat reaches every server
3.3 Ordinary rate limit reached Rate gate, ordinary budget Silent, including any acknowledgement that would have followed, and counted. The specification defines a “rate limited” status, but it is deliberately never sent
3.3b Emergency command arrives after a flood has used up the ordinary budget Emergency priority is charged to its own budget Admitted and dispatched as if the bus were quiet
3.3c More emergency frames than the emergency budget allows Rate gate, emergency budget The excess is dropped silently and counted. The emergency path is bounded like any other
3.3a A long segmented transfer arrives The rate gate precedes the segmentation gate Every frame is charged and the reassembled unit is not charged again. Charging after segmentation claimed the frame left the whole path free
3.4 Burst straddling a window reset The window is fixed and resets on a timer Up to twice the configured number can be accepted in quick succession. Design limit: a sliding window would need a timestamp per frame (§7.2)
3.5 A response arrives at a server Command/response gate Silent; prevents a response being dispatched as a command
3.6 One server sees another’s acknowledgement or discovery answer Those message types are numerically in the command range, so the address filter, not the type gate, rejects them Silent, because such frames carry the sending server’s own address. Latent: the protection here is addressing, not typing
3.7 Frame for an unregistered category Category lookup fails Silent — the frame may be legitimate traffic for a different server
3.8 Known category, unknown message type Dispatch reports “no handler matched” Answered “unknown command”; the sequence counter and the liveness timer are left alone
3.8a Known message type, handler rejects the payload Dispatch distinguishes “matched but rejected” from “no match” Answered “invalid payload”, so the client is not sent looking for a command that does exist; the sequence counter and the liveness timer are left alone
3.9 One category too many registered Slot limit Registration refused; the system category holds one slot from construction
3.10 Two categories claiming the same identity Identity scan Registration refused — including re-registering the same object
3.11 Registration failure ignored by the application None The category is invisible: its commands are dropped silently (3.7) and its client waits for answers that never come
3.12 A category destroyed while still registered None The list holds a dangling entry: the next frame for it, and the server’s own destruction, both walk into freed memory. Unregister first. Latent
3.13 A category acknowledges before being registered No acknowledger yet Dropped: nothing is sent. Only reachable when a test drives a category directly
3.14 A category acknowledges after being unregistered Unregistering clears the acknowledger it was given Dropped, rather than a call into a server that no longer owns it. A late firmware upgrade completion still sends its response
3.15 A category registers two handlers for one message type Checked when the second is added Assertion, rather than one handler silently shadowing the other

4. Sequence validation

# Condition Mechanism Observable outcome
4.1 First validated command after the server starts The baseline is adopted Accepted whatever its value: a client that restarts from zero needs no help from the server
4.2 A gap in the sequence Rejection Answered “sequence error” carrying the expected number; the counter does not advance
4.3 Repeated gaps Rejection is idempotent Every rejection names the same expected number — no drift, no lockout
4.3a Malformed or unrecognised commands carrying the next number The number is recorded before dispatch and restored if the handler does not accept The expected number stays where it was; the real next command is accepted
4.3b An emergency command after arbitrary earlier traffic The sequence check is skipped for emergency priority Accepted whatever its number; that number becomes the baseline, so the client’s next ordinary command is accepted too
4.4 The counter wraps Modular arithmetic Transparent
4.5 The client process restarts mid-session Resynchronisation from the expected number One command lost, one round trip wasted, traffic continues (§7.1)
4.6 Two clients commanding one server One shared counter Each breaks the other’s ordering; both see near-continuous sequence errors. Design limit: one client per server (REQ-CAN-006.1)
4.7 One client using two validated categories on one server One counter per server on each side Correct: one interleaved stream, one ordering
4.8 A client sending without a sequence number to a validating server None The payload’s first byte is read as a sequence number: sporadic rejection plus a mis-parsed payload
4.9 A client sending with a sequence number to a non-validating server None The sequence byte is delivered as payload, and every field is shifted by one
4.10 A send refused by a full queue The reservation is undone exactly The number is not consumed and nothing else changes; the next attempt reuses it and the server sees no gap

Entries 4.8 and 4.9 are the two configuration errors with no diagnostic at all (Chapter 5, §5).

5. Client state

# Condition Mechanism Observable outcome
5.1 A ninth server commanded Round-robin eviction of a sequence slot The evicted server’s counter restarts; its next command draws one sequence error and resynchronises (§7.3)
5.2 A ninth server heard from Round-robin eviction of a liveness slot The evicted server is reported offline and the new one online. On a bus with more active servers than slots the application sees continuous churn — the honest signal that the client is under-provisioned
5.3 A second command to a server before the first is answered The tracked command is replaced The first answer no longer matches and does not cancel the timer, which now belongs to the second command
5.4 Two consecutive commands with the same identity Matching is by category and message type only The first command’s acknowledgement cancels the second command’s timer, so a lost answer to the second goes unreported. Design limit: one outstanding command per server
5.5 A malformed acknowledgement, or one claiming an impossible source Guarded before it is acted on Ignored: no timer cancelled, no counter changed; the command’s timeout fires normally
5.6 An acknowledgement from a server other than the one commanded State is kept per server Only that server’s state is touched; the commanded server’s timer keeps running
5.7 A stale sequence-error acknowledgement arrives late Applied unconditionally The counter jumps to a value the server may have moved past, costing one further round trip. Self-correcting
5.8 Cancelling a reservation other than the latest Checked at run time Assertion. Only reachable by bypassing the category’s send path
5.9 A second discovery before the first answer A single pending discovery Refused; the pending one is untouched
5.10 A discovery answer from a different server The answer is matched to the server asked Ignored; the discovery stays pending
5.11 Discovery never answered The discovery timeout Completes without an answer, and the next discovery may start
5.12 No observer attached Notification checks first Safe: events are discarded silently. The firmware upgrade category answers its commands instead (9.11)
5.13 A queued command whose frame never leaves the node Its completion gives the number back The counter steps back and the acknowledgement timeout reports the loss. If a later command already took the next number, that command draws one sequence error and resynchronises
5.14 The answer arrives before the driver’s send call returns Reserved before the driver sees the frame Matched as usual: an acknowledgement cancels the timer, and a sequence error sets the number the next command carries
5.15 A command to a ninth server refused by a full queue The reservation is undone exactly The evicted server keeps its slot, its counter and the command it awaited

6. Liveness and heartbeat

# Condition Mechanism Observable outcome
6.1 The node is busy sending The heartbeat timer restarts on every outgoing frame No heartbeat at all; liveness is carried by the traffic itself
6.2 A client commands continuously and never sends a heartbeat The server restarts its timer on every accepted command The client is not declared offline for being busy
6.2a A departed client’s address keeps receiving malformed, unknown or out-of-sequence frames Refused frames do not restart the timer The client is still reported offline when its timeout expires, so the application’s fallback runs
6.3 The bus goes silent Liveness timers expire Both sides report their peer offline
6.4 A prolonged outage The offline state is remembered Reported once, not once per timer expiry
6.5 A server heartbeat reaches the client Liveness is marked before dispatch Reported online; the frame then finds no handler in the client’s system category, which is harmless
6.6 A hardware acceptance filter that excludes the broadcast address None The server never sees its client’s heartbeat and reports it offline once per timeout on an idle bus. Latent (Chapter 3, §4)
6.7 Peers with different protocol versions The version is carried but not checked They communicate regardless. Design limit: version negotiation exists on the wire only
6.8 The heartbeat falls due while the outbound queue is full Sending a heartbeat re-arms the heartbeat timer directly The heartbeat is dropped, and the next one is attempted an interval later. Re-arming only from the send notification would have stopped heartbeats for good, because a refused frame produces no notification
6.9 A burst of status requests, or the timer falling due, while a heartbeat is queued At most one heartbeat is pending at a time One heartbeat answers the whole burst. Before, the backlog a bus adapter releases when the server comes back after a restart filled the ordinary share, and the first command’s acknowledgement was refused

7. Four walkthroughs

7.1 A client restart mid-session

The lost command is the application’s problem, deliberately: replaying a command with side effects is not the protocol layer’s decision to make. The application learns of it from the missing response, or from the acknowledgement timeout.

7.2 The rate-limit window boundary

The limit is a tumbling window, not a sliding one. Sizing a server therefore means budgeting for twice the number in the worst case, or configuring half of what the hardware can absorb.

7.3 The ninth server

The cost of eviction is bounded and self-healing: one wasted round trip per re-admission. Refusing to talk to a ninth server would fail harder for no benefit.

7.4 A transfer abandoned mid-flight

The last line catches applications out. The completion means “the transfer succeeded”; failure arrives on the abort path, and an application that handles only the completion waits forever.

8. Segmentation

# Condition Mechanism Observable outcome
8.1 Payload beyond the protocol’s length field, or beyond the configured buffer Refused when the transfer is requested Nothing is transmitted
8.2 Empty payload Refused Segmentation has no zero-length encoding
8.3 A second transfer while the channel’s sender is busy Refused One transfer at a time per channel; more concurrency means more channels
8.4 No free channel Refused Both registration and transmission report it
8.5 A pair sharing one identifier with another channel Lookup matches either identifier of a pair Refused for both registration and transmission; overlapping pairs would make routing ambiguous
8.6 A transfer fails on a registered channel Reported, never released The channel stays open; the next transfer with that peer uses it as before
8.7 No flow control within N_Bs Sender timer Abort
8.8 No continuation frame within N_Cr Receiver timer Abort; the partial payload is discarded
8.9 The peer asks to wait too many times in a row Wait limit Abort
8.10 The peer reports overflow Flow-status check Abort
8.11 A continuation frame out of sequence Sequence check Abort; the partial payload is discarded
8.12 The frame sequence number wraps Modular on both sides Transparent; long payloads are ordinary
8.13 A first frame declaring a length that would fit in one frame Length check Ignored, as ISO 15765-2 requires; a reception in progress carries on
8.14 A first frame shorter than a full frame Size check Ignored; a reception in progress carries on
8.15 A first frame declaring more than the buffer holds Capacity check Overflow is signalled to the peer, then abort
8.16 A single frame longer than the buffer Capacity check Abort, without signalling — the peer has already finished
8.17 A valid single or first frame arriving mid-reassembly The partial transfer is discarded Reported as an unexpected frame; the new frame starts a new reception
8.18 An unsolicited continuation frame State check Ignored silently — not an error
8.19 An extra continuation frame after completion The receiver is idle again Ignored
8.20 A frame carrying more bytes than remain Bounded by the declared length Surplus dropped, so padding from a padding-using peer is harmless on the last frame
8.21 A send fails mid-transfer The failure is reported as an abort Reported as send failed, distinct from anything the peer did; the channel stays open
8.22 A pacing request the standard reserves Mapped to the slowest legal value Conservative by design
8.23 A peer that requires padded frames can-lite never pads No interoperation. Design limit (Chapter 8, §6)
8.24 An unknown protocol control nibble, or a single frame with an impossible length Classification check Ignored, as ISO 15765-2 requires; a reception in progress carries on
8.25 Sending on the registered peer’s own data identifier Pair check Refused: data to a peer leaves on the transmit identifier, the reverse of the registered pair
8.26 A channel released mid-transfer Timers cancelled, state reset Silent; nothing from the old transfer reaches the slot’s next user
8.27 A stale completion from a transfer that has ended Per-transfer token Discarded; it cannot abort the transfer that followed
8.28 A consecutive frame too short to be legal, or carrying no data Length check against the bytes still expected Ignored; it neither advances the sequence nor restarts N_Cr, so it cannot keep a reception alive
8.29 The driver never reports a single or consecutive frame sent N_As Abort, reported as an N_As timeout

9. Category level

# Condition Mechanism Observable outcome
9.1 A validated handler that does not skip the sequence number None Every field is shifted by one byte; values are plausible and wrong. Not detectable at compile time
9.2 A handler that does not check payload validity Reads return zeros The command executes with zeroed parameters
9.3 A completion never called None No response, no acknowledgement. A sequence-validated command meets the client’s acknowledgement timeout; an unsequenced one, such as a firmware upgrade command, only the client application’s own
9.4 A completion called twice None Two answers to one command
9.5 Overlapping commands in a stateful category The category does not serialise A second command is dispatched while the first is pending; a category that cannot cope must refuse it itself
9.6 A client crashing mid-upgrade The session timer The application is told to release its staging state. No frame is sent — there is nobody to tell
9.7 A supervisor polling progress while the client is dead Polling does not extend the session The session still expires, as intended
9.8 A firmware block lost or replayed The application compares block indices Reported as a category error; the checksum at the end is the backstop
9.9 An observer that allocates or blocks None Heap use on a no-heap target, or a stalled receive path. The rule is stated, not enforced
9.10 A begin or block the application refuses Timer started only on acceptance No session is opened or prolonged, so no expiry follows
9.11 No application attached to the firmware upgrade category Checked before notifying Every command is answered not ready; an abort is acknowledged and progress reports idle
9.12 A firmware upgrade response the send queue refuses None Not repeated. The client’s own response timeout and a progress query recover
9.13 A firmware upgrade response with an undefined status or state Range check on the client Discarded as an invalid payload; the application is not told

10. Configuration and topology

# Condition Mechanism Observable outcome
10.1 A server configured with the broadcast address Checked at construction Assertion
10.2 Two servers sharing an address None Both accept the same commands and both answer; the client attributes every answer to one node and their counters diverge. Addresses must be unique by construction
10.3 Two protocol objects on one bus interface The interface holds a single receive callback The second construction replaces the first’s callback, and the first stops receiving entirely
10.4 A liveness timeout no longer than the peer’s heartbeat interval None On an idle bus the peer times out before the heartbeat arrives, producing an online/offline flap (Chapter 11, §2)
10.5 An acknowledgement timeout shorter than the server’s worst-case handler latency None Spurious timeouts for commands that are merely slow
10.6 A rate limit below the client’s steady-state command rate Rate gate Commands are dropped silently, and the client sees timeouts with no explanation on the bus
10.7 A protocol object destroyed while its bus interface lives on Its destructor deregisters the receive callback and releases the send-notification slot The interface holds nothing rather than a callback into freed memory, and the slot can be claimed again by a replacement
10.8 Something else claims the receive callback after a protocol object was built None — the interface cannot report who owns it The protocol object stops receiving from that moment, and its destructor later clears the newcomer’s callback. Latent: the slot has one owner (see below)
10.9 Segmentation destroyed before the protocol object it is attached to Detaching is the application’s to do The protocol object still holds the pointer its receive path dereferences. Latent: segmentation is built from that object’s transport, so it dies first

The receive callback has exactly one owner. hal::Can stores a single callback and offers no way to read back who installed it, so neither registration nor deregistration can be conditional on still owning the slot. Construction claims it unconditionally and destruction releases it unconditionally, and the two are symmetric. A second claimant therefore breaks the arrangement at the moment it registers — entry 10.3, not at teardown — and the later clear in 10.8 is a consequence of that, not a separate fault. One protocol object per bus interface, destroyed before anything else claims the slot, is the supported arrangement; detecting a violation would need hal::Can to expose ownership, which is an upstream change.

11. What is deliberately not handled

Not handled Consequence Mitigation
Authentication and integrity Any node on the bus can command any server, including a firmware upgrade Application-level signature checks; physical bus security
Confidentiality Payloads are visible to every node Application-level encryption
Replay by an attacker Sequence numbers deter accidents, not adversaries Application-level nonces or signatures
Bus-off recovery Send failures are reported, but there is no recovery machine Driver-level recovery, plus the liveness notifications to detect the outage
Duplicate address detection Silent misbehaviour (10.2) Assign addresses by construction or provisioning
More than eight servers, categories or queued frames Eviction, refusal or a dropped frame as catalogued above Compile-time limits; raising them is a library change