-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathICanBusService.cs
More file actions
180 lines (173 loc) · 11.1 KB
/
Copy pathICanBusService.cs
File metadata and controls
180 lines (173 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CanKit.Abstractions.API.Can;
using CanKit.Abstractions.API.Can.Definitions;
using CanKit.Abstractions.API.Common.Definitions;
namespace CanKit.Pro.RawCan
{
/// <summary>
/// One demultiplexing service instance per <see cref="ICanBus"/>: it turns the single RX
/// stream exposed by <see cref="ICanBus.FrameObserved"/> into N independent, filtered
/// read-only <see cref="ISubscription"/>s, so multiple protocol instances (ISO-TP, J1939,
/// CANopen, …) can each see their own view of the bus without competing over
/// <see cref="ICanBus.ReceiveAsync"/> (arc42 §5.3, ADR-5; FR-RAW-010..013).
/// </summary>
/// <remarks>
/// The service is built purely on top of the public <see cref="ICanBus.FrameObserved"/>
/// surface (a read-only <see cref="CanFrameView"/> per frame, with no disposal/ownership
/// concerns), so it works identically for every adapter with no per-adapter changes.
/// Disposing the service unwinds all outstanding subscriptions and detaches its handler from
/// the underlying bus (FR-RAW-012). Dispose is idempotent.
/// <para>
/// <b>Echoes.</b> When the bus is configured for echo (<c>WorkMode == ChannelWorkMode.Echo</c>)
/// it reports the host's own transmissions back through the same RX stream, flagged as such.
/// A subscription receives them only if it asked for them, and the flag rides along on every
/// delivered <see cref="CanFrameEvent"/> either way. The default is off because the frames a
/// protocol layer sends are not frames it received: a J1939 node that treats its own Address
/// Claim as a competitor's, or a CANopen node that acts on its own PDO, is broken in a way
/// that only shows up on hardware that happens to echo. Before this flag existed each of those
/// layers each carried their own answer to "is this mine?" — comparing NAMEs, comparing source
/// addresses, guarding on actor affinity. Those checks are still there and still needed; what
/// changed is that the flag and the timestamp are now available to a caller who wants them.
/// </para>
/// <para>
/// <b>Two limits, both of which make the gate a convenience rather than a guarantee.</b>
/// First, it can only drop what the adapter flags: an adapter that echoes without setting
/// <see cref="CanFrameEvent.IsEcho"/> — <c>CanKit.Adapter.Virtual</c> in
/// <c>ChannelWorkMode.Echo</c> does — delivers its echo to every subscription regardless of
/// <c>includeEcho</c>. Second, and more important, the flag is <b>host-scoped</b>: it says
/// something on this host transmitted the frame, not which of the possibly several protocol
/// instances sharing this service did. A sibling instance's traffic is flagged identically
/// to one's own.
/// </para>
/// <para>
/// So <c>includeEcho: false</c> suits a single consumer that owns its bus. A protocol layer
/// that may share a service — every one in this repository does, by documented design — asks
/// for echoes instead, and must then tell its own traffic apart by something it actually
/// owns. That is not a workaround for a missing feature: the demux genuinely cannot attribute
/// a transmission to a local instance.
/// </para>
/// <para>
/// How far each layer takes that is a per-layer decision, not a guarantee this interface
/// makes. The J1939 transport rejects its own source address; the J1939 node recognises an
/// Address Claim it transmitted when the bus echoes one, and treats another CA's claim — the
/// same NAME included — as a contest; it rejects its own source address on an application
/// PGN. CANopen rejects its own node id on an EMCY or a heartbeat.
/// </para>
/// <para>
/// None is a blanket self-filter, and each exception is deliberate. A J1939 frame addressed to
/// the node itself is still delivered, so a request against one's own address is answered.
/// CANopen still delivers NMT, SYNC, both SDO directions and RPDOs from its own producer — a
/// node acts on its own SYNC, and an RPDO's COB-ID is whatever the application configured —
/// and still feeds a heartbeat or node-guarding consumer registered for the local node id. The
/// shared rule is that an explicitly configured or explicitly addressed frame outranks a guess
/// about who sent it.
/// </para>
/// </remarks>
public interface ICanBusService : IDisposable
{
/// <summary>
/// The underlying bus this service demultiplexes.
/// </summary>
ICanBus Bus { get; }
/// <summary>
/// Number of currently registered (not yet disposed) subscriptions. Primarily for
/// diagnostics/tests: after disposing every subscription it returns to zero, proving no
/// registry entries leak (FR-RAW-012).
/// </summary>
int SubscriptionCount { get; }
/// <summary>
/// Raised when a caller-supplied subscription filter predicate throws during dispatch
/// (FR-RAW-023-style fault channel). The failing frame is isolated to that subscription
/// (delivery to the other subscriptions continues), and the exception is surfaced here
/// instead of being silently swallowed. Invoked synchronously on the bus's dispatch
/// thread, so handlers must return quickly and must not call back into the service.
/// </summary>
event EventHandler<Exception>? BackgroundExceptionOccurred;
/// <summary>
/// Registers a subscription that receives every frame for which
/// <paramref name="predicate"/> returns true; a null predicate accepts all frames
/// (FR-RAW-010).
/// </summary>
/// <param name="predicate">
/// Per-frame filter, or null to accept all frames. Runs on the bus's dispatch thread
/// before the payload is copied, so the <see cref="CanFrameEvent.Frame"/> it inspects
/// aliases the adapter's RX lease and must not be retained beyond the call. It is never
/// offered an echo unless <paramref name="includeEcho"/> is set.
/// </param>
/// <param name="bufferCapacity">
/// Bounded buffer capacity for this subscription; null uses
/// <see cref="CanBusService.DefaultBufferCapacity"/>. When the buffer is full the oldest
/// buffered frame is dropped so dispatch never blocks (FR-RAW-011).
/// </param>
/// <param name="includeEcho">
/// Whether this subscription also receives the local host's own transmit echoes. Defaults
/// to <c>false</c>: a protocol layer that sees its own transmissions come back as if they
/// were peer traffic misbehaves in ways that are tedious to diagnose, so opting in is a
/// decision the caller makes deliberately (see the remarks below).
/// </param>
ISubscription Subscribe(Func<CanFrameEvent, bool>? predicate = null, int? bufferCapacity = null, bool includeEcho = false);
/// <summary>
/// Registers a subscription using the allocation-free ID-range/mask fast path
/// (FR-RAW-010/013).
/// </summary>
/// <param name="filter">ID-range or acceptance-code/mask filter.</param>
/// <param name="bufferCapacity">
/// Bounded buffer capacity for this subscription; null uses
/// <see cref="CanBusService.DefaultBufferCapacity"/>.
/// </param>
/// <param name="includeEcho">
/// Whether this subscription also receives the local host's own transmit echoes; false by
/// default. See the predicate overload above.
/// </param>
ISubscription Subscribe(CanIdFilter filter, int? bufferCapacity = null, bool includeEcho = false);
/// <summary>
/// Diagnostic: finds every pair of currently registered, still-undisposed
/// <see cref="CanIdFilter"/>-based subscriptions whose ID spaces overlap, and the range of
/// CAN IDs each pair shares (FR-RAW-041, "Should") -- helps catch misconfiguration when
/// multiple protocol instances were meant to have disjoint ID ranges but don't.
/// Subscriptions registered via the generic
/// <see cref="Subscribe(Func{CanFrameEvent,bool}, int?, bool)"/> predicate overload are opaque
/// and are not analyzable, so they are skipped.
/// </summary>
/// <returns>
/// One <see cref="FilterOverlap"/> per overlapping pair, each naming the two subscriptions
/// and the ID range on which they collide. Empty when no two filters share ID space.
/// </returns>
IReadOnlyList<FilterOverlap> FindOverlappingFilterSubscriptions();
/// <summary>
/// Sends <paramref name="frame"/> and asynchronously confirms it was actually sent, using
/// a uniform abstraction regardless of whether the underlying bus has hardware TX echo
/// enabled (arc42 §6.3, ADR-7; FR-RAW-030). When the bus both declares
/// <see cref="CanFeature.Echo"/> and has <c>WorkMode == ChannelWorkMode.Echo</c> configured,
/// confirmation comes from an actually-matched echo frame (FR-RAW-031, including correct
/// FIFO matching of multiple concurrent byte-identical sends — no cross-matching or crash);
/// otherwise it is a documented approximation based on driver acceptance
/// (<see cref="TxConfirmation.IsApproximated"/>, FR-RAW-032). Never hangs: timeout,
/// bus-off, and outright rejection all resolve the returned task within bounded time
/// (FR-RAW-033) — see <see cref="TxConfirmation"/> for exactly how.
/// </summary>
/// <param name="frame">
/// The frame to send. As with <see cref="ICanBus.Transmit(in CanFrame)"/>, the caller
/// remains the owner (TX-lease) and is responsible for disposing it after this call
/// returns/completes — <see cref="ICanBusService"/> never disposes it.
/// </param>
/// <param name="timeout">
/// Maximum time to wait before failing with <see cref="TxConfirmFailureReason.Timeout"/>
/// (FR-RAW-034); null uses <see cref="CanBusService.DefaultConfirmTimeout"/>. On the echo
/// path it bounds the wait for the echo; on the approximated path it bounds the wait for
/// the driver to accept the frame (<see cref="ICanBus.TransmitAsync(CanFrame, System.Threading.CancellationToken)"/>),
/// which every current adapter completes immediately, so it only bites for a bus whose
/// asynchronous transmit stalls. A frame reported as timed out may still reach the wire
/// later (the bus is asked to cancel the transmit, but a driver that ignores that cannot
/// be stopped). Must be positive.
/// </param>
/// <param name="cancellationToken">
/// Caller-supplied cancellation; cancels the returned task per standard .NET convention,
/// distinct from the domain-level <see cref="TxConfirmFailureReason.Timeout"/> outcome.
/// </param>
Task<TxConfirmation> SendConfirmedAsync(CanFrame frame, TimeSpan? timeout = null, CancellationToken cancellationToken = default);
}
}