Skip to main content

procwire_client/protocol/
frame.rs

1//! Frame struct with typed accessors.
2//!
3//! Represents a complete protocol frame with header and payload.
4//! Uses `bytes::Bytes` for zero-copy payload sharing.
5//!
6//! # Example
7//!
8//! ```
9//! use procwire_client::protocol::{Frame, Header, flags};
10//! use bytes::Bytes;
11//!
12//! let header = Header::new(1, flags::RESPONSE, 42, 5);
13//! let payload = Bytes::from_static(b"hello");
14//! let frame = Frame::new(header, payload);
15//!
16//! assert_eq!(frame.method_id(), 1);
17//! assert_eq!(frame.payload(), b"hello");
18//! ```
19
20use bytes::Bytes;
21
22use super::wire_format::{flags, Header, HEADER_SIZE};
23
24/// A complete protocol frame.
25#[derive(Debug, Clone)]
26pub struct Frame {
27    /// Decoded header.
28    pub header: Header,
29    /// Payload bytes (zero-copy via `bytes::Bytes`).
30    pub payload: Bytes,
31}
32
33impl Frame {
34    /// Create a new frame from header and payload.
35    pub fn new(header: Header, payload: Bytes) -> Self {
36        Self { header, payload }
37    }
38
39    /// Create a frame from header and raw bytes (copies data).
40    pub fn from_parts(header: Header, payload: &[u8]) -> Self {
41        Self {
42            header,
43            payload: Bytes::copy_from_slice(payload),
44        }
45    }
46
47    /// Get a reference to the payload bytes.
48    #[inline]
49    pub fn payload(&self) -> &[u8] {
50        &self.payload
51    }
52
53    /// Get a clone of the payload as Bytes (cheap, zero-copy).
54    #[inline]
55    pub fn payload_bytes(&self) -> Bytes {
56        self.payload.clone()
57    }
58
59    /// Get the payload length.
60    #[inline]
61    pub fn payload_len(&self) -> usize {
62        self.payload.len()
63    }
64
65    /// Get the method ID.
66    #[inline]
67    pub fn method_id(&self) -> u16 {
68        self.header.method_id
69    }
70
71    /// Get the flags byte.
72    #[inline]
73    pub fn flags(&self) -> u8 {
74        self.header.flags
75    }
76
77    /// Get the request ID.
78    #[inline]
79    pub fn request_id(&self) -> u32 {
80        self.header.request_id
81    }
82
83    /// Check if this is a response.
84    #[inline]
85    pub fn is_response(&self) -> bool {
86        flags::has_flag(self.header.flags, flags::IS_RESPONSE)
87    }
88
89    /// Check if this is an error response.
90    #[inline]
91    pub fn is_error(&self) -> bool {
92        flags::has_flag(self.header.flags, flags::IS_ERROR)
93    }
94
95    /// Check if this is a stream chunk.
96    #[inline]
97    pub fn is_stream(&self) -> bool {
98        flags::has_flag(self.header.flags, flags::IS_STREAM)
99    }
100
101    /// Check if this is the final stream chunk.
102    #[inline]
103    pub fn is_stream_end(&self) -> bool {
104        flags::has_flag(self.header.flags, flags::STREAM_END)
105    }
106
107    /// Check if this is an ACK.
108    #[inline]
109    pub fn is_ack(&self) -> bool {
110        flags::has_flag(self.header.flags, flags::IS_ACK)
111    }
112
113    /// Check if direction is to parent.
114    #[inline]
115    pub fn is_to_parent(&self) -> bool {
116        flags::has_flag(self.header.flags, flags::DIRECTION_TO_PARENT)
117    }
118
119    /// Check if this is an event (request_id == 0, not a response).
120    #[inline]
121    pub fn is_event(&self) -> bool {
122        self.header.request_id == 0 && !self.is_response()
123    }
124
125    /// Check if this is an abort signal.
126    #[inline]
127    pub fn is_abort(&self) -> bool {
128        self.header.is_abort()
129    }
130}
131
132/// Build a complete frame as a single byte vector.
133///
134/// Encodes header and appends payload into a contiguous buffer.
135/// Use `build_frame_parts` for scatter/gather I/O (writev).
136///
137/// # Example
138///
139/// ```
140/// use procwire_client::protocol::{build_frame, Header, flags};
141///
142/// let header = Header::new(1, flags::RESPONSE, 42, 5);
143/// let bytes = build_frame(&header, b"hello");
144/// assert_eq!(bytes.len(), 11 + 5); // header + payload
145/// ```
146pub fn build_frame(header: &Header, payload: &[u8]) -> Vec<u8> {
147    let mut buf = Vec::with_capacity(HEADER_SIZE + payload.len());
148    buf.extend_from_slice(&header.encode());
149    buf.extend_from_slice(payload);
150    buf
151}
152
153/// Build frame parts for scatter/gather I/O.
154///
155/// Returns the encoded header and a reference to the payload.
156/// This avoids copying for writev-style operations.
157///
158/// # Example
159///
160/// ```
161/// use procwire_client::protocol::{build_frame_parts, Header, flags, HEADER_SIZE};
162///
163/// let header = Header::new(1, flags::RESPONSE, 42, 5);
164/// let payload = b"hello";
165/// let (header_bytes, payload_ref) = build_frame_parts(&header, payload);
166///
167/// assert_eq!(header_bytes.len(), HEADER_SIZE);
168/// assert_eq!(payload_ref, b"hello");
169/// ```
170pub fn build_frame_parts<'a>(header: &Header, payload: &'a [u8]) -> ([u8; HEADER_SIZE], &'a [u8]) {
171    (header.encode(), payload)
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_frame_creation() {
180        let header = Header::new(1, flags::RESPONSE, 42, 5);
181        let payload = Bytes::from_static(b"hello");
182        let frame = Frame::new(header, payload);
183
184        assert_eq!(frame.method_id(), 1);
185        assert_eq!(frame.flags(), flags::RESPONSE);
186        assert_eq!(frame.request_id(), 42);
187        assert_eq!(frame.payload(), b"hello");
188        assert_eq!(frame.payload_len(), 5);
189    }
190
191    #[test]
192    fn test_frame_from_parts() {
193        let header = Header::new(2, 0, 100, 4);
194        let frame = Frame::from_parts(header, b"test");
195
196        assert_eq!(frame.method_id(), 2);
197        assert_eq!(frame.payload(), b"test");
198    }
199
200    #[test]
201    fn test_frame_empty_payload() {
202        let header = Header::new(1, 0, 1, 0);
203        let frame = Frame::new(header, Bytes::new());
204
205        assert_eq!(frame.payload_len(), 0);
206        assert!(frame.payload().is_empty());
207    }
208
209    #[test]
210    fn test_frame_flag_accessors() {
211        // Response frame
212        let response_frame = Frame::new(Header::new(1, flags::RESPONSE, 1, 0), Bytes::new());
213        assert!(response_frame.is_response());
214        assert!(response_frame.is_to_parent());
215        assert!(!response_frame.is_error());
216        assert!(!response_frame.is_stream());
217
218        // Error frame
219        let error_frame = Frame::new(Header::new(1, flags::ERROR_RESPONSE, 1, 0), Bytes::new());
220        assert!(error_frame.is_response());
221        assert!(error_frame.is_error());
222
223        // Stream frame
224        let stream_frame = Frame::new(Header::new(1, flags::STREAM_CHUNK, 1, 0), Bytes::new());
225        assert!(stream_frame.is_stream());
226        assert!(!stream_frame.is_stream_end());
227
228        // Stream end frame
229        let stream_end_frame = Frame::new(
230            Header::new(1, flags::STREAM_END_RESPONSE, 1, 0),
231            Bytes::new(),
232        );
233        assert!(stream_end_frame.is_stream());
234        assert!(stream_end_frame.is_stream_end());
235
236        // ACK frame
237        let ack_frame = Frame::new(Header::new(1, flags::ACK_RESPONSE, 1, 0), Bytes::new());
238        assert!(ack_frame.is_ack());
239    }
240
241    #[test]
242    fn test_frame_is_event() {
243        // Event: request_id == 0, not a response
244        let event_frame = Frame::new(
245            Header::new(1, flags::DIRECTION_TO_PARENT, 0, 0),
246            Bytes::new(),
247        );
248        assert!(event_frame.is_event());
249
250        // Not an event: is a response
251        let response_frame = Frame::new(Header::new(1, flags::RESPONSE, 0, 0), Bytes::new());
252        assert!(!response_frame.is_event());
253
254        // Not an event: has request_id
255        let request_frame = Frame::new(Header::new(1, 0, 42, 0), Bytes::new());
256        assert!(!request_frame.is_event());
257    }
258
259    #[test]
260    fn test_frame_is_abort() {
261        use super::super::wire_format::ABORT_METHOD_ID;
262
263        let abort_frame = Frame::new(Header::new(ABORT_METHOD_ID, 0, 0, 0), Bytes::new());
264        assert!(abort_frame.is_abort());
265
266        let normal_frame = Frame::new(Header::new(1, 0, 1, 0), Bytes::new());
267        assert!(!normal_frame.is_abort());
268    }
269
270    #[test]
271    fn test_payload_bytes_zero_copy() {
272        let original = Bytes::from_static(b"test data");
273        let frame = Frame::new(Header::new(1, 0, 1, 9), original.clone());
274
275        // payload_bytes() should return a cheap clone
276        let cloned = frame.payload_bytes();
277        assert_eq!(cloned, original);
278
279        // Both should point to the same data
280        assert_eq!(cloned.as_ptr(), original.as_ptr());
281    }
282
283    #[test]
284    fn test_build_frame() {
285        let header = Header::new(1, flags::RESPONSE, 42, 5);
286        let bytes = build_frame(&header, b"hello");
287
288        assert_eq!(bytes.len(), HEADER_SIZE + 5);
289
290        // Parse it back
291        let parsed_header = Header::decode(&bytes[..HEADER_SIZE]).unwrap();
292        assert_eq!(parsed_header, header);
293        assert_eq!(&bytes[HEADER_SIZE..], b"hello");
294    }
295
296    #[test]
297    fn test_build_frame_empty_payload() {
298        let header = Header::new(1, 0, 1, 0);
299        let bytes = build_frame(&header, b"");
300
301        assert_eq!(bytes.len(), HEADER_SIZE);
302    }
303
304    #[test]
305    fn test_build_frame_parts() {
306        let header = Header::new(1, flags::RESPONSE, 42, 5);
307        let payload = b"hello";
308        let (header_bytes, payload_ref) = build_frame_parts(&header, payload);
309
310        assert_eq!(header_bytes.len(), HEADER_SIZE);
311        assert_eq!(payload_ref, b"hello");
312
313        // Verify header encoding
314        let parsed = Header::decode(&header_bytes).unwrap();
315        assert_eq!(parsed, header);
316    }
317
318    #[test]
319    fn test_build_frame_roundtrip() {
320        use super::super::FrameBuffer;
321
322        let header = Header::new(123, flags::STREAM_CHUNK, 456, 10);
323        let payload = b"0123456789";
324        let bytes = build_frame(&header, payload);
325
326        let mut buffer = FrameBuffer::new();
327        let frames = buffer.push(&bytes).unwrap();
328
329        assert_eq!(frames.len(), 1);
330        let frame = &frames[0];
331        assert_eq!(frame.method_id(), 123);
332        assert_eq!(frame.request_id(), 456);
333        assert_eq!(frame.payload(), payload);
334        assert!(frame.is_stream());
335    }
336}