Skip to main content

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}