Skip to content

Commit 79de87f

Browse files
committed
dev(owned): trying to have a stream and a sink at the same time
Signed-off-by: Jad K. Haddad <jadkhaddad@gmail.com>
1 parent e5d2287 commit 79de87f

8 files changed

Lines changed: 221 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ thiserror = { version = "2.0.12", default-features = false }
1919
httparse = { version = "1.10.1", default-features = false }
2020
base64 = { version = "0.22.1", default-features = false }
2121
sha1 = { version = "0.10.6", default-features = false }
22+
heapless = { version = "0.9.1", default-features = false }
2223

2324
[dev-dependencies]
2425
rand = { version = "0.9.1", features = ["std_rng"] }

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"fuzzingclient",
1717
"fuzzingserver",
1818
"Haddad",
19+
"heapless",
1920
"httparse",
2021
"jadkhaddad",
2122
"linkall",
@@ -48,4 +49,4 @@
4849
"target",
4950
"autobahn/reports"
5051
]
51-
}
52+
}

src/close_frame.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,45 @@ impl<'a> CloseFrame<'a> {
3030
self.reason
3131
}
3232
}
33+
34+
/// An owned WebSocket Close frame.
35+
#[derive(Debug)]
36+
pub struct OwnedCloseFrame<const N: usize> {
37+
/// The reason as a code.
38+
code: CloseCode,
39+
/// The reason as text string.
40+
reason: heapless::String<N>,
41+
}
42+
43+
impl<const N: usize> OwnedCloseFrame<N> {
44+
/// Creates a new [`OwnedCloseFrame`].
45+
pub const fn new(code: CloseCode, reason: heapless::String<N>) -> Self {
46+
Self { code, reason }
47+
}
48+
49+
/// Creates a new [`OwnedCloseFrame`] with no reason.
50+
pub const fn no_reason(code: CloseCode) -> Self {
51+
Self::new(code, heapless::String::new())
52+
}
53+
54+
/// Returns the close code.
55+
pub const fn code(&self) -> CloseCode {
56+
self.code
57+
}
58+
59+
/// Returns the reason as a string slice.
60+
pub fn reason(&self) -> &str {
61+
&self.reason
62+
}
63+
}
64+
65+
impl<const N: usize> TryFrom<CloseFrame<'_>> for OwnedCloseFrame<N> {
66+
type Error = heapless::CapacityError;
67+
68+
fn try_from(value: CloseFrame<'_>) -> Result<Self, Self::Error> {
69+
Ok(Self {
70+
code: value.code(),
71+
reason: heapless::String::try_from(value.reason())?,
72+
})
73+
}
74+
}

src/functions.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use framez::state::{ReadState, WriteState};
33
use rand::RngCore;
44

55
use crate::{
6-
ConnectionState, Frame, Message, OnFrame, WebSocketCore,
6+
ConnectionState, Frame, Message, OnFrame, OwnedMessage, WebSocketCore,
77
codec::FramesCodec,
88
error::{Error, ProtocolError, ReadError, WriteError},
99
websocket_core::FragmentsState,
@@ -57,6 +57,64 @@ impl ReadAutoCaller {
5757
WebSocketCore::<RW, Rng>::on_frame(fragments_state, frame)
5858
.map(|result| result.map_err(Error::from))
5959
}
60+
61+
// TODO: see comments in on WebSocket::next()
62+
// TODO: delete
63+
#[allow(clippy::too_many_arguments)]
64+
pub async fn call_next<const N: usize, F, RW, Rng>(
65+
&self,
66+
auto: F,
67+
websocket: &mut WebSocketCore<'_, RW, Rng>,
68+
) -> Option<Result<Option<Result<OwnedMessage<N>, heapless::CapacityError>>, Error<RW::Error>>>
69+
where
70+
RW: Read + Write,
71+
Rng: RngCore,
72+
F: FnOnce(Frame<'_>) -> Result<OnFrame<'_>, ProtocolError> + 'static,
73+
{
74+
let frame = match framez::functions::maybe_next(
75+
&mut websocket.framed.core.state.read,
76+
&mut websocket.framed.core.codec,
77+
&mut websocket.framed.core.inner,
78+
)
79+
.await
80+
{
81+
Some(Ok(Some(frame))) => frame,
82+
Some(Ok(None)) => return Some(Ok(None)),
83+
Some(Err(err)) => return Some(Err(Error::Read(ReadError::ReadFrame(err)))),
84+
None => return None,
85+
};
86+
87+
let frame = match auto(frame) {
88+
Ok(on_frame) => match on_frame {
89+
OnFrame::Send(message) => {
90+
websocket.state.closed = message.is_close();
91+
92+
match framez::functions::send(
93+
&mut websocket.framed.core.state.write,
94+
&mut websocket.framed.core.codec,
95+
&mut websocket.framed.core.inner,
96+
message,
97+
)
98+
.await
99+
{
100+
Ok(_) => match websocket.state.closed {
101+
false => return Some(Ok(None)),
102+
true => return None,
103+
},
104+
Err(err) => return Some(Err(Error::Write(WriteError::WriteFrame(err)))),
105+
}
106+
}
107+
OnFrame::Noop(frame) => frame,
108+
},
109+
Err(err) => return Some(Err(Error::Read(ReadError::Protocol(err)))),
110+
};
111+
112+
WebSocketCore::<RW, Rng>::on_frame(&mut websocket.fragments_state, frame).map(|result| {
113+
result
114+
.map(|opt| opt.map(OwnedMessage::<N>::try_from))
115+
.map_err(Error::from)
116+
})
117+
}
60118
}
61119

62120
#[derive(Debug)]

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ mod close_code;
348348
pub use close_code::CloseCode;
349349

350350
mod close_frame;
351-
pub use close_frame::CloseFrame;
351+
pub use close_frame::{CloseFrame, OwnedCloseFrame};
352352

353353
mod codec;
354354
use codec::FramesCodec;
@@ -368,7 +368,7 @@ pub mod http;
368368
mod mask;
369369

370370
mod message;
371-
pub use message::Message;
371+
pub use message::{Message, OwnedMessage};
372372

373373
#[doc(hidden)]
374374
pub mod mock;

src/message.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{CloseFrame, Frame, OpCode, error::FragmentationError, fragments::FragmentsIterator};
1+
use crate::{
2+
CloseFrame, Frame, OpCode, OwnedCloseFrame, error::FragmentationError,
3+
fragments::FragmentsIterator,
4+
};
25

36
/// A WebSocket message.
47
#[derive(Debug)]
@@ -122,3 +125,40 @@ impl<'a> Message<'a> {
122125
}
123126
}
124127
}
128+
129+
/// An owned WebSocket message.
130+
#[derive(Debug)]
131+
pub enum OwnedMessage<const N: usize> {
132+
/// A text WebSocket message
133+
Text(heapless::String<N>),
134+
/// A binary WebSocket message
135+
Binary(heapless::Vec<u8, N>),
136+
/// A ping message with the specified payload
137+
///
138+
/// The payload here must have a length less than 125 bytes
139+
Ping(heapless::Vec<u8, N>),
140+
/// A pong message with the specified payload
141+
///
142+
/// The payload here must have a length less than 125 bytes
143+
Pong(heapless::Vec<u8, N>),
144+
/// A close message with the optional close frame.
145+
Close(Option<OwnedCloseFrame<N>>),
146+
}
147+
148+
impl<const N: usize> TryFrom<Message<'_>> for OwnedMessage<N> {
149+
type Error = heapless::CapacityError;
150+
151+
fn try_from(value: Message<'_>) -> Result<Self, Self::Error> {
152+
match value {
153+
Message::Text(payload) => Ok(Self::Text(heapless::String::try_from(payload)?)),
154+
Message::Binary(payload) => Ok(Self::Binary(heapless::Vec::try_from(payload)?)),
155+
Message::Ping(payload) => Ok(Self::Ping(heapless::Vec::try_from(payload)?)),
156+
Message::Pong(payload) => Ok(Self::Pong(heapless::Vec::try_from(payload)?)),
157+
Message::Close(Some(frame)) => Ok(Self::Close(Some(OwnedCloseFrame::new(
158+
frame.code(),
159+
heapless::String::try_from(frame.reason())?,
160+
)))),
161+
Message::Close(None) => Ok(Self::Close(None)),
162+
}
163+
}
164+
}

src/websocket.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::cell::RefCell;
2+
13
use embedded_io_async::{Read, Write};
24
use framez::{
35
Framed,
@@ -6,9 +8,10 @@ use framez::{
68
use rand::RngCore;
79

810
use crate::{
9-
FragmentsState, Frame, FramesCodec, Message, OnFrame, WebSocketCore,
11+
FragmentsState, Frame, FramesCodec, Message, OnFrame, OwnedMessage, WebSocketCore,
1012
error::{Error, ProtocolError},
1113
http::{Request, Response},
14+
next,
1215
options::{AcceptOptions, ConnectOptions},
1316
};
1417

@@ -460,3 +463,41 @@ impl<'buf, RW, Rng> WebSocketWrite<'buf, RW, Rng> {
460463
self.core.send_fragmented(message, fragment_size).await
461464
}
462465
}
466+
467+
#[derive(Debug)]
468+
pub struct OwnedWebSocket<'buf, const N: usize, RW, Rng> {
469+
// XXX: the idea of the refcell is to have a stream and a sink at the same time using the same core
470+
// but this would panic when using select on the stream and then using the sink to send a message, because the stream would have a mutable reference to the core and then the sink would try to borrow it again.
471+
// See clippy warning
472+
// I think we can only have a stream or a sink at a time, mutably borrowing the core
473+
core: RefCell<WebSocket<'buf, RW, Rng>>,
474+
}
475+
476+
impl<'buf, const N: usize, RW, Rng> OwnedWebSocket<'buf, N, RW, Rng>
477+
where
478+
RW: Read + Write,
479+
Rng: RngCore,
480+
{
481+
pub async fn next(
482+
&self,
483+
) -> Option<Result<Result<OwnedMessage<N>, heapless::CapacityError>, Error<RW::Error>>> {
484+
let mut websocket = self.core.borrow_mut();
485+
486+
match 'next: loop {
487+
match websocket
488+
.caller()
489+
.call_next::<N, _, _, _>(websocket.auto(), &mut websocket.core)
490+
.await
491+
{
492+
Some(Ok(None)) => continue 'next,
493+
Some(Ok(Some(item))) => break 'next Some(Ok(item)),
494+
Some(Err(err)) => break 'next Some(Err(err)),
495+
None => break 'next None,
496+
}
497+
} {
498+
None => None,
499+
Some(Ok(msg)) => Some(Ok(msg)),
500+
Some(Err(err)) => Some(Err(err)),
501+
}
502+
}
503+
}

0 commit comments

Comments
 (0)