Software Requirements Specification – CanKit.Pro Protocol Stacks¶
Document type: Software Requirements Specification (SRS), modelled on ISO/IEC/IEEE 29148
Method: IREB principles, MASTeR sentence templates, MoSCoW prioritisation
Project: CanKit.Pro – higher CAN protocol layers for .NET, built on CanKit
Origin: written on 2026-07-14 in the predecessor repository
CanKit.Pro.legacy (reference: CanKit master @ 36866ff,
review docs/reviews/2026-07-14-deep-code-review.md)
Status: draft
Translation: this is the English translation of SRS-CanKit.Pro.md; the German document is the original and the one eng/verify-requirements-traceability.py reads
System boundary. This document was written when CanKit.Pro was still a fork of CanKit, and therefore partly states requirements on CanKit itself (layers L0/L1). CanKit.Pro is no longer a fork today: L0/L1 are CanKit, are consumed as NuGet packages and are here a constraint rather than a deliverable. Requirements on L0/L1 — above all
FR-RAW-001..005(frame ownership) — are for CanKit.Pro assumptions about the upstream; they can be enforced only upstream. The requirements actually implemented in this repository are:
Requirements Implemented in FR-RAW-010..015(demux/subscriptions),FR-RAW-030..034(TX confirm)CanKit.Pro.RawCanFR-RAW-020..024(threading/actor model)CanKit.Pro.ActorFR-RAW-040..041(addressing, filter overlap)CanKit.Pro.Addressing+CanKit.Pro.RawCanFR-RAW-050..051(deadlines, bus state)CanKit.Pro.ReliabilityFR-TP-001..020(ISO-TP)CanKit.Pro.IsoTpFR-TP-030..035(J1939-TP)CanKit.Pro.J1939TpFR-UDS-001..012CanKit.Pro.UdsFR-CO-001..059CanKit.Pro.CANopenFR-J1939-001..007CanKit.Pro.J1939Thus every requirement family of this document is assigned to a package of this repository (exception:
FR-RAW-001..005, see above). Which individual requirement is backed by a test is stated byeng/verify-requirements-traceability.py; "implemented" here does not mean "verified".
1. Introduction¶
1.1 Purpose of the document¶
This document specifies the requirements on CanKit for realising, on top of the existing raw-CAN library, a hardened raw-CAN service layer and transport and application protocol stacks built on it. It serves as the binding basis for architecture (docs/architecture/arc42-CanKit.Pro.md, written in parallel), implementation and acceptance tests.
1.2 Scope¶
The document specifies:
- L2 raw-CAN service layer – the hardening and extension of
ICanBus/CanFrameby multi-consumer demultiplexing, a binding frame ownership contract, TX confirmation, addressing helpers and a uniform threading model per protocol instance. - L3 transport layer – ISO-TP (ISO 15765-2) and J1939-TP (TP.BAM/TP.CM).
- L4 application protocol layer – UDS (ISO 14229 over ISO-TP), CANopen (CiA 301), J1939 application (SAE J1939/ISO 11783).
Out of scope: re-implementation of L0 (vendor adapters) and of the already existing L1 core functionality (ICanBus, CanFrame, CanRegistry) – these are taken as given and referenced only where L2 builds on them or where existing defects (see review) justify the L2 requirements.
1.3 Definitions, acronyms, abbreviations¶
| Term | Meaning |
|---|---|
| CAN | Controller Area Network (ISO 11898) |
| CAN-FD | CAN with Flexible Data-Rate (ISO 11898-1) |
| ISO-TP | ISO 15765-2, transport protocol for multi-frame PDUs over CAN |
| PDU | Protocol Data Unit |
| SF/FF/CF/FC | Single Frame / First Frame / Consecutive Frame / Flow Control (ISO-TP PCI types) |
| STmin | Separation Time Minimum (ISO-TP flow-control parameter) |
| N_As/N_Bs/N_Cr | ISO-TP timing supervisions (sender- and receiver-side deadlines) |
| UDS | Unified Diagnostic Services (ISO 14229) |
| DID | Data Identifier (UDS) |
| P2/P2* | UDS response times (server timing, default and extended after 0x78) |
| NRC | Negative Response Code (UDS) |
| SDO/PDO | Service/Process Data Object (CANopen, CiA 301) |
| NMT | Network Management (CANopen) |
| EMCY | Emergency Object (CANopen) |
| OD | Object Dictionary (CANopen) |
| PGN/SPN | Parameter Group Number / Suspect Parameter Number (SAE J1939) |
| TP.BAM/TP.CM | Broadcast Announce Message / Connection Mode (J1939 transport protocol) |
| CTS/RTS | Clear To Send / Request To Send (J1939-TP) |
| Address Claiming | J1939 address assignment procedure |
| SID | Service ID |
| ECU | Electronic Control Unit |
| SPI | Service Provider Interface (here: CanKit-internal extension interfaces, not "Serial Peripheral Interface") |
| HIL | Hardware-in-the-Loop |
| MoSCoW | Must/Should/Could/Won't prioritisation |
| L0…L4 | Canonical layer nomenclature of this project, see section 2.1 |
1.4 References¶
- ISO 11898-1/-2: Controller Area Network (CAN), CAN-FD
- ISO 15765-2: Road vehicles – Diagnostic communication over CAN – Part 2: Transport protocol and network layer services (ISO-TP)
- ISO 15765-4: Diagnostic communication over CAN – Part 4: Requirements for emissions-related systems (UDS on CAN)
- ISO 14229-1: Unified Diagnostic Services (UDS) – Specification and requirements
- SAE J1939 / ISO 11783: Serial Control and Communications Vehicle Network (commercial vehicles/agricultural machinery)
- CiA 301: CANopen Application Layer and Communication Profile
- CiA 302: CANopen Additional Application Layer Functions (network management, boot-up)
- CiA DSP 304 V1.0 (2001-01-01): CANopen Framework for Safety-Relevant Communication (SRDO, GFC); V1.1 was absorbed into EN 50325-5:2010, the reconciliation is
docs/reviews/2026-11-04-en50325-5-conformance.md - CiA 319 v2.0.0 (Draft Specification Proposal, 2025-12-02): CANopen implementation and configuration specification for safety-related devices — the procedure of the configuration entity (clause 5: identity verification, configuration steps, storage) and the checksum rules for unimplemented optional parameters (§6.2), as well as the device side — §5.3 step 4 on the A5h write, the first download indication, the periodic recalculation, the start-up comparison per §5.4 (FR-CO-036, FR-CO-058, FR-CO-059; #308); clauses 7 and 8 (safety application layer of the profiles) are not implemented
docs/reviews/2026-07-14-deep-code-review.md– as-is review, source of the L2 architecture gapsdocs/reviews/2026-09-15-canopen-scope.md– scope of the CANopen device role against CiA 301; origin of the item numbers in 4.3.2docs/reviews/2026-09-26-canopen-master-tool-scope.md– scope of the CANopen master/tool role (#131): decisions of 26 Sept, answers to all eight decision questions; origin of FR-CO-029..034docs/reviews/2026-10-09-canopen-safety-scope.md– scope of CANopen Safety (CiA 304): decisions of 9 Oct, item table; origin of FR-CO-035..046- ISO/IEC/IEEE 29148:2018 – Requirements Engineering
- IREB CPRE syllabus (sentence templates, MoSCoW)
2. Overall Description¶
2.1 Product perspective – layer model¶
CanKit is structured in five layers. This nomenclature is binding and is used identically in the architecture document:
| Layer | Name | Content | As-is state |
|---|---|---|---|
| L0 | Adapter layer | 7 vendor adapters (SocketCAN, ZLG, PCAN, Kvaser, Vector, ControlCAN, Virtual) + fake-native layer for CI | existing, close to production |
| L1 | Raw-CAN core | ICanBus, CanFrame/CanFrameView, ITransceiver, ICanDevice, IPeriodicTx, CanRegistry, utilities (AsyncFramePipe, QueuedTxCanBus, SoftwarePeriodicTx, PreciseDelay, BitTimingSolver) |
existing, with known defects (see review) |
| L2 | Raw-CAN service layer | Multi-consumer demultiplexing, frame ownership contract, TX confirmation, addressing helpers, threading model per protocol instance, fault/timeout infrastructure | shipped as CanKit.Pro.RawCan, .Actor, .Addressing, .Reliability |
| L3 | Transport layer | ISO-TP (ISO 15765-2), J1939-TP (TP.BAM/TP.CM) | both shipped as CanKit.Pro.IsoTp and CanKit.Pro.J1939Tp |
| L4 | Application protocol layer | UDS (over ISO-TP), CANopen, J1939 (application) | all three shipped as CanKit.Pro.Uds, .CANopen, .J1939 |
L2 is the additional "raw-CAN" layer the client asked for: it encapsulates everything several protocol stacks need in common and that does not (or not correctly) exist in L1 today.
2.2 Product functions (overview)¶
- Hardened, multi-consumer-capable access to one physical CAN bus for several simultaneously active protocol instances (e.g. UDS client + CANopen master on the same channel).
- Complete, specification-compliant ISO-TP for diagnostic and generic PDU transfer.
- J1939 transport protocol for PGN-based commercial-vehicle communication.
- UDS client functionality for diagnostic tools/test automation.
- CANopen basic services (SDO, PDO, NMT, heartbeat, EMCY) for automation applications.
- J1939 application layer (PGN/SPN access, address claiming, request PGN).
2.3 User/stakeholder characteristics¶
See section 3.
2.4 Constraints (overview)¶
See section 6 (CON-xxx). Central: multi-targeting (netstandard2.0, net10.0), P/Invoke-based vendor SDKs (upstream in CanKit), platform-dependent timing behaviour.
2.5 Assumptions & dependencies¶
| ID | Assumption/dependency |
|---|---|
| A-1 | The four L2 architecture gaps derived in section 4.1 (frame ownership, demultiplexing, threading model, TX confirm) must be closed before L3/L4 stacks are built productively; L3/L4 requirements in this SRS presuppose a functioning L2. |
| A-2 | Obsolete. The ISO-TP prototype was not reused; L3 is rebuilt as CanKit.Pro.IsoTp, IsoTpScheduler no longer exists. The assumption stays as a record because A numbers are referenced elsewhere. |
| A-3 | J1939-TP is realised as a standalone transport following the same pattern as ISO-TP (own package, static factory, NFR-010). |
| A-4 | Vendor SDK licences (Peak PCANBasic, Kvaser CANlib, Vector XL driver) remain proprietary and are not part of this specification; only the integration points are considered. |
| A-5 | HIL test infrastructure with real hardware is needed for acceptance tests of the L3/L4 layers but is not the subject of this SRS (see section 7). |
| A-6 | netstandard2.0 remains a target TFM for the core library and L2/L3 in order to support .NET Framework consumers (legacy Windows systems with Kvaser/Vector/PCAN). |
3. Stakeholders & Actors¶
| Actor | Role |
|---|---|
| Application developer | Uses CanKit APIs (L1–L4) to build applications/tools on CAN buses. |
| Diagnostic tester (UDS application) | Uses the L4 UDS API to diagnose ECUs (read/write DID, routines, security). |
| ECU (external) | Communication partner on the bus; not a CanKit user, but a source of responses/error conditions against which CanKit must be robust. |
| CANopen master/node application | Uses the L4 CANopen API for NMT/PDO/SDO interaction. |
| J1939 application | Uses the L4 J1939 API for PGN/SPN access and address claiming. |
| Library maintainer | Responsible for architecture, code quality, SPI extensibility, release process. |
| CI/test automation | Runs unit, virtual-loopback and (where available) HIL tests automatically; consumer of the fake-native layer. |
4. Functional Requirements¶
4.1 L2 – Raw-CAN service layer (NEW)¶
The following requirements derive directly from the four architecture gaps identified in the review (review, overall assessment and §2.1–2.3), which must be closed before the protocol stacks (L3/L4) are built.
4.1.1 Frame ownership and lifetime contract¶
Background: Review §1.5 and §2.1 show that CanFrame.Dispose() ignores the ownMemory flag and that frames are shared without clear rules between sender, broadcast hub, event handlers and async pipes, leading to use-after-free/double-dispose.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-001 | The system MUST define a binding, documented frame ownership contract that for every frame hand-over point (TX caller→adapter, adapter RX→event/pipe/hub, hub→multi-consumer) unambiguously states who owns the memory and when Dispose() may be called. |
Must | Documentation review + architecture decision record; the contract is referenceable in docs/architecture/arc42-CanKit.Pro.md. |
Review §2.1, recommendation item 2 |
| FR-RAW-002 | The system MUST ensure that CanFrame.Dispose() releases the underlying memory only if the frame instance is the owner according to the ownership contract (correction of the defect described in Review §1.5). |
Must | Unit test: create a frame with ownMemory=false, call Dispose(), check that the underlying IMemoryOwner<byte> was not disposed. |
Review §1.5 |
| FR-RAW-003 | The system MUST give the protocol developer the possibility to consume received frames through a pure read view (CanFrameView) without taking on ownership or dispose responsibility. |
Must | Unit test: FrameObserved handler receives CanFrameView; calling Dispose() is not possible on the view type (compile-time check). |
Review §2.1; existing CanFrameView in ICanBus.FrameObserved |
| FR-RAW-004 | On multi-consumer broadcast (e.g. virtual bus hub) the system MUST deliver to every consumer an independently viable copy or a reference-counted instance released only after all recipients have consumed it, so that one consumer's dispose does not invalidate the data of other consumers/the sender. | Must | Integration test (virtual loopback): 2 consumers + sender subscribe to the same frame; one consumer disposes immediately, the others continue reading unchanged afterwards. | Review §2.1 (VirtualBusHub use-after-free) |
| FR-RAW-005 | The system SHOULD document and enforce for TX paths that the caller remains owner of the passed frame unless the adapter makes a copy, and that adapters make internal copies before using the frame asynchronously (scheduler queues, echo matching). | Should | Code review checklist per adapter/transport; regression test for ISO-TP scheduler echo matching (Review §2.1, item "Scheduler (ISO-TP)"). | Review §2.1 |
4.1.2 Multi-protocol demultiplexing (subscription/filtering)¶
Background: Today several protocol instances (e.g. ISO-TP + CANopen) cannot obtain independently filtered views of the same ICanBus without taking frames away from each other or blocking callbacks.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-010 | The system MUST give the protocol developer the possibility to register an independent, filtered frame subscription (ID range, mask or predicate) on a shared ICanBus without affecting other subscriptions. |
Must | Integration test: 2 subscriptions with disjoint ID filters on the same virtual bus; each receives exclusively the frames matching it. | Review, overall assessment ("multi-protocol demultiplexing") |
| FR-RAW-011 | The system MUST ensure that every subscription owns its own independent buffer/pipe, so that a slow or blocked subscription does not delay the reception of other subscriptions or of the base FrameObserved event. |
Must | Load test: one subscription processes artificially slowly, the second subscription remains unaffected in latency/throughput. | Derived from the "demultiplexing" architecture gap |
| FR-RAW-012 | The system MUST support orderly un-/re-registration of subscriptions (dispose pattern), so that terminated protocol instances (e.g. ISO-TP channel closed) leave no resources (threads, buffers, hub entries) behind. | Must | Unit test: create and dispose N subscriptions, afterwards resource counters (handles/threads) unchanged compared with the initial state. | Review §2.4 (Virtual: VirtualBusHub._hubs leak as negative example) |
| FR-RAW-013 | The system SHOULD provide a default demultiplex strategy for the common case "one 11/29-bit CAN-ID range per protocol instance" (fast path without generic predicate) to avoid overhead at high bus frequency. | Should | Performance test: throughput comparison ID-range filter vs. generic predicate at ≥ 1000 frames/s. | NFR performance, section 5.1 |
| FR-RAW-014 | The system MAY allow the protocol developer to reconfigure subscriptions at runtime (change filter criteria) without recreating the subscription. | Could | Unit test: change filter criterion at runtime, subsequent frames follow the new criterion. | — |
| FR-RAW-015 | The system MUST attach to every subscription element the echo marking reported by the bus and the receive timestamp captured by the adapter, and it MUST deliver echo frames only to subscriptions that explicitly requested them (default: no echoes). | Must | Unit test: bus reports a receive frame and an echo frame; the default subscription receives only the receive frame, includeEcho: true receives both with correct IsEcho; the timestamp reaches the subscriber unchanged. |
#23 (review finding Q5) |
| FR-RAW-016 | Built-in subscriptions MUST expose known local buffer overflow through an optional extension compatible with existing ISubscription implementations: an independent drop count and ordered frame sequence for conservatively locating capture gaps. Filter/echo exclusions, reads, cancellation and disposal do not count as overflow. Dispatch does not wait for readers; overflow cannot erase the loss metadata itself. Zero local drops do not prove loss-free adapter/driver capture. |
Must | Deterministic small-buffer tests: exact evictions, ordering/gaps, independent subscriptions, concurrent writers, exclusions, lifetime and counter boundaries. | #402 |
4.1.3 Threading/actor model per protocol instance¶
Background: Review §1.1 items 9/14 and §2.2/§2.3 show unsynchronised access to shared state (scheduler lists, _tx state, timer contexts) from several threads as well as a busy-loop scheduler without a defined concurrency model.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-020 | The system MUST provide for every protocol instance (e.g. an ISO-TP channel, a CANopen node) a defined, documented threading model that states on which thread/context RX processing, TX scheduling and timeout checking take place. | Must | Architecture review + concurrency test (stress test with ThreadSanitizer equivalent/Interlocked counters) without data-race reports. |
Review §1.1 items 9, 14; overall assessment "threading model" |
| FR-RAW-021 | The system MUST protect internal state of a protocol instance reachable from several threads (channel registers, timer queues, state machines) against concurrent read/write access (e.g. lock, concurrent collection or single-writer actor). | Must | Unit/stress test: parallel AddChannel/RemoveChannel and a running processing loop without InvalidOperationException on enumeration. |
Review §1.1 item 14 (Router._channels, IsoTpScheduler._channels as List without synchronisation) |
| FR-RAW-022 | The system MUST provide an event-driven (not busy-loop-based) scheduling mechanism for periodic/timed protocol tasks (e.g. STmin waits, timeout checks) that blocks when there is no work instead of consuming CPU. | Must | Performance test: CPU utilisation of an inactive channel over 60 s < 1 % (reference value, to be adjusted to the target platform). | Review §1.1 item 9 (100 % CPU busy loop of the IsoTpScheduler) |
| FR-RAW-023 | The system MUST pass background exceptions of a protocol instance consistently to the user via a defined channel (event/callback) instead of throwing them on the caller thread of the triggering adapter or losing them as unobserved task exceptions. | Must | Unit test: an artificially triggered background exception is observed via the protocol instance's BackgroundExceptionOccurred equivalent, the adapter's RX loop remains unaffected. |
Review §1.1 item 15 |
| FR-RAW-024 | The system SHOULD allow the protocol developer to configure the execution context (e.g. dedicated thread vs. thread pool vs. user SynchronizationContext) per protocol instance. |
Should | Configuration test: instance created with a dedicated thread, the callback is demonstrably executed on that thread. | Derived from the threading-model requirement |
4.1.4 TX confirmation abstraction (TX confirm)¶
Background: State machines in L3/L4 (e.g. ISO-TP send path, CANopen SDO) need a reliable statement of when a frame was actually sent on the bus – not just that the driver accepted it. ICanBus.Transmit returns only the number of accepted frames (ITransceiver.Transmit), no send confirmation; echo support is optional (CanFeature.Echo).
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-030 | The system MUST offer the protocol developer a uniform TX confirmation abstraction that works regardless of whether the underlying adapter supports hardware echo (CanFeature.Echo). |
Must | Integration test against two adapter configurations (with/without CanFeature.Echo), each with a successful TX confirm callback. |
Review, overall assessment "TX confirm abstraction"; CanFeature.Echo in CanEnums.cs |
| FR-RAW-031 | For adapters with echo support the system MUST use the actual send confirmation (echo frame) for the TX confirm and thereby correctly assign frames to the original send call by suitable criteria (e.g. ID + payload + sequence number/time window). | Must | Integration test (virtual loopback with echo enabled): N simultaneously sent, identical frames are correctly confirmed individually (no mis-matching). | Review §1.1 item 12 (QueuedDeadline.Enqueue crashes on identical frames) as negative example |
| FR-RAW-032 | For adapters without echo support the system MUST provide a documented approximation of the TX confirm (e.g. "accepted by the driver" as the best available confirmation) that communicates this difference explicitly to the caller (no silent pretence of a hardware confirmation). | Must | Unit test: the TX confirm result contains the confirmation kind (echo vs. driver acceptance); documentation check. | Derived from the "TX confirm" architecture gap |
| FR-RAW-033 | The system MUST report TX confirm failures (timeout, bus-off, rejection) to the caller as an observable result (e.g. failed future/task) instead of leaving the operation hanging indefinitely. A bus-off is reported as such when the adapter raises ICanBus.FaultOccurred while ICanBus.BusState reads BusOff; a fault in any other state leaves the send pending. No CanKit 0.5.6 adapter reports a bus-off that way (src/CanKit.Pro.RawCan/README.md, § TX-Confirm); there a send outstanding during a bus-off ends as a timeout (#348). The caller's cancellation token MUST be checked immediately before the driver call — on the echo path inside the send gate, i.e. after waiting behind another send's driver call —; a send cancelled by then ends with OperationCanceledException, and nothing reaches the driver (#294). |
Must | Unit test: the TX confirm timeout scenario yields an error result within the configured deadline; a token cancelled before the call does not reach the driver on the approximated path (TxConfirmTests.A_Token_Cancelled_Before_The_Call_Abandons_The_Approximated_Send), one cancelled while waiting at the send gate does not on the echo path (A_Token_Cancelled_While_The_Echo_Path_Waits_On_The_Gate_Abandons_The_Send); a fault while BusState == BusOff resolves an outstanding confirmation as BusOff at once (Outstanding_SendConfirmed_Resolves_As_BusOff_Immediately_On_Fault), one in any other state does not (A_Fault_While_The_Bus_Is_Not_Off_Leaves_The_Send_Pending). |
Review §1.1 items 8, 10 (hanging tasks in ISO-TP as negative example) |
| FR-RAW-034 | The system SHOULD allow the protocol developer to configure the TX confirm timeout per send operation. | Should | Configuration test: different timeout values lead to proportionally different failure times. | — |
4.1.5 Addressing/ID helpers¶
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-040 | The system MUST provide the protocol developer with helper functions for constructing, validating and decomposing 11-bit (standard) and 29-bit (extended) CAN IDs, including the PGN/priority/source-address split of the 29-bit ID needed for J1939. | Must | Unit tests: encode/decode round trip for representative standard, extended and J1939 PGN IDs including boundary values (0, 0x7FF, 0x1FFFFFFF). | Overall description, assignment "addressing/ID helpers" |
| FR-RAW-041 | The system SHOULD offer the protocol developer helper functions for detecting ID collisions/overlaps between registered subscriptions (e.g. for fault diagnosis when several protocol instances are misconfigured). | Should | Unit test: two overlapping filter ranges are detected and reported. | Derived |
4.1.6 Fault/timeout infrastructure¶
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-RAW-050 | The system MUST provide a reusable deadline/timeout primitive that L3/L4 protocols can use for time-bound state transitions (e.g. N_Bs, N_Cr, P2, SDO timeout) and whose expiry is reliably checked and reported. | Must | Unit test: a deadline with a short period demonstrably triggers the timeout callback; regression test against the review finding "deadlines are maintained but never checked". | Review §1.1 item 10 |
| FR-RAW-051 | The system MUST make bus fault states (BusState: ErrWarning, ErrPassive, BusOff) available to the L3/L4 protocol instances such that they can abort or pause active transfers in a controlled way. |
Must | Integration test: a simulated BusOff state leads, during an active L3 send, to a defined abort instead of hanging. |
Derived from ICanBus.BusState, ErrorCounters() |
| FR-RAW-052 | The system SHOULD handle reserved/invalid protocol values in incoming frames (e.g. reserved ISO-TP STmin values) robustly per specification instead of aborting with an exception. | Should | Unit test analogous to Review §1.1 item 6 (reserved STmin values 0x80–0xF0/0xFA–0xFF are interpreted as 127 ms instead of an exception). | Review §1.1 item 6 |
4.2 L3 – Transport layer¶
4.2.1 ISO-TP (ISO 15765-2)¶
As-is state (2026-09-15): shipped as CanKit.Pro.IsoTp. The following requirements describe the target behaviour; which of them are backed by a test is stated by eng/verify-requirements-traceability.py.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-TP-001 | The system MUST give the protocol developer the possibility to send a PDU of any permissible length (single frame to multi-frame, classic CAN and CAN-FD) over an ISO-TP channel (SendAsync) and to learn of the completion of the transfer asynchronously. |
Must | Integration test (virtual loopback): SF and multi-frame round trip for payloads of 1–4095 bytes. | ISO 15765-2; Review §1.1 (defect catalogue as starting point) |
| FR-TP-002 | The system MUST correctly reassemble incoming ISO-TP PDUs from single, first and consecutive frames and provide them via DatagramReceived/GetFramesAsync. |
Must | Integration test: multi-frame reception with correct reassembly including sequence-number checking. | ISO 15765-2 §6; Review §1.1 item 7 (SN error as negative example) |
| FR-TP-003 | The system MUST correctly produce classic-CAN and CAN-FD frames according to the mode configured in the channel options (correction of the inversion canfd ? Classic : Fd described in Review §1.1 item 1). |
Must | Unit test: IsoTpOptions with CanFd=true produces exclusively CanFrameType.CanFd frames for SF/FF/CF/FC. |
Review §1.1 item 1 |
| FR-TP-004 | The system MUST produce flow-control frames with the correct PCI type (FC, not FF) and without overwriting BS/STmin by padding. |
Must | Unit test: the produced FC frame has PCI nibble 0x3, the BS and STmin bytes match the values passed in. |
Review §1.1 items 2, 3 |
| FR-TP-005 | The system MUST correctly encode and decode first-frame length fields > 255 bytes (correction of the high-nibble binding error). | Must | Unit test: FF length round trip for the values 256, 512, 4095. | Review §1.1 item 4 |
| FR-TP-006 | The system MUST encode STmin values per ISO 15765-2, including the common values 0 ms and 1 ms, without throwing. | Must | Unit test: EncodeStmin(0), EncodeStmin(1000µs) yield a valid encoding without exception. |
Review §1.1 item 5 |
| FR-TP-007 | The system MUST treat reserved STmin values when decoding as 0x7F (127 ms) per ISO 15765-2 and must not raise an unhandled IndexOutOfRangeException on faulty/unexpected PCI lengths. |
Must | Unit test: reserved values 0x80–0xF0, 0xFA–0xFF yield 127 ms; a fuzz test with truncated PCI bytes raises no unhandled exception. | Review §1.1 item 6 |
| FR-TP-008 | The system MUST send consecutive frames with the correct payload offset (no data loss at the FF/CF transition) and a correctly starting sequence number (SN=1 on the first CF). | Must | Integration test: a multi-frame message is transferred byte-for-byte without loss over virtual loopback and compared. | Review §1.1 item 7 |
| FR-TP-009 | The system MUST start multi-frame sends reliably (the first frame is sent and the channel waits correctly for flow control) without the send hanging unobserved. | Must | Integration test: SendAsync for a payload > SF limit completes successfully within the configured time. |
Review §1.1 item 8 |
| FR-TP-010 | The system MUST actively check all ISO-TP timing supervisions (at least N_As, N_Bs, N_Cr) and on exceedance complete the send/receive operation with an error observable to the caller. | Must | Integration test: an artificially delayed/absent peer triggers an error within the configured deadline instead of a hanging task. | Review §1.1 item 10 |
| FR-TP-011 | The system MUST accept flow-control "Wait" (FS=WT) only up to a configurable upper limit (WFTmax) and abort the operation afterwards. | Must | Unit test: the peer sends WT more often than WFTmax → abort with error. | ISO 15765-2 §6.3 (WFTmax); Review §1.1 item 10 |
| FR-TP-012 | On overflow flow control (FS=OVFLW) the system MUST complete the running send with an observable error result instead of only marking it internally as "Failed". | Must | Unit test: an FS=OVFLW response leads to a failed SendAsync task within a defined time. |
Review §1.1 item 10 |
| FR-TP-013 | On netstandard2.0 target platforms the system MUST deliver the same correct TX queue behaviour as on net10.0 (origin: correction of the fork prototype's inverted TryPeek polyfill logic; the polyfill no longer exists in this tree). |
Must | Unit test explicitly against the netstandard2.0 build: the TX queue yields no element without exception when empty and the expected element when filled. |
Review §1.1 item 11 |
| FR-TP-014 | The system MUST support several simultaneously in-flight frames with identical content (e.g. padded consecutive frames) in deadline/confirm tracking without crashing. | Must | Unit test: two identical frames in progress at the same time, no exception, correct separate timing supervision. | Review §1.1 item 12; see also FR-RAW-031 |
| FR-TP-015 | The system MUST obtain the ISO-TP-internal frame buffer via the ownership contract defined in FR-RAW-002 (no direct ArrayPool.Rent without return, no frames > 8 bytes for classic CAN). |
Must | Unit test: SF generation for classic CAN produces a valid ≤8-byte frame without ArgumentOutOfRangeException; memory-leak test over N sends. |
Review §1.1 item 13 |
| FR-TP-016 | The system MUST implement the threading model defined in FR-RAW-020..023 for the ISO-TP scheduler: event-driven instead of busy loop, synchronised access to channel registers, consistent error forwarding. | Must | As FR-RAW-022/023, applied to IsoTpScheduler. |
Review §1.1 items 9, 14, 15 |
| FR-TP-017 | The system MUST realise the TX confirmation for ISO-TP sends via the abstraction defined in FR-RAW-030..033 (no direct SetResult/SetException in race situations with cancellation). |
Must | Unit test: simultaneous cancellation and TX failure lead to exactly one consistent result, no InvalidOperationException. |
Review §1.1 item 14 |
| FR-TP-018 | The system SHOULD allow the protocol developer to operate several ISO-TP channels with different address pairs simultaneously over the same physical bus (use of L2 demultiplexing per FR-RAW-010ff.). | Should | Integration test: two ISO-TP channels with different CAN IDs on the same virtual bus work independently and correctly. | Derived from the L2 requirements |
| FR-TP-019 | The system MAY support functional addressing (1:n requests per ISO 15765-2/ISO 14229). | Could | Integration test: a functional request is answered by several simulated ECUs. | ISO 15765-2 |
| FR-TP-020 | The system MUST keep the ISO-TP package's dependency list free of unnecessary vendor-SDK references (e.g. Peak.PCANBasic.NET). |
Must | Build/package check: CanKit.Pro.IsoTp.csproj references no adapter-specific vendor packages. |
Review §1.1 item 16 |
4.2.2 J1939-TP (TP.BAM/TP.CM)¶
As-is state (2026-09-15): shipped as CanKit.Pro.J1939Tp.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-TP-030 | The system MUST give the protocol developer the possibility to send PDUs of up to 1785 bytes by Broadcast Announce Message (TP.BAM) to all network participants. | Must | Integration test (virtual loopback): BAM transfer, several receivers reassemble identically. | SAE J1939-21 |
| FR-TP-031 | The system MUST give the protocol developer the possibility to send PDUs by connection-oriented transport protocol (TP.CM: RTS/CTS/EndOfMsgAck) to a specific destination address, including segment-size negotiation via CTS. | Must | Integration test: RTS/CTS handshake, correct segment transfer, EndOfMsgAck. | SAE J1939-21 |
| FR-TP-032 | The system MUST check the peer's timing supervisions per J1939-21 §5.10.2.4 (T1–T4) and trigger connection aborts on exceedance. Tr (200 ms) is a node's own response deadline, not a supervision of the peer, and is not kept as a timing supervision; Th (500 ms) is the hold time between CTS(0) messages. | Must | Integration test: an absent peer triggers an abort within the specification deadlines. | SAE J1939-21 |
| FR-TP-033 | On the receive side the system MUST correctly recognise both TP.BAM and TP.CM messages by PGN 0xEC00/0xEB00 and assign them to the associated reassembly state machine. | Must | Unit test: a mixed frame stream of BAM and CM sessions is correctly reassembled separately. | SAE J1939-21 |
| FR-TP-034 | The system MUST implement J1939-TP, analogous to ISO-TP, via the L2 threading model (FR-RAW-020ff.) and the ownership contract (FR-RAW-001f.). | Must | As FR-TP-016/017, applied to J1939-TP. | Consistency requirement |
| FR-TP-035 | The system SHOULD support several simultaneous TP.CM sessions with different peer addresses. | Should | Integration test: two parallel TP.CM sessions to different destination addresses without mutual interference. | SAE J1939-21 |
4.3 L4 – Application protocol layer¶
4.3.1 UDS (ISO 14229, over ISO-TP)¶
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-UDS-001 | The system MUST give the diagnostic tester the possibility to change a diagnostic session (DiagnosticSessionControl, service 0x10) and to track the active session. |
Must | Integration test against a simulated ECU: session change Default→Extended, confirmation evaluated. | ISO 14229-1 §9.2 |
| FR-UDS-002 | The system MUST give the diagnostic tester the possibility to read a data identifier (ReadDataByIdentifier, service 0x22). |
Must | Integration test: the DID read yields the expected payload. | ISO 14229-1 §9.3 |
| FR-UDS-003 | The system MUST give the diagnostic tester the possibility to write a data identifier (WriteDataByIdentifier, service 0x2E). |
Must | Integration test: write + positive response. | ISO 14229-1 §9.6 |
| FR-UDS-004 | The system MUST give the diagnostic tester the possibility to control a routine (RoutineControl – start/stop/requestResults, service 0x31). |
Must | Integration test: all three sub-functions against a simulated ECU. | ISO 14229-1 §9.10 |
| FR-UDS-005 | The system MUST give the diagnostic tester the possibility to trigger an ECU reset (service 0x11) and to process the reset confirmation. | Must | Integration test: reset request + positive response before the simulated ECU drops the connection. | ISO 14229-1 §9.1 |
| FR-UDS-006 | The system MUST give the diagnostic tester the possibility to perform security access (service 0x27) (seed request, key transfer), with the key computation lying outside CanKit (callback/delegate). | Must | Integration test: seed/key exchange with an injected test algorithm. | ISO 14229-1 §9.4 |
| FR-UDS-007 | The system MUST give the diagnostic tester the possibility to send Tester Present (service 0x3E) periodically to keep a session alive. | Must | Integration test: periodic 0x3E transmission over a configurable interval, the session stays active. | ISO 14229-1 §9.12 |
| FR-UDS-008 | The system MUST supervise P2 and P2* server timing (default and extended response time) and report a timeout to the caller if the ECU does not answer within the deadline. | Must | Integration test: a delayed simulated ECU response just above P2 triggers a timeout, just below does not. | ISO 14229-1 §7.3 (P2/P2*) |
| FR-UDS-009 | The system MUST handle Response Pending (NRC 0x78) correctly by restarting the P2* deadline and waiting for the final response instead of terminating prematurely. | Must | Integration test: the simulated ECU sends 0x78 1..n times, followed by the final response; the caller receives the final response without timeout. | ISO 14229-1 §7.3.3 |
| FR-UDS-010 | The system MUST pass negative response codes (NRC) to the caller in structured form (service, requested service, NRC value) instead of reporting them as a generic exception without context. | Must | Unit test: a simulated NRC response (e.g. 0x31 "Request Out Of Range") is mapped with correct context in the error result. | ISO 14229-1 §8.7 |
| FR-UDS-011 | The system SHOULD support multi-DID reads (service 0x22 with several DIDs in one request). | Should | Integration test: request with 3 DIDs, correct assignment of the response data. | ISO 14229-1 §9.3 |
| FR-UDS-012 | The system MAY support upload/download services (RequestDownload/TransferData/RequestTransferExit, services 0x34/0x36/0x37) for flash/data transfer. |
Could | Integration test: complete download cycle against a simulated ECU. | ISO 14229-1 §14 |
4.3.2 CANopen (CiA 301)¶
As-is state (2026-10-09): shipped as CanKit.Pro.CANopen. The scope of the device role was decided on 2026-09-16 (docs/reviews/2026-09-15-canopen-scope.md) and implemented as FR-CO-013..024; the EDS/DCF path from it (items 1, 2, 13 and the EDS half of 26) followed with EdsDcfNet 1.13.0 as FR-CO-025..028 (#132). The master/tool role was decided on 2026-09-26 (docs/reviews/2026-09-26-canopen-master-tool-scope.md, #131); its decision questions were answered by 28 Sept. It stands as FR-CO-029..034: the peer-SDO gate (#174), observing foreign PDOs (#163), flying master and boot-up manager per CiA 302-2 version 4.1.0 (#164) and node discovery. The device requirements FR-CO-013..028 remain technically correct; the demotion of their attention (decision 5) is moot because none of their items is open (question 7). The item numbers in the Source column refer to the table "Die Posten" in docs/reviews/2026-09-15-canopen-scope.md. "Architecture decision" stands where CiA 301 does not demand the behaviour — transmission types and inhibit time are architecture, not norm; where the norm contributes only the rejection, the semantics or an abort code, the cell says so. CANopen Safety per CiA DSP 304 V1.0 was decided on 2026-10-09 (docs/reviews/2026-10-09-canopen-safety-scope.md) and stands as FR-CO-035..046; their item, decision and decision-question numbers in the Source column refer to that record, their paragraphs to DSP 304 V1.0. The reconciliation against EN 50325-5:2010 is #289. The tool side was extended on 2026-10-17 by the configuration entity per CiA 319 v2.0.0 and stands as FR-CO-051. The device side of CiA 319 — §5.3 step 4, the first download indication, the periodic recalculation, the start-up comparison per §5.4 — stands since 2026-10-20 as FR-CO-058 and FR-CO-059 and in FR-CO-036 (#308).
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-CO-001 | The system MUST give the application developer the possibility to define a local object dictionary (OD) with index/sub-index entries and to read/write it at runtime. | Must | Unit test: create, read, write an OD entry, type check. | CiA 301 §7.4 |
| FR-CO-002 | The system MUST support SDO expedited transfer (≤4 bytes) for read/write access to remote OD entries. | Must | Integration test against a simulated node: SDO read/write expedited. | CiA 301 §7.2.4 |
| FR-CO-003 | The system MUST support SDO segmented transfer for OD values > 4 bytes (toggle-bit handling per specification). | Must | Integration test: segmented transfer with several segment changes and correct toggle bit. | CiA 301 §7.2.4.3 |
| FR-CO-004 | The system SHOULD support SDO block transfer for large OD values. | Should | Integration test: block transfer including block-size negotiation. | CiA 301 §7.2.4.3.8–7.2.4.3.16 (block transfer) |
| FR-CO-005 | The system MUST support PDO mapping (static/dynamic) for TPDO (transmit) and RPDO (receive), including the assignment of OD entries to PDO payload offsets. | Must | Unit test: the PDO mapping configuration produces the correct frame payload from mapped OD values and vice versa. | CiA 301 §7.3 |
| FR-CO-006 | The system MUST support event- and time-driven TPDO transmission (change of state, timer) as well as SYNC-triggered PDO transmission/reception. | Must | Integration test: a TPDO is sent correctly on OD value change and on SYNC reception. | CiA 301 §7.3, §7.5 |
| FR-CO-007 | The system MUST support NMT state changes (Pre-Operational, Operational, Stopped, Reset) both as NMT master (sending commands) and as NMT node (receiving commands and changing state). | Must | Integration test: all four NMT commands against a simulated node/master with correct state transition. | CiA 301 §7.2.8.3 |
| FR-CO-008 | The system MUST support heartbeat production (periodic transmission of its own state) and heartbeat consumption (timeout detection when a monitored node's heartbeat is missing). | Must | Integration test: a missing heartbeat triggers a timeout event within the configured deadline. | CiA 301 §7.2.8.3.4 |
| FR-CO-009 | The system MAY support node guarding (legacy alternative to heartbeat). | Could | Integration test: guarding request/response cycle. | CiA 301 §7.2.8.3.3 |
| FR-CO-010 | The system MUST be able to send SYNC objects (as SYNC producer) and receive them (as SYNC consumer). | Must | Integration test: a SYNC frame triggers the configured SYNC-triggered PDOs. | CiA 301 §7.2.6 |
| FR-CO-011 | The system MUST send emergency objects (EMCY) when internal error conditions occur and pass received EMCY objects to the application developer in structured form (error code, error register, manufacturer data). For every EMCY the node raises itself (8210h RPDO/MPDO length, 8230h MPDO destination, 8240h SYNC length, 8250h RPDO timeout, 8210h SRDO length) it MUST keep a communication error: from the EMCY that reports it (once per error event) until its cause ends (a frame that fits, a SYNC of the expected length, a destination written, a reception after the timeout, a reconfiguration of the PDO or SRDO). While one is active, 1001h carries bit 4 and bit 0. The bits from SendEmcyAsync, and a value of 1001h that the application (or a device description) wrote through the object dictionary, belong to the application; they are ORed in and never cleared by the node. When the last one ends, the node MUST send EMCY 0000h with the remaining register (Figure 37, transition 4 or 2). A held EMCY (Stopped) carries the register of its transmission. An NMT reset ends the communication errors without an EMCY and leaves the application's bits. With the EMCY switched off (1014h bit 31) nothing is raised, and an end still clears the bit. |
Must | Unit test: an EMCY frame is correctly encoded/decoded including all fields. Virtual-loopback integration test (CanOpenErrorRegisterTests, #379): set and reset per source, overlap with the application's bits, reset, EMCY switched off, held EMCY; SrdoEngineConsumerTests.A_Short_Pair_Raises_Emcy_8210h_Once_And_Is_Not_Processed for the end of an SRDO run. |
CiA 301 §7.2.7.1 (Figure 37), §7.5.2.2 Table 54 |
| FR-CO-012 | The system SHOULD use L2 multi-consumer demultiplexing (FR-RAW-010ff.) to process a node's NMT, SDO, PDO and SYNC traffic in parallel and independently. | Should | Integration test: simultaneous SDO and PDO traffic on the same node without mutual delay. | Derived from the L2 requirements |
| FR-CO-013 | When opening a node the system MUST create in the OD the communication-profile objects 1000h, 1001h, 1018h (sub0 = 01h, vendor-ID 0 = "no vendor-ID assigned"), 1005h, 1006h, 100Ch, 100Dh, 1010h, 1011h, 1014h, 1016h, 1017h, 1200h and the PDO records 1400h–1403h, 1600h–1603h, 1800h–1803h and 1A00h–1A03h with their CiA 301 default values (PDOs "not valid", bit 31 set). Without the option WritableCommunicationParameters the PDO records MUST be ro on the bus (download → 0601 0002h); 1200h is constant; the reserved sub-index 04h of the PDO communication records MUST be absent, so that an access yields 0609 0011h. |
Must | Virtual-loopback integration test (CanOpenCommunicationProfileTests): an SDO upload of every object yields the default value; a download to 1800h:02 without the option → 0601 0002h, with the option accepted; access to 1800h:04 → 0609 0011h. |
CiA 301 §7.5.2.1, §7.5.2.2, §7.5.2.21 and object overview ro M (item 3); footnote * of the object overview (items 4, 16); §7.5.2.5, §7.5.2.6, §7.5.2.17 (item 19); §7.5.2.11, §7.5.2.12 (item 24); §7.5.2.37 on sub-index 04h (item 18); architecture decision (scope document 2026-09-15, items 4b, 21) for 1200h and 1016h/1017h |
| FR-CO-014 | The system MUST keep the OD as the single source of the communication configuration, in both directions and on both write paths: a value the OD accepts for an object managed by the node — by SDO download as by local write access through ObjectDictionary — MUST take effect in the associated service (SYNC, EMCY, heartbeat, life guarding, PDO); StartHeartbeatProducer, AddHeartbeatConsumer, StartSyncProducer, SendEmcyAsync (1001h), ConfigureTpdo and ConfigureRpdo MUST be executed as writes to these objects; a value that cannot be realised MUST be rejected before storage (SDO: abort code, local: ArgumentException with the same code in its text); a managed object must not be redeclared via Add* (InvalidOperationException). |
Must | Virtual-loopback integration test (CanOpenCommunicationProfileTests): 1017h set via SDO starts the heartbeat; StartSyncProducer writes 1006h and bit 30 of 1005h; SendEmcyAsync mirrors 1001h; AddU32(0x1006, …) throws; a local write with an invalid value throws with the abort code in its text. |
Architecture decision (scope document 2026-09-15, items 14, 20, 22, 23) |
| FR-CO-015 | For TPDOs the system MUST implement the transmission types from 1800h:02: 00h synchronous-acyclic (send after the next SYNC if since the previous SYNC a state change of a mapped object or TriggerTpdoAsync occurred), 01h–F0h synchronous-cyclic (on every n-th SYNC), FCh RTR-only synchronous (sampling on every SYNC, the buffered value answers an RTR), FDh RTR-only event-driven (sampling and sending on RTR), FEh/FFh event-driven (state change, TriggerTpdoAsync, event timer). TpdoTransmission MUST map onto these bytes (EventDriven/EventTimer → FEh, Synchronous → 01h); 02h–F0h is written as a raw byte into 1800h:02. A download of a reserved value (TPDO F1h–FBh, RPDO F1h–FDh) MUST be rejected with 0609 0030h. |
Must | Virtual-loopback integration test (CanOpenPdoEngineTests): per transmission type the expected frame sequence over SYNC, RTR and trigger (03h sends on the third SYNC; FCh answers only after the first SYNC); download F1h → 0609 0030h. |
Architecture decision (scope document 2026-09-15, items 5, 6a, 6b, 11); the rejection of reserved values is normative: CiA 301 §7.5.2.37 Table 72, §7.5.2.35 Table 68 (item 17) |
| FR-CO-016 | The system MUST honour the inhibit time 1800h:03 (multiples of 100 µs) as the minimum spacing of two transmissions of an event-driven TPDO (FEh/FFh) — an event within the inhibit period leads to exactly one transmission after it expires, further events until then coalesce with it — and the event timer 1800h:05 (multiples of 1 ms) as the maximum spacing: it restarts with every transmission and sends only in the Operational state. Neither applies to synchronous and RTR-only types; 0 switches off; 1800h:03 may only be changed while the PDO is invalid (bit 31 = 1), otherwise 0609 0030h. | Must | Unit test with virtual clock and virtual-loopback integration test (CanOpenPdoEngineTests): two state changes within the inhibit time yield two frames spaced ≥ inhibit time; without an event a frame follows after 1800h:05; a download to 1800h:03 with a valid PDO → 0609 0030h. |
Architecture decision (scope document 2026-09-15, item 7); semantics and units: CiA 301 §7.5.2.37 (sub-indices 03h, 05h) |
| FR-CO-017 | The system MUST treat an RPDO with 1400h:02 = 00h–F0h synchronously: received data are held and written into the OD and reported via RpdoReceived only with the next SYNC (with several receptions before the SYNC, the most recent); FEh/FFh takes effect immediately. ConfigureRpdo MUST take the type as RpdoTransmission. |
Must | Virtual-loopback integration test (CanOpenPdoEngineTests): an RPDO frame before the SYNC leaves the OD unchanged, the SYNC writes it and raises RpdoReceived. |
Architecture decision (scope document 2026-09-15, item 12); semantics: CiA 301 §7.2.2.2, §7.5.2.35 Table 68 |
| FR-CO-018 | The system MUST evaluate the COB-ID entries 1400h:01, 1800h:01, 1005h and 1014h as 32-bit control words: bit 31 valid (1400h:01, 1800h:01: set = the PDO is removed from the runtime; 1014h: set = EMCY sending is refused), bit 30 RTR in 1800h:01 (set = no RTR is answered), bit 30 gen. in 1005h (SYNC producer on/off), bit 30 of 1014h reserved (set → 0609 0030h), bit 29 frame selects the 29-bit ID for PDOs; for 1005h and 1014h it continues to be rejected with 0609 0030h. Bits 0–29 must not be changed while the object is valid (1005h: while bit 30 = 1), otherwise 0609 0030h; a CAN-ID from Table 40 (restricted) MUST be rejected with 0609 0030h, locally with ArgumentException. |
Must | Virtual-loopback integration test (CanOpenCommunicationProfileTests): bit 31 set on 1800h:01 stops the TPDO, cleared starts it; a download with CAN-ID 000h/07Fh/581h/701h and with an ID change on a valid PDO → 0609 0030h. |
CiA 301 §7.5.2.5 Table 55, §7.5.2.17 Table 59, §7.5.2.35 Table 66, §7.5.2.37 Table 70, §7.3.5 Table 40 (items 25, 27; #41) |
| FR-CO-019 | On NMT Reset Node and Reset Communication the system MUST reset the managed communication-profile objects (except 1001h, 1010h, 1011h, 1200h) to their power-on values — the values last saved via 1010h:01 "save" or StoreParameters(), otherwise the defaults at opening —, abort running SDO server sessions with 0800 0022h, send the boot-up and enter Pre-Operational; both commands reset the same set, because without a device description no source for application objects exists (the application resets those itself via ApplicationReset, which runs synchronously before the boot-up). 1011h:01 "load" or RestoreDefaultParameters() MUST cause only the next reset to restore the defaults; a wrong signature on 1010h:01 or 1011h:01 MUST be rejected with 0800 0020h. Storage is volatile (process lifetime). |
Must | Virtual-loopback integration test (CanOpenCommunicationProfileTests): ConfigureTpdo → Reset Communication → 1800h:01 invalid again; after StoreParameters() the configuration stays; after RestoreDefaultParameters() back to the default only with the reset; wrong signature → 0800 0020h. |
CiA 301 §7.3.2.2.1, §7.5.2.13, §7.5.2.14 (item 15) |
| FR-CO-020 | The system MUST check every mapping entry — via SDO in 1600h/1A00h as via ConfigureTpdo/ConfigureRpdo — before storing it: the target exists (otherwise 0602 0000h); it is PDO-mappable (OdEntry.PdoMappable), accessible in the PDO direction (TPDO readable, RPDO writable) and has the mapped width (otherwise 0604 0041h); the bit length lies within 1..64 (otherwise 0607 0010h); the sum does not exceed 8 bytes (otherwise 0604 0042h); entries are changeable only with sub0 = 0 (otherwise 0601 0000h); sub0 is UNSIGNED8 with range 0..64 and the MPDO values FEh/FFh (41h–FDh → 0609 0030h); the position of the payload follows the sub-index order; a dummy entry 0001h–0007h (sub-index 0) occupies the width of its data type without OD access. With ConfigureTpdo/ConfigureRpdo a rejection MUST be reported as ArgumentException and the PDO MUST stay invalid. PdoMapping.Add(PdoMappingEntry) and the constructor from an enumeration MUST reject an entry with a width the PdoMappingEntry constructor rejects — default(PdoMappingEntry), a struct with 0 bits — with ArgumentOutOfRangeException and leave the mapping unchanged, so that a configured PDO is not destroyed by such an attempt (#292). |
Must | Unit test and virtual-loopback integration test (CanOpenPdoEngineTests): per rule one rejected download with the named code; entries written out of order sit at their sub-index position; a dummy entry shifts the following bytes; ConfigureTpdo onto a missing object throws; an entry of 0, 12 or 72 bits is rejected by Add and by the constructor (Mapping_Add_Refuses_An_Entry_Of_A_Width_The_Constructor_Would_Refuse), and a configured TPDO survives the attempt (A_Configured_Tpdo_Survives_An_Attempt_With_A_Zero_Width_Entry). |
CiA 301 §7.5.2.36, §7.5.2.38 (items 26, 28; #40) |
| FR-CO-021 | The system MUST implement producer-side life guarding from 100Ch (guard time, ms) and 100Dh (life time factor): guarding starts with the first received guarding RTR; if the next RTR stays away longer than guard time × life time factor, LifeGuardingEvent MUST be reported with Occurred (once per failure), a later RTR reports Resolved; 0 in either object switches off. With an active heartbeat producer (1017h ≠ 0) the node MUST leave guarding RTRs unanswered and keep no life guarding. |
Must | Virtual-loopback integration test (CanOpenLifeGuardingAndNmtStateTests): consumer polls and stops → Occurred; polls again → Resolved; with 1017h ≠ 0 no guarding answer. |
CiA 301 §7.2.8.2.2, §7.2.8.2.2.2, §7.2.8.3.2.1, §7.2.8.3.2.2, §7.5.2.11, §7.5.2.12 (item 24; #43) |
| FR-CO-022 | The system MUST honour the assignment of NMT state and communication objects (Table 37): PDOs are sent and processed only in Operational; in Stopped, SDO requests are not served and running server sessions are aborted with 0800 0022h, SYNC is neither sent nor processed and a SendEmcyAsync is held back (the most recent EMCY goes out on leaving Stopped); heartbeat and guarding remain active in every state. The heartbeat on a state change may be sent only with an active producer (1017h ≠ 0); the guarding toggle MUST be reset to 0 on Reset Communication (and Reset Node). |
Must | Virtual-loopback integration test (CanOpenLifeGuardingAndNmtStateTests): an SDO request in Stopped stays unanswered, a running session receives 0800 0022h; an EMCY in Stopped reaches the bus only after Start; Stop without producer sends no heartbeat; toggle is 0 again after reset. |
CiA 301 §7.3.2.2.4, §7.3.2.2.5 Table 37, §7.2.8.3.2.1, §7.2.8.3.2.2 (#59, #43) |
| FR-CO-023 | The system MUST derive heartbeat consumers and producer from 1016h and 1017h: every sub-index of 1016h with node-ID 1..127 and time ≠ 0 (ms) is a consumer whose timeout is armed immediately; AddHeartbeatConsumer occupies the sub-index that already monitors this producer, otherwise a free one, otherwise 1016h grows by one sub-index (sub0 increases, at most 7Fh); a second sub-index with time ≠ 0 for the same node-ID MUST be rejected with 0604 0043h; 1017h (ms) starts or stops (0) the producer. |
Must | Virtual-loopback integration test (CanOpenCommunicationProfileTests): 1016h:01 set via SDO → HeartbeatTimeout without producer; a second sub-index with the same node-ID → 0604 0043h; 1017h = 0 stops the producer; AddHeartbeatConsumer for a second producer increases sub0. |
Architecture decision (scope document 2026-09-15, item 21); semantics and abort code: CiA 301 §7.5.2.19, §7.5.2.20 |
| FR-CO-024 | The system MUST leave an RPDO whose frame carries fewer data bytes than mapped unprocessed and — with a valid 1014h — send an EMCY with error code 8210h; if the frame carries more data bytes than mapped, the first bytes up to the mapped length MUST be used. | Must | Virtual-loopback integration test (CanOpenPdoEngineTests): a 1-byte frame on a 2-byte RPDO leaves the OD unchanged and produces EMCY 8210h; a 4-byte frame writes the first 2 bytes. |
CiA 301 §7.5.2.36 |
| FR-CO-025 | The system MUST be able to open a node from a device description per CiA 306 (EDS or DCF, read with EdsDcfNet): the file's application objects are created in the OD with data type, access type, PDOMapping attribute and value; the managed communication objects (1000h, 1001h, 1005h, 1006h, 100Ch, 100Dh, 1014h, 1016h, 1017h, 1018h) take over access type and value from the file via the same validated write path as an SDO download; the PDO records are created in the order of §7.5.2.38 (communication parameters with the PDO destroyed, then mapping, then "create PDO"), and PDOs the file does not declare do not exist (upload → 0602 0000h). $NODEID+… expressions are evaluated against the node's node-ID; for a DCF ParameterValue takes precedence over DefaultValue, and its NodeID is the node's if the call names none. The runtime follows the loaded OD like any other (FR-CO-014). |
Must | Virtual-loopback integration test (CanOpenDeviceDescriptionTests): device.eds yields type, access, mappability and value of 1000h, 1018h, 2000h, 2002h as well as 1005h, 1006h, 1014h, 1016h, 1017h, 1400h, 1600h, 1800h and 1A00h as described, 1401h/1801h/1A01h are absent (0602 0000h); heartbeat and SYNC producer run, TPDO1 sends 2000h, RPDO1 writes 2001h; device.dcf yields node-ID 5 and the ParameterValue. |
CiA 306 (EDS/DCF); CiA 301 §7.5.2.38 (order), §7.3.3 ("PDO does not exist"); architecture decision (scope document 2026-09-15, item 1); #132 |
| FR-CO-026 | The system MUST degrade every entry of a device description that it cannot implement as described, correct the degradation in the OD and report it in ICanOpenNode.DeviceDescription (DeviceDescriptionReport) per entry with index, sub-index, outcome, reason, described value and — where an SDO rule decided — the abort code; nothing is silently passed over. Outcomes: Omitted (not creatable: data type without equivalent, fifth PDO, unparsable value, unimplemented sub-index of a fixed record), Corrected (the value was rejected by the same rule as an SDO download, the CiA 301 default remains), PdoDisabled (the PDO stays destroyed because communication record or mapping is not applicable), NotImplemented (the entry exists as a datum without behaviour, e.g. 1012h or a deviating 1200h), SuppliedDefault (a missing mandatory object 1000h/1001h/1018h keeps the placeholder). IsExact is true exactly when there is no finding. |
Must | Unit test (CanOpenDeviceDescriptionTests): quirky.eds produces per case the finding with the expected outcome and abort code — 1018h missing (SuppliedDefault), 1800h:02 = F5h (Corrected, 0609 0030h, FEh remains), 1801h:01 with bit 29 (PdoDisabled), 1A02h:01 onto a non-existent object (PdoDisabled, 0602 0000h), 1804h/1A04h (Omitted), 1012h (NotImplemented), INTEGER24 (Omitted), duplicate node-ID in 1016h (Corrected, 0604 0043h), deviating 1200h:01 (NotImplemented), 1010h:02 (Omitted) — and the OD carries the corrected value in each case. |
Architecture decision (scope document 2026-09-15, item 2); abort codes per CiA 301 §7.2.4.3.17; #132 |
| FR-CO-027 | The system MUST enforce on the bus the access rights a device description states for the PDO records 1400h–1403h, 1600h–1603h, 1800h–1803h and 1A00h–1A03h — a record described as rw is writable via SDO even without the option WritableCommunicationParameters, an object described as ro stays ro (0601 0002h) — and take over the file's PDOMapping attribute as the object's mappability, so that an object with PDOMapping=0 is rejected with 0604 0041h when mapped per FR-CO-020. |
Must | Virtual-loopback integration test (CanOpenDeviceDescriptionTests): with device.eds and without the option a master writes 1800h:01 and 1A00h:00/01 by the re-mapping procedure and reads 1A00h:01 back; 2002h (ro) → 0601 0002h; PDOMapping=0 of 2002h stands as PdoMappable = false in the OD, PDOMapping=1 of 2000h as true. |
Architecture decision (scope document 2026-09-15, items 13, 26); CiA 301 §7.5.2.36; #132 |
| FR-CO-028 | With a device description the node MUST keep its values as power-on values: "load" (1011h:01, RestoreDefaultParameters) and an NMT Reset Communication restore 1000h–1FFFh to the described — or the values stored since the last "save" —, an NMT Reset Node additionally the file's application objects; Reset Communication leaves the application objects unchanged. |
Must | Virtual-loopback integration test (CanOpenDeviceDescriptionTests): after local writes of 2000h and 1017h, Reset Communication yields 1017h = 200 (file) and 2000h unchanged, Reset Node 2000h = 1234h (file); "save" after 1017h = 300, then "load" and Reset Communication → 1017h = 200. |
CiA 301 §7.3.2.2.1 (power-on values), §7.5.2.14 ("load"), §7.2.8.2.1.5/§7.2.8.2.1.6 (Reset Node / Reset Communication); architecture decision (scope document 2026-09-15, item 1); #132 |
| FR-CO-029 | The system MUST check every SDO client upload and download before sending against the device description bound for the server (BindPeerDeviceDescription, EDS or DCF): with a bound file only (index, sub-index) pairs it lists — 1000h, 1001h and 1018h included —, without a file only 1000h:00, 1001h:00 and 1018h:00–04h. Sole exception (CiA DSP 304 §8.4.2.2, 13FFh:00 = number of SRDOs): if the file lists SRDO records up to at most N, sub-indices 00h–06h of 1301h–(1300h + N) and 00h–10h of 1381h–(1380h + N) count as listed, as a device provides them when loading the file; otherwise nothing, and without an SRDO record in the file none. A refusal MUST be reported as PeerSdoAccessException before any SDO frame. A DCF commissioned for another node-ID MUST be rejected with ArgumentException and leave an existing binding in place. The bound description is mutable (Objects, the EdsDcfNet model) and is read by every reader — the gate on every call, the loader, the observers, a safety transaction at its reading at the start — without synchronisation: an in-place change takes effect from the next call; a change during a reading call is not supported, and what that call then sees is undefined — the EdsDcfNet dictionaries are not safe for reading in parallel with writing: an enumerating reader (loader, reading of a safety transaction) may fail with the dictionary's InvalidOperationException, reported as that call's error, a reader with single accesses around its own awaits (file fallback of the observers, Contains of the gate) may answer from a partly old, partly new description or report no hit, without error; a bound description is therefore changed only while no reading call is running (documented limitation, #296; immutable description in the next major, #299). |
Must | Virtual-loopback integration test (CanOpenPeerSdoGateTests): a listed object is transferred, an unlisted one rejected before the first frame; without a file the three base objects pass, 1003h and 1018h:05 do not; a file without 1000h refuses 1000h; a DCF for another node-ID throws and does not replace the binding; the SRDO records up to the file's highest pass, above it, beyond sub-index 6/16 and without an SRDO record in the file they do not (CanOpenPeerSafetyTests.The_Gate_Implies_The_Srdo_Records_Up_To_The_Highest_Declared_One, Without_An_Srdo_Record_In_The_File_The_Gate_Implies_None), and a device with a gap in the SRDO numbers is configured, verified and started in the boot-up (A_Device_Whose_File_Leaves_An_Srdo_Number_Out_Is_Configured_And_Verified, CanOpenSafetyBootUpTests.A_Slave_Whose_File_Leaves_An_Srdo_Number_Out_Is_Verified_And_Started). |
Decision 1 (scope document 2026-09-26); #174 |
| FR-CO-030 | The system MUST, with ObserveForeignPdoAsync, split the payload of another node's PDO into a sink of the caller's without writing its own OD. COB-ID (1400h:01 / 1800h:01) and mapping (1600h–17FFh / 1A00h–1BFFh) MUST be read live via the SDO client, i.e. through the gate of FR-CO-029; if the live read aborts, times out, the gate refuses the pair, the node's SDO channel is occupied at the start of the observation or it yields no usable word — for the mapping also a value of another width than its type: sub-index 00h exactly one byte (UNSIGNED8), an entry exactly four (UNSIGNED32), not truncated to the low bytes —, the entry of the EDS/DCF passed to the call MUST apply (for a DCF the ParameterValue). A live-read COB-ID with bit 31 set is not replaced by the file, a cancelled call does not fall back to the file; an unknown COB-ID, an invalid PDO and a restricted CAN-ID are not split; a payload shorter than the mapping writes nothing. The live reads of one observation — COB-IDs, mapping — are a snapshot of the peer: the observer takes the peer's SDO channel without waiting and holds it until the end, so that a safety transaction starting meanwhile waits for it (as for every running call) and another SDO call of this node to the peer is refused as "already in flight"; a channel occupied at the start turns every live read of this observation into "already in flight", the whole snapshot then comes from the file — a live-read COB-ID is never combined with the file mapping because a caller cut in (#295). A local SDO timeout or transport error ends the live reading for this call; the remaining records use the file, the next call tries the peer again. |
Must | Virtual-loopback integration test (CanOpenForeignPdoObserveTests): live mapping splits a TPDO without OD write; SDO abort, timeout, gate refusal and running transfer take the file, as do a counter of two bytes and an entry of three or five bytes (A_live_read_that_is_not_a_usable_COB_ID_or_mapping_falls_back_to_the_file); a live-invalid COB-ID stays invalid; short payload, unknown and restricted COB-ID yield no signal; a call cutting in between the COB-ID and the mapping read is refused as "already in flight" and the snapshot stays live (A_call_cutting_in_mid_snapshot_is_refused_and_the_snapshot_stays_live). |
Decision 2 and question 3 (scope document 2026-09-26); #163 |
| FR-CO-031 | The system MUST perform the election of the active NMT master per CiA 302-2 version 4.1.0 (NMT flying master, 1F90h): participation via 1F80h bit 0 and bit 5 or StartFlyingMaster, request on 073h, response and claim on 071h (priority, node-ID), time-slot race on 072h with priority × 1F90h:04 + node-ID × 1F90h:05, re-election on 076h. A better or equal priority puts this node on standby without deposing an active master of equal rank; with equal priority the lower node-ID wins; after the first, cold election without response, Reset Communication MUST go to all and a warm re-election follow; the loss of the active master's heartbeat MUST trigger a warm election. A priority above 2, a device time slot of 0 and a time-slot pair that does not separate the priorities (1F90h:04 ≤ 127 × 1F90h:05) MUST be rejected with 0609 0030h. |
Must | Virtual-loopback integration test with virtual clock (CanOpenFlyingMasterTests, CanOpenFlyingMasterGapTests): alone on the bus the node wins after the cold reset; worse priority yields, better forces the restart and wins; an equal claim does not depose the active master; equal priority → lower node-ID; heartbeat loss starts a warm election; invalid 1F90h values → 0609 0030h. |
Decision 4 and question 4 (scope document 2026-09-26); CiA 302-2 v4.1.0 (public description, members' text not in the repository); #164 |
| FR-CO-032 | As active master the system MUST boot the slaves from 1F81h: Reset Communication per slave to its node-ID (not with bit 4 keep-alive), NMT Start after its first heartbeat or boot-up (bit 2), mandatory slaves (bit 3) before its own Operational and before a simultaneous start (1F80h bit 1); 1F80h bit 2 holds its own state, bit 3 suppresses booting, bit 4 and bit 6 select the reaction to the boot timeout 1F89h (Reset Node or Stop to all, otherwise Reset Node to the missing slave). 1F82h MUST hold the last seen state per node-ID; a write to it requests the NMT command as active master and is otherwise rejected with 0800 0022h. A tool profile starts with bits 2 and 3 set. | Must | Virtual-loopback integration test with virtual clock (CanOpenFlyingMasterTests, CanOpenFlyingMasterGapTests): an assigned slave is reset, then started and its state kept in 1F82h; bit 3 leaves slaves untouched; a never-answering mandatory slave times out, bit 4 and bit 6 take effect; 1F82h request only as active master; a tool starts neither itself nor slaves until the bits are cleared. |
Question 5 (scope document 2026-09-26); CiA 302-2 v4.1.0 (1F80h, 1F81h, 1F82h, 1F89h); #164 |
| FR-CO-033 | The system MUST be able to detect the nodes on the bus by listening, without opening a node and without sending (CanOpenDiscovery.ListenAsync): during a window chosen by the caller (default 2 s, any positive duration up to int.MaxValue ms) a heartbeat (state byte 04h, 05h, 7Fh) or a boot-up (00h) on 700h + node-ID, bit 7 (guarding toggle) masked, counts per node-ID 1..127; either of the two messages suffices. Other state bytes, RTR, 29-bit and empty frames are no message. The result names per node the observed message kinds and the state of the last heartbeat. |
Must | Virtual-loopback integration test (CanOpenDiscoveryTests): boot-up alone and heartbeat alone each report one node, a state byte 01h none; while listening no frame goes onto the bus, not even for NMT, SDO request and guarding RTR; the window is the caller's, 2 s without one; 0 and negative windows are rejected. |
Decision 3, questions 1, 6 and 8 (scope document 2026-09-26) |
| FR-CO-034 | The system MUST perform an active scan only on explicit call (CanOpenDiscovery.ScanAsync): via an opened node exactly one SDO upload of 1000h:00 and no other request goes to every node-ID 1..127 except its own and those excluded by the caller, through the gate of FR-CO-029; a silent node-ID afterwards receives only the SDO client's usual timeout abort (0504 0000h). Present is a node that answers — with the value, with an SDO abort or with a response the client aborts as faulty; the client's own timeout is absence. A node-ID whose file does not list 1000h:00 or with which an SDO transfer is already running is neither requested nor reported; every other error ends the scan. An SDO client request the bus rejects or does not confirm MUST end its transfer immediately with CanOpenTransportException (in addition to BackgroundExceptionOccurred) instead of letting it run into the SDO timeout, so that a dead bus does not appear as an empty one. |
Must | Unit test and virtual-loopback integration test (CanOpenDiscoveryTests): the requested node-IDs are 1..127 without client and exclusions; value, peer abort and faulty response report, timeout, gate and running transfer do not; a disposed client and a transport error end the scan; two virtual nodes are found with their 1000h, a file without 1000h keeps the request off the bus; a bus that rejects every send ends the scan with CanOpenTransportException, and upload, download and block upload fail there immediately (CanOpenSdoClientSendFailureTests). |
Decision 3, questions 2 and 8 (scope document 2026-09-26) |
| FR-CO-035 | With CanOpenNodeOptions.SrdoCount = n > 0 (0..64, default 0) the system MUST create the safety objects 1300h, 1301h–(1300h + n), 1381h–(1380h + n), 13FEh and 13FFh as managed communication objects with the defaults of §8.4.2.2: 1300h = 0; in the communication record sub0 = 6, sub1 = 1 for SRDO 1 with node-ID 1..32, 2 for node-ID 33..64 (EN 50325-5:2010 Table 5) and otherwise 0, sub2 = 50 with default direction 2 and otherwise 25 (ibid.), sub3 = 20, sub4 = 254, sub5/sub6 = FFh + 2 × node-ID / 100h + 2 × node-ID only for SRDO 1 with node-ID ≤ 64, otherwise 0; mapping empty; 13FEh = 0; 13FFh:00 = n, sub1..n = 0. With n = 0 the node MUST remain the CiA 301 node it was — none of these objects, no reception on 001h —, and records above n are not reserved. Reset Node, Reset Communication, 1010h "save" and 1011h "load" MUST treat the safety objects like the other communication objects; restoring stored values does not set 13FEh to 0. Outside OPERATIONAL they MUST be writable on the bus without WritableCommunicationParameters as well (EN 50325-5:2010 Tables 5, 8, 11, 13, 15), sub0 and sub4 of the record and 13FFh:00 read-only; a file may describe them ro. 13FEh MUST hold a written value other than A5h as 00h (ibid. §6.4.1.6). |
Must | Virtual-loopback integration test (CanOpenSafetyCommunicationProfileTests, CanOpenSafetyNodeTests.Reset_Communication_Restores_The_Safety_Objects, CanOpenSafetyNodeTests.Node_Without_Srdos_Is_Unchanged): without SRDO no safety object, 1301h is then declared by the application; defaults per §8.4.2.2, no default COB-IDs above node-ID 64; with two SRDOs only their records reserved; a stored valid configuration survives Reset Communication and Reset Node (Reset_Node_Restores_The_Stored_Safety_Objects); "load" and Reset Communication bring back the defaults (Load_Then_Reset_Brings_Back_The_Safety_Defaults); without the option read-only on the bus side. |
DSP 304 V1.0 §8.4.2, Table 6, §9.4; item 1 (scope document 2026-10-09) |
| FR-CO-036 | The system MUST check every write to these objects — via SDO as locally — before storage per §8.3.2.4 and §8.4.2.2: records, 13FEh and 13FFh are rejected in OPERATIONAL with 0800 0022h (1300h not); direction 3..255, time 0, every write to sub4, a COB-ID outside 101h..17Fh odd or 102h..180h even, COB-ID 2 ≠ COB-ID 1 + 1, set bits 11..31, a COB-ID change on an existing SRDO and the COB-ID of another existing SRDO with 0609 0030h; a mapping write on an existing SRDO with 0601 0000h; an odd mapping count or one above 16 with 0609 0030h, an unequal pair (plain/inverted) with 0604 0041h, more than 8 bytes with 0604 0042h, every entry as in FR-CO-020, but without dummy entries and only onto objects of fixed width: a VISIBLE_STRING, OCTET_STRING, UNICODE_STRING or DOMAIN is rejected at the slot write, at the count write (for every slot it releases) and at creation with 0604 0041h — a rule of this implementation, not a clause of DSP 304: safety process data have a fixed width, and the width is what mapping entry and checksum pin. Creation (sub1 ≠ 0) requires both COB-IDs and mapped objects accessible in that direction. Every accepted write to a record or to 13FFh MUST set 13FEh to 0 — also one with the value the object already holds (CiA 319 v2.0.0 §5.3: "with the first SDO download indication for any of the safety-related configuration data, even if the value included in the download indication is identical to the value currently in the object entry"); a rejected write — read-only entry, value range, OPERATIONAL — not (§5.3: not if "the download indication is answered with a failure") —; 13FEh takes any value, valid is only A5h. An object that an existing SRDO (sub1 ≠ 0) maps, producer as consumer, MUST reject redeclaration by the application (Add*) with InvalidOperationException naming the SRDO: the checksum covers the mapping entry, not the declaration behind it (§8.4.2.2, 1381h–13C0h: "For changing the SRDO mapping first the SRDO shall be deleted"); after deletion it is allowed, because re-creation checks every mapped object. The PDOs do not know this rule. |
Must | Virtual-loopback integration test (CanOpenSafetyCommunicationProfileTests): value-range, COB-ID and mapping rules with their abort codes; no COB-ID change on an existing SRDO; two SRDOs share no COB-ID, not even via the write; a PDO takes no SRDO COB-ID; the direction checks the access of the mapped objects; creation needs both COB-IDs; write lock in OPERATIONAL except 1300h, also for a direct write racing the transition to OPERATIONAL: the transition takes the object dictionary's write gate (CanOpenSafetyNodeTests.A_Direct_Safety_Write_Holding_The_Gate_Is_Stored_Before_The_Node_Enters_Operational); a 13FFh write clears 13FEh, an SDO download with unchanged value likewise, a rejected one does not (CanOpenSafetyNodeTests.A_Download_Of_The_Identical_Value_Clears_13FEh_A_Refused_One_Does_Not); an object mapped by an existing SRDO is not redeclared, after deleting the SRDO it is, an unmapped one always (CanOpenSafetyNodeTests.An_Object_An_Existing_Srdo_Maps_Cannot_Be_Redeclared); a string or a domain is rejected at the slot write and at creation (A_Variable_Size_Object_Cannot_Be_Mapped), at the count write also a slot whose object was redeclared as a domain after the slot write (The_Count_Write_Refuses_A_Slot_Whose_Object_Lost_Its_Fixed_Width), likewise by the local pre-check that writes nothing (CanOpenSafetyNodeTests.A_Refused_Reconfiguration_Leaves_The_Committed_Srdo_As_It_Was, cases "visible string", "domain"). |
DSP 304 V1.0 §8.3.2.4 footnote 1, §8.4.2.2, Figure 7; rule from #133; item 2 (scope document 2026-10-09) |
| FR-CO-037 | The system MUST form the checksum 13FFh:n per §8.4.2.2 over direction (1 byte), refresh time or SCT (2), SRVT (1), COB-ID 1 (4), COB-ID 2 (4), mapping count (1) and per mapping sub-index that index (1) and its value (4), as CRC-16/XMODEM (polynomial 1021h, initial value 0000h, no reflection and no final XOR) with multi-byte fields little-endian, the CANopen byte image, as CANopenNode's CO_SRDOcalcCrc feeds them. V1.0 names neither initial value nor byte order; EN 50325-5:2010 §6.4.1.5 gives the initial value 0000h and leaves the byte order open, so it stays a decision (#289). SrdoCrc is public; Compute rounds the times to whole milliseconds (half to even) and rejects a negative time, a refresh time or SCT above 65535 ms, an SRVT above 255 ms (each after rounding) and a direction above 2 with ArgumentOutOfRangeException instead of checksumming a value truncated to the field; 0 is permitted. |
Must | Unit test (SrdoCrcTests): the primitive yields 31C3h for "123456789"; the canonical byte sequence follows the listed order; Compute is the primitive over that sequence; every field changes the checksum; every bound on both sides, after rounding; rounding half to even. |
DSP 304 V1.0 §8.4.2.2; initial value and byte order decision; item 3 (scope document 2026-10-09); #289 |
| FR-CO-038 | On the transition to OPERATIONAL the system MUST check per SRDO with direction ≠ 0 that 13FEh = A5h and that 13FFh:n matches the record's checksum; otherwise the SRDO is ConfigurationInvalid: a producer does not send, a consumer reports invalid at once. The node's NMT state is unaffected. CommitSafetyConfiguration MUST write the checksums before A5h, because every checksum write clears 13FEh. |
Must | Unit test with virtual clock (SrdoEngineProducerTests: with an invalid configuration nothing is sent) and virtual-loopback integration test (CanOpenSafetyNodeTests.Tampered_Checksum_Makes_The_Configuration_Invalid_At_Start, Commit_Writes_Checksums_Then_Valid). |
DSP 304 V1.0 §9.5, §8.3.1 step D, §9.2; item 4 (scope document 2026-10-09) |
| FR-CO-039 | A valid producer MUST in OPERATIONAL send one pair per refresh time — the plain frame on COB-ID 1, then its bitwise inversion on COB-ID 2, as one unit on an ordered send chain for all SRDOs of the node —, the first transmission delayed by 0.5 ms × node-ID. TriggerSrdoAsync and a change-of-state write by the application to a mapped object (EnableChangeOfStateSrdo, default on) MUST send immediately and restart the cycle. Per SRDO at most one pair is in flight; a pair falling due meanwhile waits as the pending one (a newer one replaces it) and goes as soon as the running one has completed; leaving OPERATIONAL and dispose discard it, and a pair already on the send chain sends nothing afterwards — neither another SRDO's pair waiting behind the running one nor the inverted half of the running one (§8.3.2.2); every frame is handed to the bus layer with the cancellation token of its OPERATIONAL period, leaving OPERATIONAL and dispose cancel it (effective up to the bus layer's driver call, which checks the token immediately before, also after waiting behind another send — #294; a frame handed to the driver still goes out). A send error is reported via BackgroundExceptionOccurred, the cycle continues. If a mapped object is missing, has no fixed width or no longer the width of its mapping entry (§8.1.3.1), the producer MUST send nothing instead of a padded or truncated pair, report that and become MappedObjectUnavailable — without further cycle and without TriggerSrdoAsync transmission until the SRDO is rebuilt. |
Must | Unit test with virtual clock (SrdoEngineProducerTests): initial delay, pair on consecutive IDs, cadence, Trigger restarts the cycle, a throwing send is reported, a mapped object of another or without fixed width or a missing one stops the producer (A_Mapped_Object_Of_Another_Width_Stops_The_Producer, A_Mapped_Object_Without_A_Fixed_Width_Stops_The_Producer, A_Missing_Mapped_Object_Stops_The_Producer); virtual-loopback integration test (CanOpenSafetyNodeTests.Producer_And_Consumer_Exchange_An_Srdo, Pairs_Reach_The_Consumer_In_Order, Pairs_Due_While_One_Is_Unconfirmed_Coalesce_Into_The_Latest, A_Pending_Pair_Is_Dropped_When_The_Node_Leaves_Operational, A_Queued_Pair_Of_Another_Srdo_Does_Not_Reach_The_Bus_After_Leaving_Operational, A_Pair_Pending_Before_A_Stop_Is_Not_Sent_After_The_Next_Start, Disposing_The_Node_Ends_The_Pairs_Still_On_The_Send_Chain, Leaving_Operational_Cancels_The_Send_Of_A_Pair_In_Flight, Disposing_The_Node_Cancels_The_Send_Of_A_Pair_In_Flight, A_Pair_Of_The_Next_Operational_Period_Is_Not_Cancelled). |
DSP 304 V1.0 §8.1, §8.1.3.1, §9.5; item 5 (scope document 2026-10-09) |
| FR-CO-040 | A consumer MUST in OPERATIONAL start invalid with NotReceived, let the SCT run and start the SRVT on the plain frame. An inverted frame without preceding plain one is OutOfOrder — except while the SRDO is still NotReceived as armed, before any pair (a short one, not processed, included), expiry or mismatch of this OPERATIONAL period: then it belongs to no pair of this consumer's (a producer already running when the consumer entered OPERATIONAL between its plain and its inverted frame), is consumed without a transition, the SCT runs on, and the next pair makes the SRDO valid (#360) —, one that is not its bitwise inversion of equal length Mismatch, one after expiry of the SRVT ValidationTimeExpired; if a valid pair fails to arrive within the SCT, the SRDO is SafeguardCycleExpired. A valid pair writes the mapped objects, reports SrdoReceived, restarts the SCT and makes an invalid SRDO valid again. If a mapped object rejects its bytes, the write MUST end there (the objects before keep the pair's bytes), the rejection MUST be reported via BackgroundExceptionOccurred and the SRDO MUST become ActuationRefused; the pair counts as not received, as §8.1.3.1 does not process a pair too short for the mapping: no SrdoReceived, the SCT is not restarted. The reason is the most recent cause: a running SCT continues from the last accepted pair and reports SafeguardCycleExpired on expiry, one already expired stays expired. On a node this is a safeguard like MappedObjectUnavailable: today no public path produces a rejected mapped write — the node sets no write validator on application objects and offers no public one, and a mapped object is not redeclared. A pair shorter than the mapping is not processed and produces EMCY 8210h once per run, a longer one delivers its first bytes within the 8 bytes of a classic frame (§8.1.3.1). A CAN-FD frame or a payload above 8 bytes on either ID MUST be rejected as no SRDO frame like a wrong inversion — Mismatch, a waiting plain frame discarded, nothing written, no SrdoReceived, the SCT not restarted, no EMCY (§8.1: classic CAN frame). SrdoStateChanged MUST report transitions only and is critical: it is never displaced, an equal one still waiting absorbs it. |
Must | Unit test with virtual clock (SrdoEngineConsumerTests): valid only with a complete pair; OutOfOrder after a pair, a lone inverted frame before it without a transition and without restarting the SCT (A_Lone_Inverted_Frame_Before_The_First_Pair_Is_No_Pair_And_No_Transition), after a short pair OutOfOrder again (A_Lone_Inverted_Frame_After_A_Short_Pair_Is_OutOfOrder), Mismatch, expiry of SRVT and SCT; a valid pair after expiry makes valid and restarts the SCT; every pair restarts the SCT; short pair → 8210h once and not processed; long pair → first bytes; a frame above 8 bytes (first, second, both) and a CAN-FD pair → Mismatch without effect, the next correct pair makes valid (A_Frame_That_Is_Not_Classic_Can_Is_No_Srdo_Frame); RTR, own send IDs and frames outside OPERATIONAL without effect; a rejected mapped object → exactly one transition to ActuationRefused, no SrdoReceived, one report, the object before written, the ones after not, the next accepted pair makes valid with exactly one transition, and the SCT does not restart (A_Refused_Mapped_Write_Invalidates_With_ActuationRefused, A_Refused_Mapped_Write_Stops_The_Pair_At_The_Refusal, A_Refused_Pair_Does_Not_Restart_The_Sct). |
DSP 304 V1.0 §8.1.1, §8.1.3.1, §9.5; decision question 8 (re-validation); item 6 (scope document 2026-10-09) |
| FR-CO-041 | The system MUST send the GFC (COB-ID 001h, DLC 0) only in OPERATIONAL — immediately, not behind SRDO pairs — and otherwise reject it with InvalidOperationException; 1300h is not a precondition but the record: the request sets it to 1, and so does a received GFC (EN 50325-5:2010 §6.4.2.1, §7.2.1, #289); the returned task MUST be the send itself — it ends when the bus layer has confirmed the send (on the echo path with the echo, otherwise with acceptance by the driver) or reports its failure (via BackgroundExceptionOccurred) or it was cancelled, not already with the actor turn; like an SRDO frame it is handed over with the cancellation token of the OPERATIONAL period, linked with the caller's token: leaving OPERATIONAL and dispose cancel it without error report, the caller with OperationCanceledException (also before the actor turn), each up to the bus layer's driver call, also for a send already waiting there behind another (#294; a frame handed to the driver still goes out); a bus layer disposed under the send ends likewise with OperationCanceledException and is additionally reported as a transport error; a received GFC MUST be reported under the same conditions and only as a classic CAN frame with DLC 0 as GlobalFailsafeCommandReceived (a CAN-FD frame on 001h is no GFC), on a bus with echo also its own. |
Must | Unit test (SrdoEngineConsumerTests.Gfc_Is_Received_And_Sent_Only_While_Operational_And_Is_Recorded_In_1300h, A_Can_Fd_Frame_On_001h_Is_No_Gfc) and virtual-loopback integration test (CanOpenSafetyNodeTests.Gfc_Round_Trip, The_Gfc_Does_Not_Wait_For_An_Unconfirmed_Pair, Repeated_Gfcs_Waiting_For_The_Handler_Are_Folded_Into_One, A_Nodes_Own_Gfc_Is_Reported_Once_On_A_Bus_That_Echoes in both echo worlds; Leaving_Operational_Cancels_A_Gfc_Send_Not_Yet_Started, Disposing_The_Node_Cancels_A_Gfc_Send_Not_Yet_Started, A_Gfc_Of_The_Next_Operational_Period_Is_Not_Cancelled, A_Gfc_Send_Cancelled_By_Its_Period_Ends_Quietly, The_Gfc_Call_Completes_When_The_Frame_Was_Handed_Over, Cancelling_The_Callers_Token_Cancels_The_Gfc_Send, A_Gfc_Call_Cancelled_Before_It_Runs_Sends_Nothing). |
DSP 304 V1.0 §8.2, §8.4.2.2 (1300h); decision question 8 (echo GFC); item 7 (scope document 2026-10-09) |
| FR-CO-042 | The system MUST offer the safety functions as ICanOpenSafety via CanOpenSafetyExtensions.Safety(this ICanOpenNode) without extending ICanOpenNode (a foreign node → NotSupportedException): ConfigureSrdoProducer/ConfigureSrdoConsumer (delete-first order, the default COB-IDs for SRDO 1 up to node-ID 64), DeleteSrdo, CommitSafetyConfiguration, TriggerSrdoAsync, GetSrdoState, SendGlobalFailsafeCommandAsync, the three events and the options SrdoCount and EnableChangeOfStateSrdo. A configuration in OPERATIONAL MUST be rejected with InvalidOperationException. A configuration the object dictionary would reject at any point MUST be rejected before the first write and change nothing (the same writes through the same validator against a copy). |
Must | Virtual-loopback integration test (CanOpenSafetyNodeTests): node without SRDO unchanged, foreign node not supported, configure, commit and send, rejection in OPERATIONAL, a rejected reconfiguration changes nothing (A_Refused_Reconfiguration_Leaves_The_Committed_Srdo_As_It_Was); unit test (SafetyTypesTests); PublicApiSurfaceTests. |
Decision 7 (scope document 2026-10-09); item 8 |
| FR-CO-043 | The system MUST load the safety objects of an EDS/DCF like the PDO records — via the validated write path, the mapping before creation, with the file's access type, every deviation a finding with abort code, 13FEh last —; the highest described SRDO record raises SrdoCount, the option is a lower bound; a record below it that the file omits nevertheless exists — 13FFh:00 is the number of SRDOs (§8.4.2.2) — with its defaults, the SRDO deleted, and is a finding (SuppliedDefault); a mapping record without communication record is applied, the SRDO stays deleted. PeerSafetyConfiguration.FromDeviceDescription MUST form as expectation what a device with the given node-ID holds after loading the DCF — with the same code as the node's loader, over an object dictionary of its own, not with a second rule set: a missing, unreadable or device-rejected value keeps the device's default (25 ms, 20 ms, the pre-defined pair of SRDO 1 with node-ID ≤ 64), and an SRDO whose creation the device rejects (no consecutive COB-ID pair, COB-ID of another existing SRDO, no mapping record, a mapping not applicable, e.g. one onto an object without fixed width) is absent. |
Must | Virtual-loopback integration test (CanOpenSafetyDeviceDescriptionTests, fixture safety.dcf): the count follows the highest record; an omitted record below it exists deleted and is a finding (A_Record_Below_The_Highest_Declared_One_That_The_File_Omits_Exists_Deleted); the values reach the records and the configuration is valid; rejected values and mapping entries are findings, the SRDO stays deleted; a mapping count above 16 is a finding and is not truncated to one byte (A_Mapping_Count_Above_Sixteen_Is_A_Finding_And_The_Srdo_Stays_Deleted); a slot mapped onto a string or a domain is a finding with 0604 0041h naming the violated rule (0604 0041h stands for several), the SRDO stays deleted (A_Mapped_Object_Without_A_Fixed_Width_Is_A_Finding_And_The_Srdo_Stays_Deleted), the shipped fixtures still load; a wrong checksum loads without finding but is invalid; $NODEID formula; peer configuration from a DCF; a DCF without SRVT yields 20 ms and configures the peer (CanOpenPeerSafetyTests.A_Description_Without_A_Validation_Time_Yields_The_Default_And_Configures_The_Peer); the expectation agrees with the device loading the same file, for every SRDO, also for node-IDs whose pre-defined COB-IDs differ from the file's (CanOpenSafetyDeviceDescriptionTests.Peer_Configuration_Agrees_With_The_Device_Loading_The_Same_File); a 13FFh:n the file declares ro or const refuses a download with 0601 0002h and leaves 13FEh (A_Checksum_The_File_Declares_Read_Only_Refuses_A_Download_And_Leaves_13FEh). |
CiA 306, FR-CO-025..028; item 9 (scope document 2026-10-09) |
| FR-CO-044 | The system MUST, with ConfigurePeerSafetyAsync, write a peer's safety parameters per §9.2 (Figure 9): read the SRDO count from 13FFh:00 (an SRDO above it → ArgumentException, a response that is not exactly one byte — 13FFh:00 is UNSIGNED8; none, two or four bytes are rejected, not truncated to the low byte — or a count above 64 (§8.4.2.2) → InvalidOperationException, both before any write), check the times before the first frame (SRVT 1..255 ms for every direction), sub1 = 0 for every SRDO, then per SRDO the record in delete-first order (unnamed ones stay deleted; of the mapping sub0 and the slots 1..2k of its k objects, none above), then 1300h, then all checksums; read everything back and compare byte-wise; only without deviation write 13FEh = A5h and read it back. VerifyPeerSafetyConfigurationAsync MUST per §8.3.1 step D read and compare 13FEh, 13FFh:n of the expected SRDOs and the records without writing; an unexpected SRDO is checked only via sub1 = 0, its checksum not, because the device does not check it (§9.5). Both run through the gate of FR-CO-029 and one after another per peer and hold the peer's SDO channel for the whole transaction — they wait for a call to the peer already running (their cancellation token ends the waiting), and every other SDO call of this node to the peer is refused meanwhile as "already in flight" instead of cutting in; a transaction works with the description as it was at the start — the moment it holds the peer's SDO channel, after a call it waited for — pre-check and gate of each of its transfers read a snapshot taken then (what it contains, FR-CO-051 lists), not the bound, publicly mutable instance —; a re- or unbinding or an in-place change of the bound description takes effect from the next call; likewise it works with a copy of the passed configuration or expectation taken at the start — pre-check, write list and comparison —, an SRDO added later is not part of it; an abort, a timeout or a gate refusal is passed through. 13FEh is 0 as soon as at least one parameter write was accepted; an abort before that — e.g. 0800 0022h from an OPERATIONAL peer — leaves it unchanged. PeerSafetyConfiguration.Add MUST reject what the peer's validator would reject regardless of its state (§8.4.2.2): a direction other than tx/rx, COB-IDs that are not an odd pair 101h..17Fh/next ID, times outside 1..65535 ms or 1..255 ms and a COB-ID another SRDO of the same configuration uses. ConfigurePeerSafetyAsync MUST check every mapping entry before the first frame against the bound EDS/DCF — declared, marked mappable (PDOMapping, missing = not mappable), of a data type of fixed width (no string, no domain, as locally; without DataType or with a type code unknown to this implementation likewise rejected, because the width is not known) and as wide as that, accessible in the SRDO's direction (AccessType) — and otherwise reject with ArgumentException, because §9.2 first deletes all SRDOs; what only the peer can judge stays with the peer. |
Must | Virtual-loopback integration test (CanOpenPeerSafetyTests): write, read back, acknowledge; rejected without bound description; an OPERATIONAL peer aborts and keeps its 13FEh = A5h; a deviating readback does not acknowledge; a tampered checksum and a deviating record make the verification fail; times outside already rejected by Add, likewise direction, COB-ID pair and collision within the configuration (A_Time_Outside_Its_Range_Is_Refused_When_The_Srdo_Is_Added, Add_Refuses_What_The_Device_Would_Refuse_Anyway, Add_Refuses_A_Second_Srdo_On_The_Same_Cob_Ids, Configure_Refuses_A_Mapping_The_Bound_File_Rules_Out_Before_Any_Frame (also VISIBLE_STRING, DOMAIN, without DataType and with unknown type code), A_Write_Between_The_Readback_And_The_Acknowledgement_Is_Refused (witnesses the reservation between two transfers; the two following show the refusal and the file fallback that during a transfer its own in-flight check would also yield), An_Sdo_Call_To_The_Peer_During_A_Safety_Transaction_Is_Refused_As_In_Flight, An_Sdo_Call_To_Another_Peer_Does_Not_Wait_For_The_Transaction, A_Safety_Transaction_Waits_For_A_Call_In_Flight, A_Safety_Transaction_Waiting_For_The_Channel_Is_Cancelled_By_Its_Token, An_Observer_During_A_Safety_Transaction_Falls_Back_To_The_File); a producer's SRVT is written; two SRDOs swap their COB-IDs; SRDO above the count and missing count; a count above 64 is rejected before any further frame, 64 is taken (A_Count_Above_64_Is_An_Error_Not_64_Srdos, A_Count_Of_64_Is_Taken); a count of 0, 2 or 4 bytes is rejected by configuration and verification before any further frame (A_Srdo_Count_That_Is_Not_One_Byte_Is_Refused_Before_Any_Further_Frame); an unbinding or rebinding during a held configuration does not change it but takes effect for the next call (A_Rebind_During_A_Safety_Transaction_Takes_Effect_For_The_Next_Call), likewise an in-place change — 1300h removed, PDOMapping of a mapped object cleared, the SRDO records removed (An_In_Place_Edit_Of_The_Bound_Description_During_A_Safety_Transaction_Takes_Effect_For_The_Next_Call); a change while the transaction waits for the channel is seen by it, and its pre-check rejects without sending (An_Edit_While_The_Transaction_Waits_For_The_Channel_Is_Seen_By_It); an SRDO added to the configuration during the held count read is neither checked nor written nor compared by configuration and verification (An_Srdo_Added_During_A_Safety_Transaction_Is_Not_Part_Of_It); a file with only the occupied mapping slots suffices for both, a spare slot above the count and the checksum of a deleted SRDO do not make the verification fail, a created unexpected SRDO does. |
DSP 304 V1.0 §9.2 Figure 9, §8.3.1 step D; items 10, 11 (scope document 2026-10-09) |
| FR-CO-045 | As active master the system MUST verify an assigned slave whose bound DCF describes at least one SRDO against that DCF before NMT Start (FR-CO-044); the expectation is formed from the description bound when the verification owns the peer's SDO channel — the moment it also takes its own snapshot —, not on the actor turn that queued it; expectation and gate of the transfers arise from a single reading of that description; if at that moment no DCF with SRDO is bound any more, there is nothing to verify — no verification is recorded, and the boot checks again: a DCF bound since is verified, without a DCF the slave is started like one without SRDO. Verified → start as before; not verified (deviation or failed upload, an exception additionally via BackgroundExceptionOccurred, except when the result arrives during a held Reset Communication — then it takes effect only afterwards) → FlyingMasterChanged with SlaveSafetyConfigurationInvalid and no start; a mandatory slave or a slave under simultaneous start (1F80h bit 1 set and bit 2 cleared) MUST halt the boot with the error reaction from 1F80h bit 4/6, an optional one is skipped. Own start and simultaneous start wait for running verifications; a result during a held Reset Communication takes effect only afterwards, one from an aborted boot is discarded. As long as an assigned slave is a safety slave, the master MUST send no NMT Start to node 0: the simultaneous start keeps its moment (every mandatory slave seen, no verification running), but then starts every seen slave individually — a safety slave only after the verification —, a slave reported later likewise individually; a keep-alive slave that never reports is not started. A slave already running OPERATIONAL when first seen is not started and therefore not verified; without DCF or without SRDO the boot-up runs as before. |
Must | Virtual-loopback integration test with virtual clock (CanOpenSafetyBootUpTests): verified slave started; unverified optional skipped and reported; mandatory slave halts the boot and applies the 1F80h reaction; simultaneous start waits and halts on a failed optional; with a safety slave no start to node 0, an unreported keep-alive safety slave is started only after report and verification, a non-safety slave at the start moment (A_Simultaneous_Start_Does_Not_Reach_A_Safety_Slave_Before_Its_Verification, A_Simultaneous_Start_With_A_Safety_Slave_Starts_Each_Slave_On_Its_Own); slave without SRDO as before; no boot timeout after a halt; verification during a held reset; result of an aborted boot; loss of role; throwing verification; a verification waiting for the occupied channel compares with the DCF rebound during the wait (Step_D_Takes_Its_Expectation_When_It_Owns_The_Peer_Channel); a verification without DCF does not count as verification, a DCF bound during the held reset is checked on resumption (A_Verification_That_Found_No_Dcf_Does_Not_Count_As_Verified_For_A_Dcf_Bound_Later). |
DSP 304 V1.0 §8.3.1 step D; decision 4; item 12 (scope document 2026-10-09) |
| FR-CO-046 | The system MUST, with ObserveForeignSrdoAsync, split an SRDO pair of another node into an IForeignPdoSink (ForeignPdoKind.Srdo, SRDO number) without writing its own OD: decisive is the record — among those the passed description implies, up to its highest declared communication or mapping record, as loader and gate read them (FR-CO-029) — whose live-read 1301h–1340h:05 matches COB-ID 1 — a read word decides, the file applies only if the upload fails or yields no word; a word is exactly four bytes (UNSIGNED32), the direction (sub1) exactly one byte (UNSIGNED8), a value of another length counts as not read — and which exists: a record with a direction other than 1 or 2 — 0 or a reserved value 3..255 — (sub1 live, or from the file if the upload fails) does not match, because a deleted SRDO keeps its COB-IDs and another may have taken them over (§8.4.2.2); the frames must be of equal length and bitwise inverse; the mapping comes live from 1381h–13C0h (every slot up to the count) or, if the read fails, from the file — both only if they satisfy the rule the device's loader applies; a read even live slot deviating from the odd one before it makes the pair not split, without fallback to the file (live wins); an unreadable live slot, even or odd, or a value of another width than its type (sub-index 00h exactly one byte, a slot exactly four) means: the live mapping is not read, the file decides: every even sub-index up to the count is declared and repeats the odd one before it (§8.4.2.2, 1381h–13C0h; the same check as the OD's mapping validator), otherwise the pair is not split with a reason naming the slot. Unlike FR-CO-030 a live-read mapping is always read in full once its count was read, also beyond the slots the file lists — the gate lets through every slot of an SRDO record the bound file implies (FR-CO-029); with a dummy entry and with a frame too short the pair is not split. The live reads of one observation — COB-ID, direction, mapping — are a snapshot of the peer: the observer takes the peer's SDO channel without waiting and holds it until the end, so that a safety transaction starting meanwhile waits for it (as for every running call) and another SDO call of this node to the peer is refused as "already in flight"; a channel occupied at the start turns every live read of this observation into "already in flight", the whole snapshot then comes from the file — the live-read COB-ID of one configuration is never combined with the file mapping of another. SRVT and SCT are judged by the caller. |
Must | Virtual-loopback integration test (CanOpenForeignSrdoTests): live record before the file; the file when the peer does not answer; wrong pair and unknown ID not split; a live record on another ID is not overruled by the file; a live mapping beyond the slots of the bound file is read in full and split (A_Live_Mapping_Beyond_The_Slots_The_File_Declares_Is_Read_In_Full); dummy entry not split; a COB-ID word with a bit above bit 10 does not match; a live-read sub5 of five bytes and a sub1 of two bytes count as not read, the file decides (A_Live_Communication_Value_Of_Another_Length_Is_Not_Read); a deleted record that kept the COB-ID does not match, live as from the file, likewise one with a reserved direction (A_Live_Reserved_Direction_Is_No_Existing_Record, A_Reserved_Direction_In_The_File_Is_No_Existing_Record_When_The_Peer_Does_Not_Answer); a file mapping with a missing or deviating even slot is not split, the reason names the slot (A_File_Mapping_Whose_Inverted_Slot_Does_Not_Repeat_Its_Plain_Slot_Is_Not_Decoded), likewise a live mapping with a deviating even slot (A_Live_Mapping_Whose_Inverted_Slot_Does_Not_Repeat_Its_Plain_Slot_Is_Not_Decoded); an unreadable even live slot leads to the file (A_Live_Inverted_Slot_That_Cannot_Be_Read_Falls_Back_To_The_File), likewise a live count of two bytes, a live slot of three or five bytes and an inverted live slot of five bytes whose low four bytes are right (A_Live_Mapping_Value_Of_Another_Length_Falls_Back_To_The_File); a record implied only by a mapping record of the file is found (A_Record_Implied_By_A_Mapping_Declaration_Is_Matched); a safety transaction starting between the COB-ID and the mapping read waits, and the snapshot stays live (A_Safety_Transaction_Starting_Mid_Snapshot_Waits_For_The_Observer); an observer starting during a transaction falls back entirely to the file (CanOpenPeerSafetyTests.An_Observer_During_A_Safety_Transaction_Falls_Back_To_The_File). |
DSP 304 V1.0 §8.1, §8.4.2.2; decision 3; item 13 (scope document 2026-10-09) |
| FR-CO-047 | The system MUST offer an LSS master per CiA 305 v3.0.0 (CanOpenLss.OpenMaster on a bus or a shared ICanBusService): sending on 7E5h, receiving on 7E4h, DLC 8, exactly one request at a time, exactly one master per network (not enforced across processes). Switch state global is unconfirmed (mode 00h waiting, 01h configuration). Switch state selective sends the four LSS numbers and returns true only on confirmation 44h within the response time. Configure node-id sends the value unchanged, also FFh and invalid values, and returns the slave's error code (00h, 01h, FFh with spec-error). Configure bit timing applies to table 0 (CiA 301; index 5 reserved; index 9, automatic detection, is not executed) and manufacturer tables 80h–FEh. Activate bit timing waits both switch-delay periods and calls BitRateSwitch in between if a bit rate is named and the callback is set; the adapter does not change the bit rate by itself. Store configuration, inquire of 1018h:01–04, the whole address and the node-ID. Identify remote slave over an inclusive range and identify non-configured remote slave wait out the identification window and return true if any slave answered, otherwise false; the window is drained before the next command begins. LSS Fastscan determines per LSS number the bits from 31 downwards, bit 0 takes precedence, with scan, match and skip (at most two numbers, not the vendor-ID) as well as a known prefix; the result is the smallest address of the unconfigured slaves, that slave is afterwards in Configuration. A timeout of a confirmed service is LssTimeoutException and no confirmation. A manufacturer command 128–255 is sent; the first response with the same specifier comes back, otherwise null. |
Must | Unit test (LssCodecTests) and virtual-loopback integration test (LssMasterTests) against a scripted slave: every command, error codes including manufacturer-specific, timeout, cancellation, dispose, Fastscan over several slaves, selective without hit, identify inside and outside the range, unconfigured only with node-ID FFh without active ID, too short a DLC is ignored, switch global without answer, activate waits both delays and the slave is silent in the second. |
CiA 305 v3.0.0 §6 and §7 |
| FR-CO-048 | The system MUST offer an LSS slave in CanOpenNode per CiA 305 v3.0.0, enabled via CanOpenNodeOptions.Lss or via LSS_Supported of an EDS/DCF (a set option wins, also with Enabled false). The slave knows Waiting and Configuration, reads the LSS address on every command from 1018h:01–04 (a missing sub-index counts as 0) and distinguishes persistent, pending and active node-ID as well as bit timing. A pending node-ID 1..127 becomes active on NMT Reset Communication and retargets pre-defined COB-IDs (EMCY 1014h, SDO 1200h, PDO 1400h–1403h and 1800h–1803h, 1F90h:06, SRDO 1 with node-ID up to 64) provided they still carry the pre-defined value of the previous node-ID, including the control bits; a deviating value stays. Reset Node first loads the persistent node-ID into the pending one; no NMT reset reloads the bit timing. Node-ID FFh does not start (no boot-up, no heartbeat, no SDO, NMT Start/Stop/Pre-Operational without effect); a valid node-ID configured afterwards boots by itself and the slave stays in Configuration. Store writes via ILssConfigurationStore (success 00h, medium 02h, spec-error other than 0 as FFh); without a store the slave answers 01h. Unsupported services or services received in the wrong state are ignored, except this store. Fastscan and selective apply only in Waiting, Fastscan only unconfigured. Identify remote applies in both states for every address in the inclusive range, identify non-configured only without active node-ID with pending FFh. During the second switch-delay period the slave does not send; NMT, boot-up and heartbeat falling due in that period go out only after the delay, and a flying-master Reset Communication is applied only when the frame has been sent. An LSS command arriving in that period is executed at once; its reply is held like the other frames and goes out after the delay — none is replaced by a later one, and the replies keep the order of the commands, also across a further silence and also when the silence ends while older ones are still being sent (#320). BitRateSwitch reports the new bit rate, the open adapter is not switched. 1010h and 1011h do not change the LSS layer parameters, and LSS store does not change the object dictionary. LssState() reads the state without extending ICanOpenNode. |
Must | Virtual-loopback integration test (LssSlaveTests): every command and the error codes against the real slave, Fastscan over several slaves in numerical order, node-ID change only after Reset Communication and SDO only on the new ID, Reset Node without store loads the persistent ID, FFh does not boot until configured, store hook success/medium/spec-error and loading on the next open, silence in the second delay, two commands in the second delay both answered and in order (Two_Commands_During_The_Second_Delay_Are_Both_Answered_In_Order; Replies_Keep_Their_Order_When_A_New_Silence_Begins_Between_Two_Held_Sends; A_Reply_Arriving_While_The_Flush_Is_Still_Draining_Waits_Behind_It; A_Driver_Throw_While_Flushing_A_Held_Reply_Is_Reported), COB-ID retargeting keeps a stored deviating value and rewrites the pre-defined one, EDS LSS_Supported and baud flags, explicitly off beats the file. |
CiA 305 v3.0.0 §6, §7 and §8 |
| FR-CO-049 | The system MUST offer LSS commissioning on the LSS master (LssCommissioning). It finds unconfigured slaves by Fastscan in numerical order and can assign them consecutive node-IDs from a start ID, optionally set bit timing and store the configuration. A scan may activate only if it selects exactly one slave; otherwise every slave already switched would stay on the new bit rate while the next is still sought on the old one. The configuration of a single slave may activate if it is the only LSS slave or all other nodes already run the new bit rate. Without assignment every found slave stays in Configuration until the end of the scan, then a switch state global switches to Waiting so that the same slave does not appear twice. With assignment the master first switches all slaves to Waiting. Fastscan leaves the found slave in Configuration, and the assignment configures it there; switch state selective is not repeated afterwards. A skipped LSS number is not in the address; the found slave stays in Configuration and is configured there. After every slave the master switches to Waiting before the next Fastscan runs. A failed switch to Waiting does not discard an already applied configure or inquire result. A refused service ends the series and returns that slave's result. Earlier slaves keep the pending bit rate, and the stored one if the store already succeeded; the caller restores the previous bit rate. A timeout likewise ends the scan and returns the slaves that already received a node-ID, including the slave whose configure node-id was confirmed before a later service timed out. A single slave is configured or read out (address and node-ID) via switch state selective. Already configured nodes that do not take part in Fastscan are read via SDO upload of 1018h:01–04; a node that does not answer the vendor-ID is skipped, a missing later sub-index (abort 0609 0011h) counts as 0, a timeout or another abort does not. |
Must | Virtual-loopback integration test (LssCommissioningTests): Fastscan over several unconfigured slaves and release to Waiting, assignment of consecutive node-IDs, also when Fastscan skips the serial number (An_Assigning_Scan_Assigns_When_Fastscan_Skips_The_Serial_Number), abort when store is not supported, configure and inquire of an address including bit timing, SDO identity of an answering node, skipping a silent one, no zero on timeout (ReadIdentities_Does_Not_Treat_A_Timeout_As_A_Zero), and a failed switch to Waiting does not discard the configure result (A_Cleanup_Failure_Does_Not_Hide_A_Successful_Configure), an empty scan leaves a slave in Configuration; a scan that can select more than one slave refuses an activation, limit 1 activates once (A_Scan_Of_More_Than_One_Slave_Refuses_To_Activate_Bit_Timing), a timeout after a confirmed node-ID keeps that ID (A_Timeout_After_A_Confirmed_Node_Id_Keeps_That_Id). |
CiA 305 v3.0.0 §6 and §7 |
| FR-CO-050 | The system MUST offer automatic bit-rate detection per CiA 801 (LssBitRateDetection.DetectAsync): for every candidate bit rate a factory supplied by the caller opens the controller in listen-only mode (§5.2.1), so that the search sends nothing and produces no error frame; if the controller receives a frame within the window, the rate is detected and the search ends. As many error frames as ErrorFramesToReject names (default 2, so that a single disturbed frame at the right rate does not advance the search, §5.2.3/§7), or a bus state no longer error-active at the end of the window, reject the candidate; a silent window is inconclusive. The candidates are checked in the given order (default 500/250/125 kbit/s first, §6) over Passes passes. The result names the bit rate and, if the CiA 301 table lists it, its table index (CiA 305 table 4), as well as every probe outcome. The factory opens every candidate anew, because adapters generally do not change the bit rate of an open channel (§5.2.3, Figure 2); a FaultOccurred ends the detection with that exception, cancellation with OperationCanceledException, and every probe bus is disposed. A bus not opened in listen-only mode, or whose adapter reports listen-only without applying it (Kvaser on CanKit 0.5.6, #345 — CiA 801 §5.2.4 leaves such a controller to external hardware), fails while RequireListenOnly applies; likewise a factory that does not open the requested candidate rate. The measurement of a single dominant bit (§5.2.2), the external gate circuit for controllers without listen-only (§5.2.4) and the default fallback bit rate (§5.1) are not built. |
Must | Unit and virtual-loopback integration test (LssBitRateDetectionTests) via DetectionProbeBus: detection on reception (echo does not count), rejection on error frames at the threshold and on a degraded bus state, one error report below the threshold does not reject, silent window inconclusive, second pass finds a previously silent rate, cancellation and error dispose the bus, listen-only requirement, an adapter reporting listen-only without applying it, and wrongly opened rate fail, table index only for table rates; plus a real listen-only probe on the loopback adapter with heartbeat traffic. |
CiA 801 §5.2.1, §5.2.3, §5.2.4, §6 (table index per CiA 305 table 4 / CiA 301) |
| FR-CO-051 | The system MUST execute ConfigurePeerSafetyAsync as the configuration entity per CiA 319 v2.0.0 clause 5. The transaction's snapshot (FR-CO-044) contains the declared pairs of 1300h–13FFh, 1018h and 1010h with the ones it declares ro or const, the identity the file declares in 1018h:01–03 (ParameterValue, otherwise DefaultValue) and its [DeviceInfo] numbers, the declaration of sub-index 03h per communication record (writable, read-only, not declared), the highest SRDO record and the described entries of the mapped objects. Before the first frame the following MUST be rejected: without bound description with PeerSdoAccessException naming 1018h:01 (the first transfer, with which there would be nothing to compare); an SRDO above the file's highest record with ArgumentException (the gate would never let its record through); every transfer the transaction makes regardless of the peer's answers — 1018h:01–03, 13FFh:00, 1300h, 13FFh:n of the named SRDOs, 13FEh, the sub-index of 1010h from Storage —, through the same gate as mid-transaction (PeerSdoAccessException); a file that declares a 1018h entry without a readable value (a number; a $NODEID formula is no identity), whose set [DeviceInfo] number contradicts its 1018h entry or whose 1018h:01–03 are all 0, with InvalidOperationException — it carries no identity; a missing [DeviceInfo] key (reads as 0) is no contradiction; a pair the configuration downloads that the file declares ro or const — 13FFh:n, whose access a description may restrict (#289), 13FEh, 1300h, a record entry, the 1010h sub-index from Storage —, which a peer honouring its file refuses with 0601 0002h, with InvalidOperationException; for an SRDO the configuration does not name this happens once the count is read, before the first write (a sub-index 03h declared read-only is not downloaded and is exempt). Then (§5.2) read the peer's 1018h:01, :02 and :03 and compare them with the file's 1018h entries — all three are read, every deviation is a PeerSafetyMismatch (expected the four bytes of the entry, read the peer's answer, of whatever length), the result is not Succeeded, and nothing is written; §5.2 demands all three of an SRD where CiA 301 leaves :02 and :03 optional — a peer that rejects one of them with 0609 0011h is not configured and the abort is passed through, unless an earlier entry already deviated: then the answer is part of the same finding and is reported as a deviation without read bytes. Then steps 1–3 of §5.3 as in FR-CO-044 (configuration data, checksums, readback, A5h with readback; step 4 is the SRD's business — a 13FEh it reset to 0 shows in the readback). With PeerSafetyConfiguration.Storage ≠ None (settable, also on a configuration from FromDeviceDescription), after the acknowledgement the signature "save" (65 76 61 73h) MUST be written to 1010h:01 (AllParameters) or 1010h:02 (CommunicationParameters) and to no other sub-index (step 5), PeerSafetyResult.Stored reports the confirmation; if the peer refuses the store, the acknowledgement stands and the result says both — Succeeded, not Stored, the abort in PeerSafetyResult.StorageAbort —, without acknowledgement nothing is stored, and a value outside the three MUST be rejected on setting with ArgumentOutOfRangeException. For a tx SRDO whose communication record the bound file declares without a writable sub-index 03h (not at all or read-only), the configuration or expectation MUST say 20 ms (§6.2; another SRVT is rejected before the first frame with ArgumentException) and the checksum MUST be formed over these 20 ms. If 03h is not declared, it is written nevertheless (the gate lets it through as an implied record entry), because a deletion writes only sub-index 01h and a peer that does hold it would otherwise keep the value of an earlier configuration: an abort 0609 0011h (sub-index does not exist) means "not implemented", is tolerated and exempts from the readback; an abort 0601 0002h (read-only) means "held", is tolerated, and the entry is read back and compared; a peer that accepted the write MUST answer the readback (its abort is passed through). If 03h is declared read-only, it is not written, only read back and compared. A consumer's SRVT and one declared writable are not optional, their abort is passed through; a record the file does not declare but only implies counts as complete. VerifyPeerSafetyConfigurationAsync MUST apply the same §6.2 rule (a tolerated 0609 0011h on an undeclared 03h is no deviation, every other abort is passed through), its pre-checks MUST name the parameter expected, and it does not read the identity. Clauses 7 and 8 (safety application layer) are not implemented; the device side of CiA 319 is implemented as FR-CO-058 and FR-CO-059 (#308, #329). |
Must | Virtual-loopback integration test (CanOpenPeerSafetyTests): identity from the 1018h entries, also without [DeviceInfo] keys (The_Identity_Comes_From_The_Files_1018h_Entries); contradiction, missing value, all 0 rejected before the first frame (A_File_Whose_DeviceInfo_Contradicts_Its_1018h_Entries_Is_Refused_Before_Any_Frame, A_File_Declaring_An_Identity_Entry_Without_A_Value_Is_Refused_Before_Any_Frame, A_File_Without_An_Identity_Is_Refused_Before_Any_Frame); deviating identity reported per entry, nothing written (Configure_Verifies_The_Peers_Identity_Before_Writing_Anything), all three deviations reported and only they read (An_Identity_That_Differs_Is_The_Only_Thing_Read_And_Every_Difference_Is_Reported), a missing entry after a deviation as deviation, otherwise passed through (An_Identity_Entry_The_Peer_Lacks_After_A_Difference_Is_A_Mismatch_Not_An_Abort, An_Identity_Entry_The_Peer_Lacks_Propagates_When_No_Earlier_Entry_Differed); a file without one identity entry, without 13FEh or without 1300h rejected before the first frame (A_File_That_Leaves_An_Identity_Entry_Out_Is_Refused_Before_Any_Frame, A_Fixed_Transfer_The_File_Does_Not_Declare_Is_Refused_Before_Any_Frame), SRDO above the highest record and above the count with the call's parameter name (An_Srdo_Above_The_Peers_Count_Is_Refused_Before_Anything_Is_Sent), a pair the file declares read-only refused before the first frame or, for an unnamed SRDO, after the count, nothing written (A_Pair_The_File_Declares_Read_Only_Is_Refused_Before_Anything_Is_Written), without description 1018h:01 named (Configure_Is_Refused_Without_A_Bound_Description); storing after the acknowledgement — a reset brings back the configuration including A5h, without storing the power-on values (Configure_Stores_The_Acknowledged_Configuration_When_Asked) —, to the sub-index asked for and no other (Configure_Stores_Through_The_Sub_Index_Asked_For), no storing without acknowledgement (No_Store_Without_The_Acknowledgement), undeclared sub-index before the first frame (A_Storage_The_Bound_File_Does_Not_Declare_Is_Refused_Before_Any_Frame), the peer's abort in the result with the acknowledgement standing (A_Store_The_Peer_Refuses_Is_Reported_With_The_Acknowledgement), Storage settable and checked (Storage_Can_Be_Set_On_A_Configuration_Built_From_A_Description); §6.2: sub-index 03h written as optional and read back, 0609 0011h tolerated, checksum over 20 ms, consumer SRVT written (A_Transmit_Srdos_Srvt_The_File_Does_Not_Declare_Is_Written_As_Optional_And_Checksummed_At_The_Default), declared read-only not written (A_Read_Only_Srvt_The_File_Declares_Is_Read_Back_Not_Written, A_Device_Whose_File_Declares_The_Srvt_Read_Only_Is_Configured_Around_It), 0601 0002h on the optional write tolerated and read back (A_Read_Only_Srvt_The_File_Leaves_Out_Is_Tolerated_On_The_Write_And_Read_Back), accepted write must be read back (A_Peer_That_Took_The_Optional_Srvt_Answers_Its_Readback), consumer SRVT and declared SRVT not optional in configuration and verification (An_Srvt_The_File_Declares_Or_A_Consumer_Needs_Is_Not_Optional), a device holding an old SRVT its file omits gets 20 ms written (A_Device_Holding_An_Srvt_Its_File_Leaves_Out_Gets_The_Default_Written), another SRVT rejected by configuration and verification before the first frame (Another_Srvt_For_A_Transmit_Srdo_Without_One_On_The_Peer_Is_Refused_Before_Any_Frame). Mutation per path in the PR description (#310). |
CiA 319 v2.0.0 §5.2, §5.3, §6.2; FR-CO-044 |
| FR-CO-052 | The system MUST evaluate 1019h and the SYNC counter per §7.2.5/§7.5.2.22 as well as 1800h:06 per §7.5.2.37: start 1, overflow to 1, reset after boot-up, on leaving Stopped, on activating the producer and on resuming a zero period; only confirmed transmissions count, discarded/cancelled transmissions consume no counter value and a late confirmation does not overwrite a reset; DLC check; start value 0 or disabled counter without start condition, otherwise for every synchronous TPDO (transmission type 00h, 01h..F0h and FCh) wait for the matching counter and count it as the first SYNC: an acyclic TPDO goes out on it at the earliest, and RTR sampling (FCh) starts with it (§7.5.2.37 exempts no transmission type). Write protection of the start value with a valid PDO and of 1019h with 1006h non-zero. Loading and reset via the same OD path. | Must | CanOpenPdoEngineTests: Sync_Start, Sync_Producer_Counts, Sync_Parameters, Description_Loads_Sync_Timing. | CiA 301 v4.2.0 §7.2.5, §7.5.2.22/37; user assignment PDO extensions |
| FR-CO-053 | The system MUST evaluate 1007h as the synchronous window in µs (0 = off): discard synchronous RPDOs outside it; discard synchronous TPDOs after expiry or after a newer SYNC before hand-over to the driver. A frame already handed to the driver is not revocable. | Must | CanOpenPdoEngineTests.Sync_Window_Discards_Late_Rpdos_And_Reopens_On_The_Next_Sync; virtual clock. | CiA 301 §7.5.2.7; chosen optional discard policy |
| FR-CO-054 | The system MUST offer with 1400h:05 an RPDO deadline in ms (0 = off): only a valid reception starts it, further valid receptions renew it; expiry reports RpdoTimeout once to the application and, with an active EMCY producer, EMCY 8250h. Reconfiguration and leaving Operational end it. | Must | CanOpenPdoEngineTests.Rpdo_Deadline_Starts_On_Reception_Refreshes_And_Stops_With_Nmt; virtual clock. | CiA 301 §7.5.2.35; user assignment PDO extensions |
| FR-CO-055 | The system MUST support bit-granular PDO mapping up to a total of 64 bits, including one-bit BOOLEAN, dummy 0001h and byte boundaries; fixed scalars keep their declared width. The OD types and the EDS/DCF loader thereby also support integers of 24/40/48/56 bits. Optionally mappable communication objects (1001h/1002h/1013h:00, 1026h:01–03, 1200h–12FFh:01–02) are admitted with the PDOMapping flag set; direction and access rights apply there too. SRDOs keep their own byte-alignment rule. | Must | CanOpenPdoEngineTests.Bit_Mapping_Packs_And_Unpacks_Across_Byte_Boundaries, Native_Integer_Widths_Are_Mapped_Without_Padding; CanOpenForeignPdoObserveTests.Observe_Extended_Bit_Mapping_And_Mpdo_From_The_File. | CiA 301 §7.5.2.36/38; user assignment PDO extensions |
| FR-CO-056 | The system MUST enable 1..512 PDOs per direction (default 4, EDS/DCF raises to the highest declared PDO). PDOs above 4 require an explicit COB-ID. Bit 29 selects extended frames; base and extended frames of the same numeric ID are routed separately. | Must | CanOpenPdoEngineTests.Additional_Pdos_And_Extended_Ids_Route_Independently; CanOpenDeviceDescriptionTests.Degradations_Are_Corrected_In_The_Dictionary_And_Reported. | CiA 301 §7.5.2.35–38; user assignment PDO extensions |
| FR-CO-057 | The system MUST send and receive SAM and DAM MPDOs (mapping count FEh/FFh), event-driven only, with at most one SAM producer. Scanner and dispatcher lists including sub-index blocks, addressed and broadcast DAM reception and EMCY 8230h on an unavailable DAM target are implemented. PDO observation delivers the transferred multiplexer and four data bytes. | Must | CanOpenPdoEngineTests.Destination_Mpdo_Addresses_Local_Objects_And_Reports_Unavailable_Ones, Source_Mpdo_Uses_Scanner_And_Dispatcher_And_Has_One_Producer; CanOpenForeignPdoObserveTests.Observe_Extended_Bit_Mapping_And_Mpdo_From_The_File. | CiA 301 §7.2.3, §7.5.2.39/40; user assignment PDO extensions |
| FR-CO-058 | As SRD per CiA 319 v2.0.0 §5.3 step 4 the system MUST, on the write of A5h to 13FEh — via SDO, locally or from a device description —, recalculate the checksum of every existing SRDO (sub1 ≠ 0 — also a reserved value 3..255, which only a corruption leaves behind, counts as existing and fails the checksum; a deleted one has no configuration to protect, and §9.5 does not check it, as FR-CO-044) over the record as it stands in the dictionary — all mapping slots raw, the inverted ones included, not synthesised from the plain slots (SrdoRecords.RecordChecksum; the same computation applies to FR-CO-038) —, compare it with 13FFh:n and on a deviation set 13FEh to 00h at once, in the same write, so that the configuration entity's readback (FR-CO-051, step 4) sees it. Per §5.4 the same check MUST run after Reset Node and Reset Communication over the restored values before the boot-up is sent; the values themselves are restored unchanged (FR-CO-035), only 13FEh is cleared on a deviation. The check on the transition to OPERATIONAL (FR-CO-038) remains. |
Must | Virtual-loopback integration test (CanOpenSafetyNodeTests.Writing_A5h_Over_A_Wrong_Checksum_Sets_13FEh_Back_To_Zero_At_Once: deviation → 00h, matching again → A5h, a deleted SRDO is not compared; A_Stored_Configuration_That_Does_Not_Match_Its_Checksum_Comes_Back_Invalid: a record changed behind the dictionary — the refresh time, an inverted mapping slot, or the direction to a reserved value — is stored with A5h, restored and 13FEh cleared in the reset pass; CanOpenSafetyDeviceDescriptionTests.A_Wrong_Checksum_In_The_File_Loads_Without_A_Finding_But_The_Configuration_Is_Not_Valid: the file as power-on state). |
CiA 319 v2.0.0 §5.3 step 4, §5.4; #308 |
| FR-CO-059 | The system MUST, with CanOpenNodeOptions.SrdoConfigurationCheckInterval (default null = off; a value ≤ 0 → ArgumentOutOfRangeException), repeat the check of FR-CO-058 periodically as long as 13FEh = A5h (CiA 319 v2.0.0 §5.3: "the SRD should validate the consistency of its safety-related configuration by periodically recalculating the CRC" — a "should", and the norm names no interval, hence opt-in); additionally it MUST compare the checksum-relevant objects of the SRDOs existing at the time (record, mapping, 13FFh:n; a deleted SRDO has nothing to protect and is not included) byte-wise with the image taken under the write gate on the A5h write or after the reset — every write to one of them clears 13FEh, so every deviation from it while A5h stands arose behind the dictionary, also a direction that now reads 0 or reserved; the engine's view is not consulted, because a regular deletion with commit may pre-empt it. A deviation sets 13FEh to 00h and MUST in OPERATIONAL invalidate every SRDO as on the transition (FR-CO-038) — ConfigurationInvalid, or no longer present (reason null) if its record reads as deleted — and end the send period as leaving OPERATIONAL does (FR-CO-039): neither a waiting pair nor one already queued on the send chain nor the inverted half of a running one goes out; a consumer reports invalid. CanOpenNodeOptions.With keeps the interval, WithSrdoConfigurationCheckInterval replaces it (null = off). The norm provides no notification of the configuration entity; SrdoStateChanged reports the transition. A reset does not end the check, dispose does. |
Must | Virtual-loopback integration test with manual clock (CanOpenSafetyNodeTests.The_Periodic_Check_Finds_A_Corrupted_Record_And_Stops_The_Producer): a check without finding changes nothing; a record changed behind the dictionary — refresh time, direction 0, direction 3 — is found on the next one, 13FEh = 00h, ConfigurationInvalid, no further pair; A_Pending_Pair_Is_Dropped_When_The_Periodic_Check_Invalidates_The_Srdo: one waiting behind an unconfirmed pair no longer goes out after the invalidation, nor does the inverted half of the running one; A_Pair_Already_Queued_Behind_A_Held_One_Is_Dropped_When_The_Periodic_Check_Invalidates: an already queued pair of a second SRDO likewise; A_Delete_Followed_By_A_Commit_Survives_The_Periodic_Check: delete plus commit is no deviation; A_Change_Behind_The_Dictionary_In_A_Deleted_Srdos_Record_Is_Not_A_Mismatch: the record of a deleted SRDO is not in the image; A_Mismatch_Found_Outside_Operational_Clears_13FEh_And_Leaves_The_Engine_To_The_Transition: outside OPERATIONAL only 13FEh; SafetyTypesTests.The_Check_Interval_Is_Kept_By_With_And_Replaced_By_Its_Own_With. |
CiA 319 v2.0.0 §5.3; interval decision; #308 |
| FR-CO-060 | The system MUST offer optional read-only peer Safety configuration acquisition compatible with existing Safety implementations. A bound EDS/DCF limits and validates uploads; an expected configuration is optional. The immutable snapshot retains raw entries, provenance, identity, acquisition interval, independent validity/signature/expected outcomes, repeated-read changes and unconfirmed atomic consistency. Partial/fallback data cannot silently become a complete verified image. No downloads or state-changing commands are sent. Monitoring eligibility is explicit; replacing a snapshot begins a new evaluation period. Existing CRC, mapping and CiA 319 SRVT rules apply, with #289 limitations visible. | Must | Deterministic controlled-SDO tests: complete/partial/invalid/changing records, expectation, fallback, channel isolation, cancellation, immutability and upload-only traffic; source mutations. | #403 |
| FR-CO-061 | The system MUST continuously monitor foreign SRDOs and GFCs receive-only from prepared immutable configuration, without a local Operational node, SDO traffic, OD writes or other transmissions. Pairing, inversion, format, length, SRVT/SCT, startup, recovery, configuration replacement and current values are explicit. Host capture timestamps separate assessment from UI delivery latency; insufficient timing and known capture gaps invalidate pending pairs and current values. Capture quality remains separate from protocol status. Each GFC occurrence retains bus/time without sender attribution or safe-state claims. Provenance and historical values cannot silently become current data or safety approval. | Must | Controlled clock and timestamped individual frames: interleaving, pair/format errors, deadline boundaries, gaps, recovery, replacement, repeated GFC, disposal, zero TX; source mutations. | #401 |
| FR-CO-062 | The system MUST, with CanOpenNodeOptions.ErrorHistoryCapacity = n > 0 (0..254, default 0) or a device description that declares 1003h (which raises n to its highest entry, at least 1), keep object 1003h as a managed object: sub0 rw = count, starting at 0; sub1..n ro. Every EMCY the node transmits MUST be entered at sub1 in the transmitting step of the EMCY chain, older entries move up, a full history loses the oldest: error code in the low, the first two manufacturer-specific bytes of the EMCY in the high 16 bits. Not entered are 0000h, received EMCYs and EMCYs that do not reach the bus (1014h bit 31, cancelled, replaced in Stopped). 0 on sub0 MUST empty the history, any other value MUST give 0609 0030h; an upload of an entry above the count MUST end with 0800 0024h. 1010h does not store it, 1011h does not restore it; an NMT reset leaves it (maintainer decision on #434, a deliberate departure from §7.3.2.2.1). With n = 0, 1003h stays the application's. |
Must | Virtual-loopback integration test (CanOpenErrorHistoryTests.cs) |
CiA 301 §7.5.2.4, §7.2.7.1 Figure 37; #434 |
4.3.3 J1939 (application)¶
As-is state (2026-09-15): shipped as CanKit.Pro.J1939, built on CanKit.Pro.J1939Tp.
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| FR-J1939-001 | The system MUST give the application developer the possibility to send/receive messages by PGN and thereby correctly encode/decode priority, PDU format (PF), PDU specific (PS) and source address into the 29-bit ID per J1939-21. | Must | Unit test: PGN round trip for PDU1- and PDU2-format messages. | SAE J1939-21 |
| FR-J1939-002 | The system MUST give the application developer the possibility to extract individual SPN values — unsigned or in two's complement — from a received PGN payload per configurable scaling/offset. The indicator ranges per SAE J1939-71 §5.1.1 (not available, error, reserved, parameter-specific) MUST thereby be reported as such and MUST NOT be output as a physical measured value. | Must | Unit test: SPN extraction with known scaling factors yields the expected physical values; known-answer tests per indicator range and at its boundaries for 1-, 2- and 4-byte fields as well as for sub-byte and signed SPNs. | SAE J1939-71 §5.1.1, §5.1.3 |
| FR-J1939-003 | The system MUST support the address-claiming procedure (sending/receiving the address-claim frame PGN 0xEE00, conflict resolution by NAME priority). | Must | Integration test: two simulated nodes with a colliding address, correct conflict resolution by NAME. | SAE J1939-81 |
| FR-J1939-004 | The system MUST signal cannot-claim-address behaviour (no available address slot) per specification. | Must | Integration test: all addresses occupied, the node reports cannot-claim correctly. | SAE J1939-81 |
| FR-J1939-005 | The system MUST be able to send and receive Request PGN (PGN 0xEA00) to request a specific PGN from one or all network participants. | Must | Integration test: Request PGN triggers the expected response PGN. | SAE J1939-21 |
| FR-J1939-006 | The system MUST automatically use J1939-TP (FR-TP-030ff.) for PGN messages > 8 bytes and the direct single-frame path for ≤8 bytes. | Must | Integration test: a PGN with a 20-byte payload uses TP.BAM/TP.CM, a PGN with a 6-byte payload uses a single frame. | SAE J1939-21 |
| FR-J1939-007 | The system SHOULD support periodic transmission of PGNs at the specification's standard rate (use of IPeriodicTx/L2 scheduling). |
Should | Integration test: a configured PGN is sent at the correct period. | SAE J1939-71; IPeriodicTx |
5. Non-functional Requirements¶
| ID | Requirement | Priority | Verification | Source |
|---|---|---|---|---|
| NFR-001 | Periodic transmission (L1 IPeriodicTx/L2 scheduling) MUST keep jitter on Windows and Linux within a documented tolerance band (target value to be set per project, e.g. < 1 ms with software timing). |
Must | Performance test: timestamp measurement of N periodic transmissions, jitter histogram. | NFR requirement "real-time/jitter" |
| NFR-002 | On macOS the system MUST NOT execute a busy loop in the software timing path; when clock_nanosleep is unavailable it MUST switch to a Thread.Sleep fallback (correction Review §2.3). |
Must | Unit/integration test on a macOS runner: CPU utilisation of an active periodic sender stays in the normal range, no send storm. | Review §2.3 (SoftwarePeriodicTx, swallowed EntryPointNotFoundException) |
| NFR-003 | ISO-TP STmin compliance MUST be kept within a documented accuracy (e.g. ±1 ms or adapter resolution). | Must | Integration test: measured inter-frame time of consecutive frames vs. configured STmin. | ISO 15765-2 |
| NFR-004 | The system MUST deliver functionally equivalent behaviour for L2/L3 core logic on all declared target frameworks (netstandard2.0, net10.0) (no platform-specific logic errors like the inverted TryPeek polyfill). |
Must | CI test matrix across both TFMs (net10.0; netstandard2.0 executed as net48 in the Windows leg) with identical test results for L2/L3 core scenarios. |
Review §1.1 item 11; CON-001 |
| NFR-005 | Vendor-adapter-dependent L0/L1 functionality (Kvaser, PCAN, Vector, ControlCAN) MUST remain restricted to its respectively supported platforms (usually Windows) and must deliver a clear error instead of an unspecified crash on unsupported platforms. | Must | Build-matrix/smoke test per platform. | CON-002 |
| NFR-006 | Error states (bus-off, timeout, connection abort) MUST be communicated consistently in L2/L3/L4 via structured exceptions/result types, not by silently lost exceptions or exceptions thrown on the wrong thread (correction Review §1.1 item 15, §2.2). | Must | Unit tests per error path; regression test against the "exception in the wrong context" findings. | Review §1.1 item 15; §2.2 |
| NFR-007 | Resource-critical paths (frame construction/sending in L2/L3 under high bus load) SHOULD minimise allocations (pooling per ownership contract, no ArrayPool.Rent without return). |
Should | Benchmark: allocation measurement (bytes/frame) for the ISO-TP SF/CF path before/after implementation. | Review §1.1 item 13 |
| NFR-008 | The system MUST guarantee thread safety for all L2 components used simultaneously by several protocol instances (demultiplex hub, registry, scheduler). | Must | Stress test with parallel registrations/unregistrations and simultaneous RX/TX traffic without data races. | Review §1.1 item 14; §2.5 (CanRegistry without lock) |
| NFR-009 | All L2/L3/L4 components MUST be testable against the existing Virtual adapter (loopback, without real hardware) to enable CI-capable regression tests. | Must | CI run: the complete L2/L3/L4 test suite runs without hardware dependency against CanKit.Adapter.Virtual. |
Assignment; existing fake-native pattern (review, overall assessment) |
| NFR-010 | New protocol layers (L3/L4) MUST be shipped as a standalone package that opens via a static factory on an already opened abstraction of the layer directly below — ICanBus/ICanBusService or a transport channel such as IIsoTpChannel (pattern: IsoTp.Open, UdsClient.Create, CanOpen.OpenNode, J1939Node.Open), requires no change to CanKit.Core and references no vendor SDK, to secure maintainability and loose coupling to the core. Registration via [CanRegistryEntry] remains the mechanism of the adapters (L0) and is not demanded for L3/L4. |
Must | Architecture review: the new transport/protocol package is its own project under src/, is opened via a static factory on the abstraction of the layer directly below, references no vendor SDK and does not change CanKit.Core. |
Shipped L3/L4 packages (arc42 §4, §8.8); #226 |
| NFR-011 | Public APIs of new layers (L2/L3/L4) SHOULD use consistent naming from the start (no typos like Excpetions, ReadTImeOutMs, ExceptionOccured from the existing code), since later corrections would be breaking changes. |
Should | API review/linting checklist before the 1.0 release. | Review §3, recommendation item 8 |
| NFR-012 | Timestamps in newly created L2/L3/L4 data structures SHOULD uniformly use UTC (correction of the DateTime.Now vs. DateTime.UtcNow inconsistency in L1). |
Should | Code review checklist; unit test for DateTimeKind.Utc. |
Review §2.5 |
6. Constraints (CON-xxx)¶
| ID | Constraint | Kind | Source |
|---|---|---|---|
| CON-001 | All L2/L3 packages MUST support at least netstandard2.0 and net10.0 as target frameworks (per src/Directory.Build.props). |
technical | src/Directory.Build.props |
| CON-002 | Vendor adapter integrations (PCAN, Kvaser, Vector, ControlCAN) are based on P/Invoke against proprietary native SDKs/DLLs; L2/L3 components must not reference these dependencies directly but use them exclusively via the ICanBus/ITransceiver abstractions. |
technical | Review §1.1 item 16 (negative example: ISO-TP references Peak.PCANBasic.NET for no reason) |
| CON-003 | The licence terms of the vendor SDKs (Peak PCANBasic, Kvaser CANlib, Vector XL driver) are proprietary; distribution/use of these SDKs is subject to third-party licences outside this project's control. | legal | CanKit.Adapter.PCAN.csproj (Peak.PCANBasic.NET reference) |
| CON-004 | New packages (L3/L4) MUST, as long as they are functionally incomplete, be clearly marked as experimental or excluded from the release pipeline (IsPackable=false), analogous to the review recommendation for the then ISO-TP prototype (today shipped as CanKit.Pro.IsoTp). |
organisational | Review §1.1, recommendation item 1 |
| CON-005 | The project uses MIT licensing (PackageLicenseExpression in Directory.Build.props); new L3/L4 packages MUST carry the same licence unless a deviating contractual arrangement stands against it. |
legal/organisational | src/Directory.Build.props |
| CON-006 | The CI builds and tests the whole solution in one workflow (.github/workflows/ci.yml, without .slnf filter files); new L3/L4 packages MUST be included in CanKit.Pro.sln and thereby appear in the build and test run of ci.yml. |
organisational | Review §4 ("the ISO-TP project has no workflow of its own"); .github/workflows/ci.yml |
| CON-007 | The default git branch is main; release/package pipelines MUST reference this branch consistently. |
organisational | Review §3, §5 item 9 (branch-trigger error of the predecessor); .github/workflows/*.yml |
7. Acceptance/Verification Strategy¶
| Test kind | Purpose | Applicable to | Prerequisite |
|---|---|---|---|
| Unit test | Isolated check of encoding/decoding logic, state-machine transitions, boundary values (e.g. STmin limits, PGN encoding, NRC mapping). | L2 (ownership, addressing), L3 (frame codec), L4 (service encoding) | No hardware needed; xUnit, existing matrix-test conventions (tests/CanKit.Tests/Matrix/*). |
| Virtual-loopback integration test | End-to-end behaviour via the existing CanKit.Adapter.Virtual without real hardware; standard procedure for CI. |
L2 (demultiplexing, TX confirm, threading), L3 (ISO-TP/J1939-TP round trips), L4 (UDS/CANopen/J1939 sessions against a simulated peer) | Simulated peers (test ECU/node) based on the Virtual adapter. |
| Fake-native test | Adapter behaviour (L0/L1) without real vendor hardware via the *.Fake.cs layer (-c Fake configuration). |
L1 (already established), indirectly L2/L3 where adapter-specific behaviour (echo, timeout) matters. | Existing fake configuration per adapter. |
| Stress/concurrency test | Uncovering data races, deadlocks, leaks under parallel load (several protocol instances, frequent subscribe/unsubscribe cycles). | L2 (threading model, registry), L3 (scheduler) | Repeated execution (N-fold), possibly with race-detection tools. |
| Performance/timing test | Measurement of jitter, throughput, STmin accuracy, idle CPU utilisation. | NFR-001..003, NFR-007 | Reference platforms (Windows, Linux; macOS for NFR-002). |
| HIL test (hardware-in-the-loop) | Validation against real ECUs/bus hardware for selected critical scenarios (e.g. real UDS diagnostic session, real CANopen device). | Sampled for L4 (UDS, CANopen, J1939) before production release. | Real test hardware, outside the standard CI run; see assumption A-5. |
| Architecture/code review | Checking requirements that cannot be tested automatically (documentation, conformance with the extension pattern, naming conventions). | FR-RAW-001, NFR-010, NFR-011 | Checklist, part of the PR review process. |
Basic principle: Every Must requirement MUST be covered by at least a unit or virtual-loopback integration test before the respective layer counts as "done". HIL tests complement this for production-critical L4 scenarios but, because of the hardware dependency, are not part of the standard CI gate. This rule has been checked mechanically since 2026-09-15: eng/verify-requirements-traceability.py reports every Must requirement no test mentions and runs as the CI job requirements traceability on every pull request. Two exemption lists in the script deliberately weaken the gate: WAIVERS names the requirements whose verification per this SRS is not a test (documentation/architecture review, CI matrix), KNOWN_GAPS the known Must requirements not yet backed by a test — a debt list that may only shrink. The job fails only on a new, unrecorded gap; a green run means "no new gap", not "every Must requirement backed". How many are open is stated by the script run — the number is deliberately not stated here so that it cannot go stale.
8. Traceability Matrix¶
Paths with src/core/… or src/adapters/… point at L0/L1 in the upstream repository pkuyo/CanKit (consumed as a NuGet package, tag in eng/Dependencies.props) — in this repository they do not exist. Everything under src/CanKit.Pro.* is local.
References to architecture building blocks use the layer names L0–L4 defined in section 2.1 and the building-block names from docs/architecture/arc42-CanKit.Pro.md, which now exists. The former planned markers are resolved: every row names the package that carries the requirement today.
| Requirement(s) | Architecture building block (L0–L4) | Verification |
|---|---|---|
| FR-RAW-001..005 | L2 – frame ownership contract, built on L1 CanFrame/CanFrameView (src/core/CanKit.Abstractions/API/Can/Definitions/CanFrame.cs) |
Unit test, virtual-loopback integration test |
| FR-RAW-010..015 | L2 – demultiplex hub/subscription manager (implemented in CanKit.Pro.RawCan), built on L1 ICanBus.FrameObserved (src/core/CanKit.Abstractions/API/Can/ICanBus.cs) |
Virtual-loopback integration test, load test |
| FR-RAW-020..024 | L2 – protocol-instance actor/scheduler, shipped as CanKit.Pro.Actor (IProtocolActor/ProtocolActor); L3 and L4 run on it, IsoTpScheduler no longer exists |
Stress/concurrency test |
| FR-RAW-030..034 | L2 – TX confirm abstraction (implemented in CanKit.Pro.RawCan), built on L1 CanFeature.Echo, ITransceiver.Transmit |
Virtual-loopback integration test (with/without echo) |
| FR-RAW-040..041 | L2 – addressing helpers (implemented as the standalone CanKit.Pro.Addressing: CanIdRange, J1939Id/J1939Fields; FR-RAW-041 as CanIdFilter.Overlaps/ICanBusService.FindOverlappingFilterSubscriptions() in CanKit.Pro.RawCan, result per hit as a named FilterOverlap with both subscriptions and the shared ID range) |
Unit test |
| FR-RAW-050..052 | L2 – fault/timeout infrastructure (FR-RAW-050/051 implemented as the standalone CanKit.Pro.Reliability: IDeadlineScheduler/DeadlineScheduler/Deadline as actor-driven deadline primitive whose expiry is actually checked and reported via IProtocolActor.Schedule (FR-RAW-050); BusStateMonitor/BusStateChangedEventArgs/BusStateExtensions for pushed ICanBus.BusState transitions (FR-RAW-051), built on CanKit.Pro.Actor and L1 ICanBus.BusState). FR-RAW-052 (reserved/invalid protocol values) is deliberately not a generic L2 primitive but implemented as the protocol-specific codec task FR-TP-007 in IsoTpFrameCodec.DecodeStMin (Review §1.1 item 6; reserved STmin ranges → 127 ms). |
Unit test, integration test |
| FR-TP-001..020 | L3 – ISO-TP transport, CanKit.Pro.IsoTp (IsoTpChannel, IsoTpFrameCodec, Pci; deadlines from CanKit.Pro.Reliability) |
Unit test (codec/timing), virtual-loopback integration test, HIL sample |
| FR-TP-030..035 | L3 – J1939 transport, shipped as CanKit.Pro.J1939Tp (J1939TpChannel, J1939TpFrames, J1939TpAbortReason) |
Virtual-loopback integration test |
| FR-UDS-001..012 | L4 – UDS client, shipped as CanKit.Pro.Uds (UdsClientImpl on IIsoTpChannel) |
Virtual-loopback integration test, HIL sample |
| FR-CO-001..059 | L4 – CANopen stack, shipped as CanKit.Pro.CANopen (CanOpenNode on L2 demultiplexing; communication profile and PDO engine are derived from the object dictionary, CanOpenNode.CommunicationProfile.cs / CanOpenNode.Pdo.cs; the object dictionary from an EDS/DCF, CanOpenNode.DeviceDescription.cs on EdsDcfNet; the master/tool role in CanOpenNode.PeerSdo.cs, CanOpenNode.ForeignPdo.cs, CanOpenNode.FlyingMaster.cs, CanOpenNode.BootUp.cs and CanOpenDiscovery.cs; CiA 304 as Safety/SrdoEngine on the same actor, CanOpenNode.Safety.cs / CanOpenNode.PeerSafety.cs / CanOpenNode.ForeignSrdo.cs / CanOpenNode.BootUp.cs; CiA 305 LSS master, LSS slave and commissioning in Lss/ and CanOpenNode.Lss.cs) |
Virtual-loopback integration test, unit test with virtual clock, HIL sample |
| FR-J1939-001..007 | L4 – J1939 application layer, shipped as CanKit.Pro.J1939 (J1939NodeImpl) |
Virtual-loopback integration test, HIL sample |
| NFR-001..003 | L2/L3 timing infrastructure, L1 SoftwarePeriodicTx/PreciseDelay (src/core/CanKit.Core/Utils/SoftwarePeriodicTx.cs) |
Performance/timing test |
| NFR-004, CON-001 | All layers – multi-targeting (src/Directory.Build.props) |
CI test matrix |
| NFR-005, CON-002, CON-003 | L0 – vendor adapters (src/adapters/*) |
Build matrix, code review |
| NFR-006 | L2/L3/L4 – error forwarding, built on L1 ICanBus.FaultOccurred/BackgroundExceptionOccurred |
Unit test per error path |
| NFR-007 | L2/L3 – memory/pooling strategy (built on FR-RAW-001..005) | Benchmark |
| NFR-008 | L2 – registry/scheduler synchronisation, L1 CanRegistry (src/core/CanKit.Core/Registry/CanRegistry.cs) |
Stress test |
| NFR-009 | All layers – test infrastructure, L0 CanKit.Adapter.Virtual, fake-native pattern |
CI run |
| NFR-010 | L2/L3/L4 – factory pattern of the protocol packages on the respective layer below (IsoTp.Open, UdsClient.Create, CanOpen.OpenNode, J1939Node.Open); the registry (L1 CanRegistryEntryAttribute) remains the mechanism of the L0 adapters |
Architecture review |
| NFR-011, NFR-012 | All new layers – API naming conventions | API review |
| CON-004..007 | Release/CI process (eng/, .github/workflows) |
Process review |
Appendix: Open Items for the Next Iteration¶
- Concrete target values for jitter/throughput (NFR-001) are to be set per project (currently marked as placeholders) – depending on the target applications (diagnostics vs. control real-time).
- Done (2026-09-15). All packages specified in 4.2.2/4.3.2/4.3.3 exist and are published on nuget.org; the as-is rows and the traceability matrix are updated accordingly.
- Done (2026-09-15).
docs/architecture/arc42-CanKit.Pro.mdexists; the planned markers in section 8 are resolved. - Partly done (2026-09-21): the scoping against the norms. For CANopen the device role is decided (
docs/reviews/2026-09-15-canopen-scope.md, 2026-09-16) and translated in 4.3.2 as FR-CO-013..028; the master/tool role was decided on 26 Sept (docs/reviews/2026-09-26-canopen-master-tool-scope.md, #131), its decision questions were answered by 28 Sept, and it stands in 4.3.2 as FR-CO-029..034. CANopen Safety (CiA DSP 304 V1.0) was decided on 2026-10-09 (docs/reviews/2026-10-09-canopen-safety-scope.md) and stands as FR-CO-035..046. The LSS master per CiA 305 v3.0.0 stands as FR-CO-047, the LSS slave in the node as FR-CO-048, commissioning as FR-CO-049, automatic bit-rate detection per CiA 801 as FR-CO-050. The configuration entity per CiA 319 v2.0.0 stands since 2026-10-17 as FR-CO-051. ISO 14229-1 and the remaining rounds fromdocs/reviews/2026-09-15-norm-gap.mdremain open. - Open: HIL. Section 7 demands HIL samples for L4 before production release. They have never run. Until that is decided, the virtual-loopback test is the actual gate — and this reservation is the difference between what is written here and what is checked. Since 2026-10-22 there is an instrument for the CANopen half of it,
tools/CanKit.Pro.CanOpenConformance, which checks a device against CiA 301/305 and against its own EDS/DCF on PEAK, Kvaser and Vector hardware and writes up what it measured, what it could not, and whether the runner, the device, the bus or the invocation is the reason. It changes nothing about this item: no run against real hardware is recorded, and a tool that exists is not a measurement. What has run is its--simulatemode against the stack itself, which is a check of the runner and not evidence about any device. - Done (2026-09-21). The EDS/DCF path of the CANopen scope — items 1, 2, 13 and the EDS half of item 26 of the scope document 2026-09-15 — is built with
EdsDcfNet1.13.0 and specified in 4.3.2 as FR-CO-025..028 (#132). The requirements were written together with the path, not before it, so thateng/verify-requirements-traceability.pynever saw aMustrow without implementation.