ocpp_client/transport.rs
1#[cfg(feature = "websocket")]
2pub(crate) mod websocket;
3
4use alloc::boxed::Box;
5use alloc::string::String;
6use alloc::vec::Vec;
7use core::future::Future;
8use core::pin::Pin;
9
10/// A transport-agnostic boxed error, so `TransportSink`/`TransportStream` stay dyn-safe
11/// regardless of what's underneath (WebSocket today, a framed serial link later).
12pub type TransportError = Box<dyn core::error::Error + Send + Sync>;
13
14/// One thing read off a transport: a complete OCPP-J text frame, or a protocol-level
15/// keepalive event. Carrying ping/pong through the abstraction (rather than hiding it
16/// entirely inside the WebSocket adapter) keeps `send_ping`/`on_ping` possible without the
17/// generic client knowing anything WebSocket-specific.
18///
19/// `Ping`/`Pong` carry the frame's application data, which RFC 6455 ยง5.5.2-3 requires a pong
20/// to echo back from the ping that triggered it. `Client` relies on that echo to match a pong
21/// to the exact `send_ping` waiting for it, so transport implementations must pass the payload
22/// through rather than discarding it.
23#[derive(Debug)]
24pub enum TransportEvent {
25 Frame(String),
26 Ping(Vec<u8>),
27 Pong(Vec<u8>),
28}
29
30/// The write half of a transport: sends one complete OCPP-J text frame at a time.
31///
32/// Implementations own only framing (e.g. WebSocket masking) - `Client` never sees
33/// anything but whole frames and keepalive events.
34///
35/// Methods return a boxed future (the shape `#[async_trait]` expands to, written by hand)
36/// rather than using `async fn` in the trait, so `Box<dyn TransportSink>` stays usable - this
37/// crate has no dependency on the `async-trait` crate itself, only on `alloc`.
38pub trait TransportSink: Send {
39 fn send<'a>(
40 &'a mut self,
41 frame: String,
42 ) -> Pin<Box<dyn Future<Output = Result<(), TransportError>> + Send + 'a>>;
43 /// Send a ping carrying `payload` as its application data. `Client` puts a correlation
44 /// token there and matches it against the echoed payload of the pong that comes back, so
45 /// implementations must transmit it verbatim instead of sending an empty ping.
46 fn ping<'a>(
47 &'a mut self,
48 payload: Vec<u8>,
49 ) -> Pin<Box<dyn Future<Output = Result<(), TransportError>> + Send + 'a>>;
50 /// Send a pong carrying `payload`. When replying to a received ping, RFC 6455 requires
51 /// this to be that ping's application data verbatim - the read loop passes it straight
52 /// through from [`TransportEvent::Ping`].
53 fn pong<'a>(
54 &'a mut self,
55 payload: Vec<u8>,
56 ) -> Pin<Box<dyn Future<Output = Result<(), TransportError>> + Send + 'a>>;
57 fn close<'a>(
58 &'a mut self,
59 ) -> Pin<Box<dyn Future<Output = Result<(), TransportError>> + Send + 'a>>;
60}
61
62/// The read half of a transport: yields one [`TransportEvent`] at a time, or `None` when
63/// the other side closed the connection.
64pub trait TransportStream: Send {
65 /// Yield the next event.
66 ///
67 /// **Must be cancel-safe**: the returned future can be dropped before it completes, and
68 /// doing so must not lose or partially consume an event - the next `recv` call has to pick
69 /// up where this one left off. `Client`'s read loop races this against an internal
70 /// "abandon this connection and redial" signal (fired by keepalive when the peer stops
71 /// answering pings), so a stalled `recv` gets dropped mid-poll rather than parking the loop
72 /// until the OS TCP timeout.
73 ///
74 /// The two implementations in the Flowion tree satisfy this because they bottom out in
75 /// already-cancel-safe primitives (`futures::StreamExt::next` over a `tokio-tungstenite`
76 /// stream; an `embassy-net` socket read). An implementation that buffers partial state in a
77 /// local variable across an `.await` needs to move that state into `self` to qualify.
78 fn recv<'a>(
79 &'a mut self,
80 ) -> Pin<Box<dyn Future<Output = Result<Option<TransportEvent>, TransportError>> + Send + 'a>>;
81}