Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

CanKit.Pro.IsoTp

ISO 15765-2 (ISO-TP) implementation for CanKit (CanKit.Pro). The package now ships two halves:

  1. Codec — deterministic, side-effect-free builders and parsers for the four ISO-TP PCI frame types (Single Frame, First Frame, Consecutive Frame, Flow Control) on classic CAN and CAN-FD.
  2. Runtime (IIsoTpChannel) — an actor-driven channel that composes on top of the CanKit.Pro L2 services (CanKit.Pro.RawCan demux + SendConfirmedAsync, CanKit.Pro.Actor, CanKit.Pro.Reliability deadlines). Segments outbound PDUs into SF/FF/CFs, honors peer Flow Control (BS/STmin/Wait/ Overflow) and enforces N_As/N_Bs/N_Cr timers, reassembles inbound PDUs (SN-checked), and delivers them via ReceiveAsync / ReceiveAllAsync / DatagramReceived.

Status: 1.3.0 is the first stable release: from 1.3.0 on the public API follows SemVer, so a breaking change costs a major version. 1.0.0 – 1.2.3 were published as stable before the API had been reviewed against the specifications; they are unlisted and deprecated on nuget.org and should not be used. See Versioning. CAN-FD long-payload cases still get the least coverage of the two halves.

What is validated, and what is not

Validated: The frame codec (unit and property tests), and the channel over CanKit.Adapter.Virtual, directly or through a controllable bus double: segmentation, Flow Control (BS, STmin, Wait, Overflow), the N_As/N_Bs/N_Cr timers, reassembly, functional addressing and CAN-FD, by the test suite in tests/CanKit.Pro.Tests. STmin pacing is tested on a clock the test drives, so the interval is checked exactly and no real elapsed time is measured.

Not validated: Conformance to ISO 15765-2 as a tester or a foreign ISO-TP stack would judge it, and STmin spacing in real time, on any host or adapter, where it carries scheduling and adapter latency. Nothing in this package has run against real CAN hardware, a conformance tester or a third-party implementation: the test project references CanKit.Adapter.Virtual and no hardware adapter.

Scope

  • IsoTpFrameCodec — bounds-safe PCI parser, BuildSingleFrame / BuildFirstFrame / BuildConsecutiveFrame / BuildFlowControl, correct classic-CAN vs CAN-FD DLC/capacity, correct First-Frame length encoding including the 32-bit CAN-FD escape form (lengths > 4095), and bounds-checked PCI parsing that never throws IndexOutOfRangeException on short frames.
  • IsoTpFrameCodec.EncodeStMin / DecodeStMin — full ISO 15765-2 STmin range including the commonly-used 0 ms and 1 ms values (Encode) and the reserved bands 0x80..0xF0 and 0xFA..0xFF which decode to 127 ms (0x7F) instead of throwing.
  • IsoTpFrameCodec.NextConsecutiveSequenceNumber — Consecutive-Frame sequence numbering that starts at 1 after the First Frame and wraps 0..15.
  • Pci / PciType / FlowStatus — parsed Protocol-Control-Information view.
  • IsoTpEndpoint / IsoTpAddressingMode — minimal addressing value type covering Normal, NormalFixed, Extended and Mixed addressing for codec purposes only.

Runtime — IIsoTpChannel

  • IsoTp.Open(ICanBus, IsoTpEndpoint, IsoTpChannelOptions?) — opens a channel that owns a private CanBusService around the supplied bus.
  • IsoTp.Open(ICanBusService, IsoTpEndpoint, IsoTpChannelOptions?, leaveOpen) — opens a channel on an existing service (allows multiple ISO-TP endpoints to multiplex over the same physical bus, SRS FR-TP-018).
  • SendAsync(ReadOnlyMemory<byte>, CancellationToken) — sends one PDU; task completes on TX-confirm of the last frame. SendWithTransmitStampAsync returns IsoTpTransmitStamps, bracketing the driver call for the last frame as the bus service reports it from inside its send lock: just before the handoff (a caller's cutoff for what can still be a response to this PDU — a peer answers only a complete request) and no later than the driver accepted it (where a response deadline starts); the two are not interchangeable (#146). Faults with IsoTpTimeoutException, IsoTpOverflowException, IsoTpWaitFrameLimitExceededException, or IsoTpSendRejectedException on the corresponding ISO 15765-2 error cases.
  • ReceiveAsync / ReceiveAllAsync — two surfaces onto the same bounded, drop-oldest PDU inbox (bounded to IsoTpChannelOptions.ReceiveBufferCapacity, default 64), in arrival order. DatagramReceived is not one of them: each completed PDU is raised on its own thread-pool work item, so handlers may run concurrently and out of order; nothing is raised for a frame handled after Dispose has begun, but a handler already queued may still run. ReceiveWithArrivalAsync returns an IsoTpReceivedPdu stamped with the arrival of its last frame and of its first (FirstFrameArrivalTimestamp), because an application deadline such as UDS P2 ends with the first frame; GetReceptionsInProgress reports the multi-frame receptions that have begun but not completed, oldest first, each as an IsoTpReceptionInProgress — that stamp, the announced length and the First Frame's data bytes, so the application layer can tell whose response it is. A record is published when the First Frame is taken from the subscription, before the channel's actor has processed it (several can be pending while the actor is behind), and withdrawn on its outcome or if the actor refuses the frame; the call itself drains what the demux has buffered first, so the answer does not wait for the reader task's scheduling either. A caller waiting on one re-checks rather than waiting unboundedly. DiscardPendingPdus drains the demux buffer the same way before clearing, so a frame buffered at discard time is part of what it drops. SettleAsync drains it the same way and completes once the actor has taken everything queued so far, without dropping anything: for a decision taken at a deadline, what the inbox does not hold after it did not arrive before the call — a Single Frame stamped in time can otherwise still be on its way when the deadline fires. A CancellationToken ends the wait, not the settling: what the demux buffered has been handed to the actor by then. DiscardPendingPdus(long) drops what arrived before the caller's own stamp rather than before now, so a caller that reads the inbox after taking the stamp and discards after reading has seen everything it drops, and a frame from between the read and the discard is kept.
  • Timings: IsoTpChannelOptions.NAs (TX-confirm), NBs (peer-FC wait), NCr (next CF wait) and WftMax (max consecutive Wait FCs) are configurable; defaults are conservative 1 s / 10.
  • Reception limits: a First Frame announcing more than MaxReceivePduLength (default 65 535 bytes; classic CAN is bounded at 4095 by the codec anyway) is answered with FC(OVFLW) and nothing is allocated for it, so a CAN-FD escape First Frame cannot make the process reserve the ~2 GB it may announce. A Consecutive Frame whose CAN_DL is not the First Frame's, unless it is the last one, is ignored as ISO 15765-2 §9.8 requires — it is not copied short. A First Frame announcing no more than a Single Frame of the same CAN_DL could carry is ignored too (§9.6.3.1): no Flow Control, no reassembly (#56).
  • Flow parameters: the sender takes BS and STmin from the first FC.CTS of a transfer and ignores the values a later FC.CTS carries, as ISO 15765-2 requires (#56).
  • Echoes: a channel receives on RxCanId and transmits on TxCanId, so on an echo-capable bus its own frames never match its filter and host echoes are kept — two reciprocal channels in one process are peers. The one exception is an endpoint with TxCanId == RxCanId and no address-extension byte telling the directions apart (Normal addressing, or Mixed with its one shared byte): there the host echo flag is the only thing telling the channel's frame from the peer's, and host echoes are withheld (#56). Extended addressing on one identifier keeps them, the source and target bytes doing the telling.
  • DiscardPendingPdus may be called from a BackgroundExceptionOccurred handler, which runs on the channel's actor: the clear then runs inline instead of waiting on the loop it is on (#56).

Timing accuracy — STmin pacing (NFR-003)

The sender paces Consecutive Frames by the peer's advertised STmin using the L2 DeadlineScheduler (actor-driven, event-based — no busy wait). On general-purpose operating systems the effective CF spacing is STmin + OS scheduling latency, with no real-time guarantee, and nothing in this repository measures how large that latency is on any host. Sub-millisecond STmin values (0xF1..0xF9, 100–900 µs) are honored as-is but bottom out at the platform timer resolution.

What the suite does verify is the logic, not the wall clock: tests/CanKit.Pro.Tests/TestCases/IsoTp/IsoTpStminTimingTests.cs advances a clock the test drives and checks that the sender releases exactly one Consecutive Frame per STmin interval. The requirement NFR-003 asks for a documented accuracy (for example ±1 ms); that figure is a target, not a measured result.

Functional (1:N) addressing — IsoTpFunctionalClient (FR-TP-019)

Per ISO 15765-2 §9 / ISO 14229-1 §7.5.4, a tester can broadcast a request to all ECUs on the bus using a shared functional CAN identifier and collect Single-Frame responses from multiple ECUs.

// Open a functional client (owns its own CanBusService).
using var client = IsoTp.OpenFunctional(
    bus,
    functionalTxCanId:          0x7DF,
    responseRxCanIdRangeStart:  0x7E8,
    responseRxCanIdRangeEnd:    0x7EF);

// Broadcast a UDS DiagnosticSessionControl(DefaultSession) request and collect all replies
// received within 25 ms.
IReadOnlyList<IsoTpFunctionalResponse> responses = await client.SendAndCollectAsync(
    pdu: new byte[] { 0x10, 0x01 },
    window: TimeSpan.FromMilliseconds(25));

foreach (var r in responses)
    Console.WriteLine($"ECU 0x{r.SourceCanId:X3}: {BitConverter.ToString(r.Data)}");
  • IsoTp.OpenFunctional(ICanBus, …) — client owns a private CanBusService.
  • IsoTp.OpenFunctional(ICanBusService, …, leaveOpen) — shares an existing service with physical IIsoTpChannel instances (disjoint ID ranges required, FR-TP-018).
  • SF-only send (ISO 15765-2 §9.4 restriction): a PDU that exceeds the Single-Frame capacity for the configured frame kind faults the task with InvalidOperationException.
  • SF-only collection: First-Frame responses (which would require per-ECU physical Flow-Control addresses) are silently dropped; Single-Frame responses from all ECUs within the range are collected in arrival order.
  • IsoTpFunctionalOptions configures IsExtendedCanId, UseCanFd, UsePadding, PaddingByte, and NAs (TX-confirm timeout).
  • When the request went out: SendWithTransmitStampAsync and SendAndCollectWithTransmitStampAsync return IsoTpTransmitStamps — the instant the frame was handed to the driver and the instant it was transmitted — for a caller that keeps a deadline from the transmission. A response that arrived before the handoff answers something else (the subscription is made before the send, and another sender may hold the service's transmit lock in between) and is left out of the collection.
  • Listening across collections: CollectResponsesAsync subscribes per call, so a response that arrives between two calls — or between a SendAsync and the first call — is missed. client.Listen() subscribes once and returns an IsoTpFunctionalListener whose CollectAsync(window) collects from that standing subscription: obtained before the send, it hears the fastest reply, and what arrives between two collections is buffered for the next. Dispose it to end the subscription.

Non-scope (yet)

  • Multi-frame (FF/CF) response reassembly in functional sessions (requires caller to supply a per-ECU physical TX address for Flow-Control replies).
  • No vendor-SDK references, ever.
  • The legacy CanKit.Transport.IsoTp prototype (and its Abstractions API/Transport surface plus the PCAN native ISO-TP register) has been removed; this package is the sole ISO-TP implementation path.

Fixes over the prototype (see review §1.1)

The codec is the specification-compliant replacement for the removed legacy prototype codec and deliberately avoids the following defects:

  1. Inverted CAN vs CAN-FD frame kind — this codec is agnostic; it returns the frame payload bytes plus the intended CAN kind, callers construct the CAN frame with the correct kind (FR-TP-003).
  2. Flow-Control frames now carry PCI type 0x3 (not the First-Frame nibble) (FR-TP-004).
  3. Padding is applied after the BS/STmin bytes and never overwrites them (FR-TP-004).
  4. First-Frame length high-nibble is composed with correct operator precedence (((data[0] & 0x0F) << 8) | data[1]), so lengths in [256, 4095] round-trip (FR-TP-005).
  5. EncodeStMin accepts the commonly-used 0 ms and 1 ms values (FR-TP-006).
  6. DecodeStMin maps the reserved raw values 0x80..0xF0 and 0xFA..0xFF to 127 ms (0x7F) per ISO 15765-2 instead of throwing (FR-TP-007, FR-RAW-052).
  7. Consecutive-Frame sequence numbers start at 1 and wrap 0..15 (FR-TP-008).
  8. TryParsePci is bounds-safe and never throws IndexOutOfRangeException, even for a 1-byte frame or a truncated Flow-Control frame (FR-TP-007).
  9. Classic-CAN single frames are always ≤ 8 bytes (FR-TP-015).
  10. BuildSingleFrame rejects a zero-length payload at build time: ISO 15765-2 does not define a Single Frame with SF_DL == 0, so producing such a frame would yield bytes no conformant peer could parse (bugbot 3594958440).
  11. TryParsePci requires an isCanFd argument so the Single-Frame escape header (0x00 LEN …) and the First-Frame escape header (0x10 0x00 LEN[4] …) are only accepted on CAN-FD frames; on classic CAN those bit-patterns are invalid and are rejected instead of being mis-parsed as escape headers (bugbot 3594958440 / 3594958445).

Status: codec plus runtime channel, both shipped. See the note at the top of this file on the withdrawn 1.0.0 – 1.2.3 releases.

Install

dotnet add package CanKit.Pro.IsoTp

# plus a CanKit adapter for the hardware you actually talk to, e.g.
dotnet add package CanKit.Adapter.Virtual   # loopback, no hardware

Dependencies: CanKit.Abstractions, CanKit.Pro.Actor, CanKit.Pro.RawCan, CanKit.Pro.Reliability.

Part of CanKit.Pro — higher CAN protocol layers built on top of CanKit, which is consumed as a NuGet package rather than forked.

License

MIT — see LICENSE. CanKit itself is a separate project licensed under Apache-2.0; see THIRD-PARTY-NOTICES.md.