Firmware Upgrade — Category Extension Specification
Category ID: 0x1
Extends: can-protocol.md
Version: 1.0
Status: Draft
Date: 2026
1. Overview
This document specifies the Firmware Upgrade category extension for the can-lite protocol. It defines a block-transfer protocol for sending firmware images from a client to a server over CAN, with integrity verification and activation control.
The category does not require protocol-level
sequence validation (RequiresSequenceValidation() = false).
Ordering is enforced by explicit block indices within the data transfer
messages, and the protocol-level 8-bit sequence counter would wrap too
quickly for large transfers (thousands of blocks).
1.1 Design Goals
- Reliability: Per-block acknowledgement and CRC32 verification before activation.
- Resumability: Block-indexed transfer allows the client to detect and retransmit missing blocks.
- Safety: New firmware is verified in-place before activation; the running firmware is never overwritten in-flight.
- Simplicity: No multi-frame segmentation; each CAN frame is self-contained.
1.2 Assumptions
- The server has a dual-bank (A/B) flash layout or a dedicated staging area where incoming firmware is written without overwriting the active image.
- The server’s bootloader or application can swap to the newly written image upon activation.
- Maximum firmware size per transfer: 393 216 bytes (65 536 blocks × 6 data bytes). For larger images, a page-based extension can be added in a future revision.
2. Upgrade States
| Value | State | Description |
|---|---|---|
| 0 | Idle | No upgrade in progress |
| 1 | Receiving | Accepting data blocks |
| 2 | Verifying | CRC32 check in progress |
| 3 | Complete | Verification passed, ready to activate |
| 4 | Error | Transfer or verification failed |
3. Error Codes
| Value | Error | Description |
|---|---|---|
| 0 | Ok | No error |
| 1 | Busy | Upgrade already in progress |
| 2 | InvalidSize | Firmware size exceeds available storage |
| 3 | SequenceError | Block index out of order or duplicate |
| 4 | WriteError | Flash write/erase failure |
| 5 | CrcMismatch | CRC32 verification failed |
| 6 | NotReady | Activate before verify, or no application |
| 7 | InvalidState | Command not valid in current upgrade state |
| 8 | SessionTimeout | No activity within the session timeout |
4. Message Types — Commands (Client → Server)
All commands are sent at CanPriority::command.
Every command is answered by its response from Section 5, followed by
a command acknowledgement for the same command (can-protocol.md §8.1.2):
success when the response’s status is Ok,
categoryError otherwise. Abort has no response, so its
acknowledgement alone answers it. Query Progress is always acknowledged
success.
The commands carry no sequence byte, so the client’s protocol layer runs no acknowledgement timeout for them. A client application that must detect a lost answer times out on the response itself (Section 7.5).
4.1 Begin Upgrade (0x00)
Initiate a firmware upgrade session. If an upgrade is already in
progress, the server responds with error code Busy.
| Byte | Field | Type | Description |
|---|---|---|---|
| 0–3 | FirmwareSize | uint32 | Total firmware image size in bytes |
Total: 4 bytes.
The server erases the staging area (if applicable), transitions to Receiving state, and responds with a Begin Upgrade Response (0x80).
4.2 Data Block (0x01)
Transfer a block of firmware data.
| Byte | Field | Type | Description |
|---|---|---|---|
| 0–1 | BlockIndex | uint16 | Zero-based block sequence number |
| 2–7 | Data | uint8[6] | Firmware data (up to 6 bytes) |
Total: 2–8 bytes. The last block may contain fewer than 6 data bytes.
Blocks must be sent in ascending order starting from 0. The server responds with a Data Block Ack (0x81) for each received block.
4.3 Verify (0x02)
Request CRC32 verification of the received firmware image against the expected checksum.
| Byte | Field | Type | Description |
|---|---|---|---|
| 0–3 | CRC32 | uint32 | Expected CRC32 of the complete firmware |
Total: 4 bytes.
The server transitions to Verifying state, computes the CRC32 over the received data, and responds with a Verify Response (0x82).
The checksum is CRC-32/ISO-HDLC, the CRC-32 of Ethernet and zlib:
| Parameter | Value |
|---|---|
| Width | 32 bits |
| Polynomial | 0x04C11DB7 |
| Initial value | 0xFFFFFFFF |
| Reflect input | yes |
| Reflect output | yes |
| Final XOR | 0xFFFFFFFF |
| Check value | 0xCBF43926 for ASCII 123456789 |
It covers the image bytes exactly as transferred: the data bytes of every Data Block in block-index order, the Begin Upgrade size in total, with no padding. An image whose size is not a multiple of the flash page is checked over its own size, not over the page-padded copy in flash.
4.4 Activate (0x03)
Instruct the server to switch to the newly uploaded firmware. This command is only valid after a successful Verify (state = Complete).
Empty payload.
The server responds with an Activate Response (0x83), then performs the bank swap or reboot. The server may go offline briefly during this process.
4.5 Abort (0x04)
Cancel the current upgrade session and discard received data. Valid in any state except Idle.
Empty payload.
The server transitions to Idle state and responds with a command acknowledgement (System category, success).
4.6 Query Progress (0x05)
Request the current state of the upgrade process.
Empty payload.
The server responds with a Progress Response (0x85).
4.7 Session Timeout
A session opens when the server’s application accepts a BeginUpgrade
with status Ok. If no further DataBlock is accepted, and no Verify,
Activate or Abort command arrives, within a configurable inactivity
period (default 30 s) after the accepted BeginUpgrade or the last
accepted DataBlock, the server automatically aborts the upgrade session
and transitions to Idle state. The application is notified via the
OnSessionTimeout() observer callback.
A refused BeginUpgrade opens no session, and a refused DataBlock does not extend one. Verify, Activate and Abort stop the timer when they arrive, whatever their outcome; a DataBlock accepted afterwards starts it again. QueryProgress does not reset the session timer.
The session timeout is configured per-category-instance via
FirmwareUpgradeCategoryServer::Config::sessionTimeout.
4.8 Server Without an Application
A server whose firmware upgrade category has no application attached answers every command itself, and opens no session:
- Begin Upgrade, Data Block, Verify and Activate are answered by their
response with status
NotReady, and acknowledgedcategoryError. The Data Block Ack echoes the block index. - Abort is acknowledged
success: there is nothing to discard. - Query Progress is answered with state Idle and zero blocks, and
acknowledged
success.
5. Message Types — Responses (Server → Client)
Response message type IDs follow the 0x80 + command_id
convention. All responses are sent at
CanPriority::response.
The client discards a response whose Status is not an error code from Section 3, or whose State is not an upgrade state from Section 2, as an invalid payload. Its application is not notified.
5.1 Begin Upgrade Response (0x80)
| Byte | Field | Type | Description |
|---|---|---|---|
| 0 | Status | uint8 | Error code (see Section 3) |
| 1–2 | PageSize | uint16 | Flash erase page size in bytes |
Total: 3 bytes.
PageSize informs the client of the server’s flash
granularity. This can be used for transfer optimization but is not
required.
5.2 Data Block Ack (0x81)
| Byte | Field | Type | Description |
|---|---|---|---|
| 0 | Status | uint8 | Error code (see Section 3) |
| 1–2 | BlockIndex | uint16 | Index of the acknowledged block |
Total: 3 bytes.
5.3 Verify Response (0x82)
| Byte | Field | Type | Description |
|---|---|---|---|
| 0 | Status | uint8 | 0 = CRC match, 5 = CRC mismatch |
Total: 1 byte.
On success, the server transitions to Complete state.
5.4 Activate Response (0x83)
| Byte | Field | Type | Description |
|---|---|---|---|
| 0 | Status | uint8 | 0 = activating, 6 = not ready |
Total: 1 byte.
5.5 Progress Response (0x85)
| Byte | Field | Type | Description |
|---|---|---|---|
| 0 | State | uint8 | Upgrade state (see Section 2) |
| 1–2 | BlocksReceived | uint16 | Number of blocks received so far |
| 3–4 | TotalBlocks | uint16 | Total blocks expected |
Total: 5 bytes.
6. Typical Flow
6.1 Successful Upgrade
6.2 Aborted Upgrade
6.3 Failed Block Write
7. Implementation Considerations
7.1 Flash Layout
A dual-bank (A/B) layout is recommended:
+-------------------+-------------------+
| Bank A (Active) | Bank B (Staging) |
| Running firmware | Incoming image |
+-------------------+-------------------+
The incoming firmware is written to the inactive bank. On activation, the bootloader swaps the active bank pointer and reboots. If the new firmware fails to start (watchdog timeout), the bootloader reverts to the previous bank.
7.2 Transfer Rate
At 1 Mbit/s with 6 data bytes per CAN frame (~120 bits per frame including framing overhead), the theoretical maximum throughput is approximately 6 KB/s (accounting for ack frames). Faster rates can be achieved by batching multiple data blocks before requiring acks (window-based acknowledgement), which is a possible future extension.
7.3 Security
For production deployments, consider:
- Signature verification: Validate a cryptographic signature (e.g., ECDSA-P256) appended to the firmware image before activation. The signature can be sent in a separate message type or included in the last data blocks.
- Encryption: If firmware confidentiality is required, encrypt the image before transfer and decrypt on the server after verification.
- Rollback prevention: Maintain a monotonic version counter in non-volatile storage; reject firmware with a lower version number.
These security extensions are out of scope for this initial specification but should be implemented for any deployment where the CAN bus is accessible to untrusted parties.
7.4 Large Firmware Images
The uint16 block index limits transfers to ~384 KB. For larger images, two extension strategies are available:
- Set Page (new message type): A “set page” command selects the upper bits of the address, and block indices are offsets within the current page. This allows addressing up to 4 GB with negligible overhead.
- Extended Data Block: A new message type using uint32 block indices, reducing data payload to 4 bytes per frame (256 MB max).
7.5 Error Recovery
- If a block ack indicates
SequenceError, the client should retransmit the expected block. - If a block ack indicates
WriteError, the client may retry the same block a limited number of times before aborting. - The server sends each response once. A response lost on the bus, or refused because the server’s send queue was full when its application completed, is not repeated, and no protocol-level timeout covers it (Section 4). The client should time out on each response, then send a Query Progress to determine the current state and resume from the last acknowledged block.