Skip to main content

memlink_protocol/
zero.rs

1//! Zero-copy message parsing.
2//!
3//! Defines ZeroCopyRequest and ZeroCopyResponse for parsing messages
4//! directly from shared memory without heap allocations.
5
6use alloc::format;
7use alloc::string::ToString;
8
9use crate::error::{ProtocolError, Result};
10use crate::header::MessageHeader;
11use crate::magic::HEADER_SIZE;
12use crate::request::Request;
13use crate::shm::ShmView;
14use crate::types::{Priority, RequestId, TraceId};
15
16#[derive(Debug, Clone)]
17pub struct ZeroCopyRequest<'a> {
18    pub header: MessageHeader,
19    pub module_name: &'a str,
20    pub method_name: &'a str,
21    pub args: &'a [u8],
22    pub trace_id: TraceId,
23    pub deadline_ns: Option<u64>,
24}
25
26impl<'a> ZeroCopyRequest<'a> {
27    pub fn parse(shm: &'a ShmView<'a>) -> Result<Self> {
28        let header = shm.read_header()?;
29
30        let mut offset = HEADER_SIZE;
31        let data = shm.as_slice();
32
33        if offset + 4 > data.len() {
34            return Err(ProtocolError::InvalidHeader(
35                "insufficient data for module_name length".to_string(),
36            ));
37        }
38        let module_len =
39            u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
40                as usize;
41        offset += 4;
42
43        if offset + module_len > data.len() {
44            return Err(ProtocolError::InvalidHeader(
45                "insufficient data for module_name".to_string(),
46            ));
47        }
48        let module_bytes = &data[offset..offset + module_len];
49        let module_name = core::str::from_utf8(module_bytes).map_err(|_| {
50            ProtocolError::InvalidHeader("module_name is not valid UTF-8".to_string())
51        })?;
52        offset += module_len;
53
54        if offset + 4 > data.len() {
55            return Err(ProtocolError::InvalidHeader(
56                "insufficient data for method_name length".to_string(),
57            ));
58        }
59        let method_len =
60            u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
61                as usize;
62        offset += 4;
63
64        if offset + method_len > data.len() {
65            return Err(ProtocolError::InvalidHeader(
66                "insufficient data for method_name".to_string(),
67            ));
68        }
69        let method_bytes = &data[offset..offset + method_len];
70        let method_name = core::str::from_utf8(method_bytes).map_err(|_| {
71            ProtocolError::InvalidHeader("method_name is not valid UTF-8".to_string())
72        })?;
73        offset += method_len;
74
75        if offset + 4 > data.len() {
76            return Err(ProtocolError::InvalidHeader(
77                "insufficient data for args length".to_string(),
78            ));
79        }
80        let args_len =
81            u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
82                as usize;
83        offset += 4;
84
85        if offset + args_len > data.len() {
86            return Err(ProtocolError::InvalidHeader(
87                "insufficient data for args".to_string(),
88            ));
89        }
90        let args = &data[offset..offset + args_len];
91        offset += args_len;
92
93        if offset + 16 > data.len() {
94            return Err(ProtocolError::InvalidHeader(
95                "insufficient data for trace_id".to_string(),
96            ));
97        }
98        let trace_id = u128::from_le_bytes([
99            data[offset],
100            data[offset + 1],
101            data[offset + 2],
102            data[offset + 3],
103            data[offset + 4],
104            data[offset + 5],
105            data[offset + 6],
106            data[offset + 7],
107            data[offset + 8],
108            data[offset + 9],
109            data[offset + 10],
110            data[offset + 11],
111            data[offset + 12],
112            data[offset + 13],
113            data[offset + 14],
114            data[offset + 15],
115        ]);
116        offset += 16;
117
118        if offset + 8 > data.len() {
119            return Err(ProtocolError::InvalidHeader(
120                "insufficient data for deadline_ns".to_string(),
121            ));
122        }
123        let deadline = u64::from_le_bytes([
124            data[offset],
125            data[offset + 1],
126            data[offset + 2],
127            data[offset + 3],
128            data[offset + 4],
129            data[offset + 5],
130            data[offset + 6],
131            data[offset + 7],
132        ]);
133        let deadline_ns = if deadline == 0 { None } else { Some(deadline) };
134
135        Ok(Self {
136            header,
137            module_name,
138            method_name,
139            args,
140            trace_id,
141            deadline_ns,
142        })
143    }
144
145    pub fn to_owned(&self) -> Request {
146        use crate::request::Request;
147
148        let mut req = Request::new(
149            self.header.request_id(),
150            Priority::Normal,
151            self.module_name,
152            self.method_name,
153            self.args.to_vec(),
154        )
155        .with_trace_id(self.trace_id);
156
157        if let Some(deadline) = self.deadline_ns {
158            req = req.with_deadline_ns(Some(deadline));
159        }
160
161        req
162    }
163
164    pub fn request_id(&self) -> RequestId {
165        self.header.request_id()
166    }
167
168    pub fn priority(&self) -> Priority {
169        Priority::Normal
170    }
171
172    pub fn serialized_size(&self) -> usize {
173        HEADER_SIZE +
174        4 + self.module_name.len() +
175        4 + self.method_name.len() +
176        4 + self.args.len() +
177        16 +
178        8
179    }
180}
181
182#[derive(Debug, Clone)]
183pub struct ZeroCopyResponse<'a> {
184    pub header: MessageHeader,
185    pub data: &'a [u8],
186    pub status: crate::types::StatusCode,
187}
188
189impl<'a> ZeroCopyResponse<'a> {
190    pub fn parse(shm: &'a ShmView<'a>) -> Result<Self> {
191        let header = shm.read_header()?;
192
193        let data = shm.as_slice();
194        let mut offset = HEADER_SIZE;
195
196        if offset >= data.len() {
197            return Err(ProtocolError::InvalidHeader(
198                "insufficient data for status code".to_string(),
199            ));
200        }
201        let status_code = data[offset];
202        let status = crate::types::StatusCode::from_u8(status_code).ok_or_else(|| {
203            ProtocolError::InvalidHeader(format!("unknown status code: {}", status_code))
204        })?;
205        offset += 1;
206
207        if offset + 4 > data.len() {
208            return Err(ProtocolError::InvalidHeader(
209                "insufficient data for data length".to_string(),
210            ));
211        }
212        let data_len =
213            u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
214                as usize;
215        offset += 4;
216
217        if offset + data_len > data.len() {
218            return Err(ProtocolError::InvalidHeader(
219                "insufficient data for data".to_string(),
220            ));
221        }
222        let data = &data[offset..offset + data_len];
223
224        Ok(Self {
225            header,
226            data,
227            status,
228        })
229    }
230
231    pub fn to_owned(&self) -> crate::response::Response {
232        use crate::response::Response;
233
234        Response::with_routing(
235            self.header.request_id(),
236            self.status,
237            self.data.to_vec(),
238            self.header.module_id(),
239            self.header.method_hash(),
240        )
241    }
242}