Skip to main content

dope_session/protocol/
server.rs

1use crate::{CodecLayer, SlotGen, SlotId};
2use std::pin::Pin;
3
4#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5pub enum RouteRequestBodyKind {
6    Inline,
7    Spilled,
8    Stream,
9}
10
11/// Server-side protocol.
12///
13/// One trait owns parsing AND handling — there is no separate `Framer` trait
14/// or `framer()` accessor. Per-connection parsing state lives in
15/// [`ConnState`]; shared protocol state (rate limiters, metrics, etc.) lives
16/// in `&mut self`.
17///
18/// `Head<'buf>` is a GAT so parsers can return zero-copy views into `buf`.
19///
20/// [`ConnState`]: ServerProtocol::ConnState
21pub trait ServerProtocol: 'static {
22    type Head<'buf>;
23
24    type ConnState: Default + 'static;
25
26    type CodecLayer: CodecLayer;
27
28    const SUPPORTS_PARK: bool = false;
29
30    /// Try to parse one frame's head from `buf`. Returns
31    /// `(head, head_len, body_len)` on success — frame ring waits for
32    /// `head_len + body_len` bytes total before dispatching to `handle`.
33    /// `body_len = 0` means a head-only frame.
34    fn parse<'buf>(
35        &self,
36        state: &mut Self::ConnState,
37        buf: &'buf [u8],
38    ) -> Option<(Self::Head<'buf>, u32, u32)>;
39
40    fn handle<'buf>(
41        &mut self,
42        req: &'buf [u8],
43        head: Self::Head<'buf>,
44        write: &mut [u8],
45    ) -> HandlerAction;
46
47    fn handle_park<'buf>(
48        &mut self,
49        conn_id: SlotId,
50        conn_state: &mut Self::ConnState,
51        req: &'buf [u8],
52        head: Self::Head<'buf>,
53        write: &mut [u8],
54    ) -> ParkAction {
55        let _ = (conn_id, conn_state, req, head, write);
56        unreachable!("ServerProtocol::handle_park called with SUPPORTS_PARK = true but no override")
57    }
58
59    fn oversize_response(&self) -> &'static [u8] {
60        b""
61    }
62
63    fn route_request_body_kind(&self, head: &Self::Head<'_>) -> RouteRequestBodyKind {
64        let _ = head;
65        RouteRequestBodyKind::Inline
66    }
67
68    fn handle_request_stream<'buf>(
69        &mut self,
70        head_bytes: &'buf [u8],
71        head: Self::Head<'buf>,
72        body_stream: RequestBodyStream,
73        write: &mut [u8],
74    ) -> RequestStreamAction {
75        let _ = (head_bytes, head, body_stream, write);
76        unreachable!(
77            "ServerProtocol::handle_request_stream called without an override; \
78             a route returned RouteRequestBodyKind::Stream but the \
79             handler does not implement the stream dispatch path"
80        )
81    }
82
83    fn on_register(
84        &mut self,
85        conn_id: SlotId,
86        conn_state: &mut Self::ConnState,
87    ) -> Option<Pin<Box<dyn std::future::Future<Output = ()> + 'static>>> {
88        let _ = (conn_id, conn_state);
89        None
90    }
91
92    fn drain_outbound(&mut self, conn_state: &mut Self::ConnState, write: &mut [u8]) -> u32 {
93        let _ = (conn_state, write);
94        0
95    }
96
97    fn close_pending(&self, conn_state: &Self::ConnState) -> bool {
98        let _ = conn_state;
99        false
100    }
101}
102
103pub enum ParkAction {
104    Send { written: u16, close_after: bool },
105    Park,
106    Close(&'static [u8]),
107}
108
109pub enum RequestStreamAction {
110    Pending(dambi::InlineFuture<'static, PendingResponse, 64>),
111    Close(&'static [u8]),
112}
113
114pub enum HandlerAction {
115    Send {
116        written: u16,
117        close_after: bool,
118    },
119    SendStatic {
120        hdr_written: u32,
121        body: &'static [u8],
122        close_after: bool,
123    },
124
125    SendVectored {
126        iovs: [dope_core::IoVec; 4],
127        iov_count: u8,
128        cookie: *const u8,
129        total_bytes: u32,
130        close_after: bool,
131    },
132    SendStream {
133        hdr_written: u32,
134        stream: Pin<Box<dyn ResponseChunkStream>>,
135        close_after: bool,
136    },
137    Pending(dambi::InlineFuture<'static, PendingResponse, 64>),
138    Close(&'static [u8]),
139}
140
141pub trait ResponseChunkStream: 'static {
142    fn poll_chunk(
143        self: Pin<&mut Self>,
144        cx: &mut std::task::Context<'_>,
145    ) -> std::task::Poll<Option<dambi::Bytes>>;
146}
147
148pub struct RequestBodyStream {
149    inner: std::rc::Rc<std::cell::RefCell<RequestStreamInner>>,
150}
151
152#[derive(Default)]
153pub(crate) struct RequestStreamInner {
154    pub(crate) pending: std::collections::VecDeque<dambi::Bytes>,
155    pub(crate) waker: Option<std::task::Waker>,
156    pub(crate) received: u32,
157    pub(crate) target: u32,
158    pub(crate) eof: bool,
159}
160
161impl RequestBodyStream {
162    pub(crate) fn new(target: u32) -> (Self, RequestStreamHandle) {
163        let inner = std::rc::Rc::new(std::cell::RefCell::new(RequestStreamInner {
164            pending: std::collections::VecDeque::new(),
165            waker: None,
166            received: 0,
167            target,
168            eof: target == 0,
169        }));
170        (
171            Self {
172                inner: inner.clone(),
173            },
174            RequestStreamHandle { inner },
175        )
176    }
177
178    pub fn poll_chunk(
179        &mut self,
180        cx: &mut std::task::Context<'_>,
181    ) -> std::task::Poll<Option<dambi::Bytes>> {
182        let mut state = self.inner.borrow_mut();
183        if let Some(chunk) = state.pending.pop_front() {
184            return std::task::Poll::Ready(Some(chunk));
185        }
186        if state.eof {
187            return std::task::Poll::Ready(None);
188        }
189        state.waker = Some(cx.waker().clone());
190        std::task::Poll::Pending
191    }
192}
193
194pub(crate) struct RequestStreamHandle {
195    pub(crate) inner: std::rc::Rc<std::cell::RefCell<RequestStreamInner>>,
196}
197
198const REQUEST_STREAM_QUEUE_CAP: usize = 8 * 1024 * 1024;
199
200impl RequestStreamHandle {
201    pub(crate) fn push_slice(&self, src: &[u8]) -> usize {
202        let mut state = self.inner.borrow_mut();
203        if src.is_empty() {
204            return 0;
205        }
206        let queued: usize = state.pending.iter().map(|b| b.len()).sum();
207        if queued >= REQUEST_STREAM_QUEUE_CAP {
208            return 0;
209        }
210        let needed = (state.target.saturating_sub(state.received)) as usize;
211        let take = src.len().min(needed);
212        if take > 0 {
213            state
214                .pending
215                .push_back(dambi::Bytes::copy_from_slice(&src[..take]));
216            state.received = state.received.saturating_add(take as u32);
217        }
218        if state.received >= state.target {
219            state.eof = true;
220        }
221        if let Some(waker) = state.waker.take() {
222            drop(state);
223            waker.wake();
224        }
225        take
226    }
227
228    pub(crate) fn close(&self) {
229        let mut state = self.inner.borrow_mut();
230        state.eof = true;
231        if let Some(waker) = state.waker.take() {
232            drop(state);
233            waker.wake();
234        }
235    }
236
237    pub(crate) fn is_complete(&self) -> bool {
238        let state = self.inner.borrow();
239        state.eof
240    }
241}
242
243type ResponseSerializer = Box<dyn FnOnce(&mut [u8]) -> PendingOutcome + 'static>;
244
245pub struct PendingResponse {
246    pub serializer: ResponseSerializer,
247}
248
249pub enum PendingOutcome {
250    Inline {
251        written: u16,
252        close_after: bool,
253    },
254    Static {
255        hdr_written: u32,
256        body: &'static [u8],
257        close_after: bool,
258    },
259    Stream {
260        hdr_written: u32,
261        stream: Pin<Box<dyn ResponseChunkStream>>,
262        close_after: bool,
263    },
264}
265
266pub(crate) struct PendingFuture {
267    pub(crate) generation: SlotGen,
268    pub(crate) future: dambi::InlineFuture<'static, PendingResponse, 64>,
269}
270
271pub(crate) struct LongFuture {
272    pub(crate) generation: SlotGen,
273    pub(crate) future: Pin<Box<dyn std::future::Future<Output = ()> + 'static>>,
274}