Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
cf24dc0
fix(rawcan): stop an expired pending send from poisoning the echo FIFO
claude Sep 12, 2026
458ff08
perf(rawcan): copy each payload once per frame, and stop copying echo…
claude Sep 12, 2026
45e10c7
fix(rawcan)!: reject CanIdFilter ranges and masks outside their ID space
claude Sep 12, 2026
11cd32c
feat(rawcan): give the callback Subscribe an onError channel, and sto…
claude Sep 12, 2026
44628e9
test(rawcan): fail the build if CanKit ever claims the borrowed 6002.…
claude Sep 12, 2026
46db7cb
feat(rawcan)!: replace the overlap tuple pair with a named FilterOver…
claude Sep 12, 2026
1bba7d7
docs(rawcan): record why SendConfirmed keeps Transmit inside the pend…
claude Sep 12, 2026
3e9a8b2
Merge branch 'main' into fix/rawcan-tx-confirm
claude Sep 12, 2026
c7efac1
Merge remote-tracking branch 'origin/main' into fix/rawcan-tx-confirm
claude Sep 13, 2026
e2a8a09
test(rawcan): pin the callback Subscribe argument guards, and the hig…
claude Sep 13, 2026
2a87013
fix(rawcan): stop a cancellation from waiting on an unrelated send's …
claude Sep 13, 2026
a37a20a
test(rawcan): cover the pump's own failure path, which Codecov was ri…
claude Sep 13, 2026
d213e00
test(rawcan): make the stream-failure test deterministic and its disp…
claude Sep 13, 2026
f75b40f
fix(rawcan): claim a pending send by completing it, not by asking whe…
claude Sep 13, 2026
3a0cedd
test(rawcan): cover the two branches Codecov flagged instead of argui…
claude Sep 13, 2026
83b7c59
test(rawcan): state the foreign-object operands as object so CodeQL r…
claude Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions docs/architecture/arc42-CanKit.Pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ flowchart TB
| Multi-Protokoll-Demux | (2) | Ein RX-Strom → N unabhängige gefilterte Consumer, **ohne** konkurrierendes `ReceiveAsync`. **Umgesetzt** im neuen Paket `CanKit.Pro.RawCan` (`ICanBusService`/`CanBusService` + `ISubscription`): je Subscription ein eigener bounded Drop-Oldest-Channel (FR-RAW-011), Fast-Path `CanIdFilter` (ID-Range/Maske) neben generischem `Func<CanFrameEvent,bool>` (FR-RAW-010/013), deterministisches Dispose (FR-RAW-012). Das gelieferte Element ist `CanFrameEvent` = Frame + `IsEcho` + Empfangszeitstempel; Echos werden nur an Subscriptions ausgeliefert, die sie mit `includeEcho: true` angefordert haben (FR-RAW-015, [#23](https://github.com/dborgards/CanKit.Pro/issues/23)). Baut ausschließlich auf `ICanBus.FrameObserved`, kein Adapter-Eingriff. | `ICanBusService.Subscribe(filter, includeEcho) → ISubscription { IAsyncEnumerable<CanFrameEvent> Frames; }` | `FR-RAW-010..013`, `FR-RAW-015` |
| Frame-Ownership-Vertrag | (1) | Verbindliche Lease-Regeln (siehe 8.1); verhindert Use-after-free/Double-Dispose. **Kernmechanik umgesetzt** (`OwnMemory`-Fix, `CanFrame.Duplicate`, Virtual-Hub-Broadcast per Kopie); ausstehend: TX-Lease für übrige L0-Adapter/ISO-TP-Scheduler. | Vertragsdoku + `OwnMemory`-Fix (Review §1.5) | `FR-RAW-OWN-*` |
| TX-Confirm | (4) | Einheitliche „gesendet"-Bestätigung, egal ob Hardware-Echo vorhanden. **Umgesetzt** in `CanKit.Pro.RawCan` (`ICanBusService.SendConfirmed`): FIFO-Echo-Matching je (ID, Payload) für gleichzeitige inhaltsgleiche Sendevorgänge (FR-RAW-031), dokumentierte Treiber-Akzeptanz-Approximation ohne Echo (FR-RAW-032), beobachtbare Fehlschläge statt Hängen bei Timeout/BusOff/Ablehnung (FR-RAW-033), konfigurierbarer Timeout je Aufruf (FR-RAW-034). | `TxConfirmation { Confirmed; Timestamp; IsApproximated; FailureReason; }` | `FR-RAW-030..034` |
| Adressierungs-Helfer | – | 11/29-bit, Extended/Mixed/NormalFixed (bislang nur als Einzelfall in `IsoTpEndpoint` vorhanden). **Umgesetzt** als eigenständiges, abhängigkeitsfreies Paket `CanKit.Pro.Addressing`: validierte 11-/29-Bit-ID-Prüfung (`CanIdRange`), allgemeine J1939-PGN/Priorität/PDU-Format/Quelladresse-Komposition/-Dekomposition (`J1939Id`/`J1939Fields`, FR-RAW-040) — verallgemeinert die zuvor auf eine feste Diagnose-PGN beschränkte 29-Bit-Konstruktion aus `IsoTpEndpoint.CreateNormalFixed`. Zusätzlich `CanIdFilter.Overlaps` sowie `ICanBusService.FindOverlappingFilterSubscriptions()` in `CanKit.Pro.RawCan` zur Erkennung überlappender Subscription-Filter (FR-RAW-041, Should). | ID-Bau/-Zerlegung, PGN/Prio-Helfer | `FR-RAW-ADDR-*` |
| Adressierungs-Helfer | – | 11/29-bit, Extended/Mixed/NormalFixed (bislang nur als Einzelfall in `IsoTpEndpoint` vorhanden). **Umgesetzt** als eigenständiges, abhängigkeitsfreies Paket `CanKit.Pro.Addressing`: validierte 11-/29-Bit-ID-Prüfung (`CanIdRange`), allgemeine J1939-PGN/Priorität/PDU-Format/Quelladresse-Komposition/-Dekomposition (`J1939Id`/`J1939Fields`, FR-RAW-040) — verallgemeinert die zuvor auf eine feste Diagnose-PGN beschränkte 29-Bit-Konstruktion aus `IsoTpEndpoint.CreateNormalFixed`. Zusätzlich `CanIdFilter.Overlaps` sowie `ICanBusService.FindOverlappingFilterSubscriptions()` in `CanKit.Pro.RawCan` zur Erkennung überlappender Subscription-Filter (FR-RAW-041, Should); jeder Treffer wird als benannter `FilterOverlap` (beide Subscriptions plus geteilter ID-Bereich) gemeldet. | ID-Bau/-Zerlegung, PGN/Prio-Helfer | `FR-RAW-ADDR-*` |
| Aktor-/Threading-Modell | (3) | Genau ein Bearbeitungs-Thread/Mailbox pro Protokollinstanz; kein geteilter mutabler State. **Umgesetzt** als eigenständiges, abhängigkeitsfreies Paket `CanKit.Pro.Actor` (siehe ADR-6): ereignisgetriebener Loop (kein Busy-Loop, FR-RAW-022), je Instanz wählbarer Ausführungskontext (`ActorExecutionMode`: `DedicatedThread`/`ThreadPool`/`SynchronizationContext`, FR-RAW-024), `BackgroundExceptionOccurred` als einziger Kanal für Hintergrundfehler (FR-RAW-023). Vom ISO-TP-Prototyp noch nicht genutzt. | `IProtocolActor { Post(msg); PostAsync(msg); Schedule(delay, cb); }` | `FR-RAW-ACTOR-*` |
| Fehler-/Timeout-Infrastruktur | – | Einheitliche Deadline-Verwaltung (ersetzt verstreute ISO-TP-`Deadline`s) und gepushte Bus-Fehlerzustände. **Umgesetzt** als eigenständiges Paket `CanKit.Pro.Reliability` (siehe ADR-11), aufbauend auf `CanKit.Pro.Actor`: `IDeadlineScheduler`/`DeadlineScheduler`/`Deadline` ist eine wiederverwendbare Deadline-Primitive, deren Ablauf über `IProtocolActor.Schedule` auf dem Aktor-Loop tatsächlich eingeplant, geprüft und gemeldet wird — behebt die Klasse „Deadlines werden gepflegt, aber nie geprüft" (Review §1.1 Punkt 10, FR-RAW-050); die Pending→{Expired\|Completed\|Cancelled}-Auflösung ist per `Interlocked`-CAS genau einmal entscheidbar, Ausnahmen aus `onExpired` laufen über den bestehenden `BackgroundExceptionOccurred`-Kanal (kein zweiter Fehlerkanal). `BusStateMonitor`/`BusStateChangedEventArgs`/`BusStateExtensions` pusht `ICanBus.BusState`-Übergänge (ErrWarning/ErrPassive/BusOff sowie Erholung) an Protokollinstanzen — zuverlässig über einen selbst-rearmenden Poll auf dem Aktor-`Schedule` (Standard 50 ms) statt eines freilaufenden Timers, ergänzt um `ErrorFrameReceived`/`FaultOccurred` als Latenz-Hinweise (FR-RAW-051). FR-RAW-052 (reservierte/ungültige Protokollwerte) ist bewusst **zurückgestellt** und dem ISO-TP-Codec-Fix FR-TP-007 zugeordnet, nicht als generische L2-Primitive gebaut. | `IDeadlineScheduler`, `DeadlineScheduler`/`Deadline`, `BusStateMonitor`, `BusStateExtensions` | `FR-RAW-050..051` |

Expand Down Expand Up @@ -1154,11 +1154,14 @@ STmin-Grenzwerte, SN-Folge, N_Bs/N_Cr-Timeouts gegen Virtual.
Umbau bestehender Adapter/Transporte in dieser Umsetzung). Zusätzlich `CanIdFilter.Overlaps`
und `ICanBusService.FindOverlappingFilterSubscriptions()` in `CanKit.Pro.RawCan` (FR-RAW-041,
Should): erkennt überlappende Range/Mask-Filter unter den aktuell registrierten Subscriptions
als Fehldiagnose-Hilfe bei falsch konfigurierten Protokollinstanzen — Range/Range und
Mask/Mask-Überlappung über direkte Intervall-/Bitvergleiche, Range/Mask-Überlappung über eine
bitweise Existenzsuche (O(Bitbreite), kein Aufzählen einzelner ID-Werte). Abgesichert per
Unit-Test (`tests/CanKit.Tests/TestCases/AddressingTests.cs`,
`tests/CanKit.Tests/TestCases/CanIdFilterOverlapTests.cs`).
als Fehldiagnose-Hilfe bei falsch konfigurierten Protokollinstanzen. Jeder Treffer ist ein
`FilterOverlap` — die beiden Subscriptions (symmetrisch, daher `A`/`B` statt `First`/`Second`)
und der geteilte ID-Bereich, bei Mask-Filtern als einschließende Hülle einer gestreuten Menge.
Alle Kombinationen laufen über dieselbe bitweise Suche (O(Bitbreite), kein Aufzählen einzelner
ID-Werte), die den kleinsten bzw. größten gemeinsamen ID direkt mitliefert, statt nur dessen
Existenz. Abgesichert per Unit-Test (`tests/CanKit.Tests/TestCases/AddressingTests.cs`,
`tests/CanKit.Tests/TestCases/CanIdFilterOverlapTests.cs`, inkl. Abgleich gegen eine
Brute-Force-Absuche des gesamten 11-Bit-ID-Raums).

### ADR-11 (umgesetzt): Fehler-/Timeout-Infrastruktur als eigenständiges Paket
- **Kontext:** L3-Protokolle brauchen zeitgebundene Zustandsübergänge (ISO-TP N_Bs/N_Cr, J1939-,
Expand Down
10 changes: 8 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,16 @@ CANopen node on a flagging adapter does see its own PDOs and heartbeats.
If two instances were meant to have disjoint ID spaces, you can check rather than hope:

```csharp
foreach (var (first, second) in service.FindOverlappingFilterSubscriptions())
logger.Warning("Overlapping subscriptions: {A} and {B}", first, second);
foreach (var overlap in service.FindOverlappingFilterSubscriptions())
logger.Warning("Overlapping subscriptions {A} and {B}, sharing IDs 0x{Low:X}..0x{High:X}",
overlap.A, overlap.B, overlap.LowestSharedId, overlap.HighestSharedId);
```

Each result is a `FilterOverlap`: the two subscriptions that share ID space — the relation is
symmetric, so `A` and `B` say nothing beyond registration order — and the range they share. For two
range filters every ID in between is shared as well; for acceptance-code/mask filters the bounds are
a hull around a scattered set. If you only want the pair, it destructures: `var (a, b) = overlap;`.

## Did the frame actually go out?

`ICanBus.Transmit` tells you the driver accepted the frame, which is not the same thing. Where the
Expand Down
2 changes: 1 addition & 1 deletion docs/requirements/SRS-CanKit.Pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ Verweise auf Architektur-Bausteine nutzen die in Abschnitt 2.1 definierten Schic
| FR-RAW-010..015 | L2 – *Demultiplex-Hub/Subscription-Manager* (umgesetzt in `CanKit.Pro.RawCan`), aufbauend auf L1 `ICanBus.FrameObserved` (`src/core/CanKit.Abstractions/API/Can/ICanBus.cs`) | Virtual-Loopback-Integrationstest, Lasttest |
| FR-RAW-020..024 | L2 – *Protokollinstanz-Aktor/Scheduler* (umgesetzt als eigenständiges `CanKit.Pro.Actor`, `IProtocolActor`/`ProtocolActor`); Referenzimplementierung noch **nicht** umgestellt in L3 `IsoTpScheduler` (`src/transports/CanKit.Transport.IsoTp/IsoTpScheduler.cs`, funktional defekt, s. Review §1.1) | Stress-/Nebenläufigkeitstest |
| FR-RAW-030..034 | L2 – *TX-Confirm-Abstraktion* (umgesetzt in `CanKit.Pro.RawCan`), aufbauend auf L1 `CanFeature.Echo`, `ITransceiver.Transmit` | Virtual-Loopback-Integrationstest (mit/ohne Echo) |
| FR-RAW-040..041 | L2 – *Adressierungs-Helfer* (umgesetzt als eigenständiges `CanKit.Pro.Addressing`: `CanIdRange`, `J1939Id`/`J1939Fields`; FR-RAW-041 als `CanIdFilter.Overlaps`/`ICanBusService.FindOverlappingFilterSubscriptions()` in `CanKit.Pro.RawCan`) | Unit-Test |
| FR-RAW-040..041 | L2 – *Adressierungs-Helfer* (umgesetzt als eigenständiges `CanKit.Pro.Addressing`: `CanIdRange`, `J1939Id`/`J1939Fields`; FR-RAW-041 als `CanIdFilter.Overlaps`/`ICanBusService.FindOverlappingFilterSubscriptions()` in `CanKit.Pro.RawCan`, Ergebnis je Treffer als benannter `FilterOverlap` mit beiden Subscriptions und dem geteilten ID-Bereich) | Unit-Test |
| FR-RAW-050..052 | L2 – *Fehler-/Timeout-Infrastruktur* (FR-RAW-050/051 umgesetzt als eigenständiges `CanKit.Pro.Reliability`: `IDeadlineScheduler`/`DeadlineScheduler`/`Deadline` als aktorgetriebene Deadline-Primitive, deren Ablauf über `IProtocolActor.Schedule` tatsächlich geprüft und gemeldet wird (FR-RAW-050); `BusStateMonitor`/`BusStateChangedEventArgs`/`BusStateExtensions` für gepushte `ICanBus.BusState`-Übergänge (FR-RAW-051), aufbauend auf `CanKit.Pro.Actor` und L1 `ICanBus.BusState`). FR-RAW-052 (reservierte/ungültige Protokollwerte) bleibt **zurückgestellt** und dem künftigen ISO-TP-Codec-Fix FR-TP-007 zugeordnet (Review §1.1 Punkt 6), da protokollspezifisch statt generische L2-Primitive. | Unit-Test, Integrationstest |
| FR-TP-001..020 | L3 – ISO-TP-Transport, `CanKit.Transport.IsoTp` (`IsoTpChannelCore`, `IsoTpScheduler`, `FrameCodec`, `Router`, `Deadline`/`QueuedDeadline`) | Unit-Test (Codec/Timing), Virtual-Loopback-Integrationstest, HIL-Stichprobe |
| FR-TP-030..035 | L3 – *J1939-Transport* (geplant, neues Paket `CanKit.Transport.J1939` analog `IIsoTpRegister`-Muster) | Virtual-Loopback-Integrationstest |
Expand Down
3 changes: 2 additions & 1 deletion src/CanKit.Pro.Addressing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ J1939Name.CompareClaimPriority(name, sameName); // 0; lower unsigned NAME wins a
`CanKit.Pro.RawCan`'s `CanIdFilter` also gained an `Overlaps(CanIdFilter other)` method and
`ICanBusService.FindOverlappingFilterSubscriptions()` (FR-RAW-041, Should): a diagnostic to catch
misconfigured protocol instances whose ID-range/mask subscriptions were meant to be disjoint but
overlap.
overlap. Each hit comes back as a `FilterOverlap` naming the two subscriptions and the range of CAN
IDs they share, so the report says where the collision is and not only that there is one.

## Install

Expand Down
9 changes: 6 additions & 3 deletions src/CanKit.Pro.IsoTp/IsoTpChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,12 @@ private async Task RunReaderAsync()
.ConfigureAwait(false))
{
var frame = frameEvent.Frame;
// Copy defensively: CanFrameView.Data may reference a reused buffer once we
// hand control back to the subscription, and the RX state machine will keep the
// payload alive across await points via the reassembly buffer.
// Not the hazard the previous comment described: the subscription already hands
// out a payload it owns, so nothing the adapter does can corrupt it. What it hands
// out is one array shared by every subscription that matched the frame, and it is
// a ReadOnlyMemory<byte> while the state machine below wants a byte[] it keeps
// across await points -- so this stays a copy, now as this channel's private
// buffer rather than as protection against the RX lease.
var payload = frame.Data.ToArray();
var addrExt = _endpoint.UsesAddressExtension;
// Endpoint uses an address-extension byte and the first byte does not match:
Expand Down
Loading