wire_codec/framing/
delimiter.rs1use crate::buf::WriteBuf;
9use crate::error::{Error, Result};
10use crate::framing::{Frame, Framer};
11
12#[derive(Debug, Clone, Copy)]
34pub struct Delimited<'d> {
35 delimiter: &'d [u8],
36 max_payload: usize,
37}
38
39impl<'d> Delimited<'d> {
40 #[inline]
47 pub const fn new(delimiter: &'d [u8]) -> Self {
48 assert!(!delimiter.is_empty(), "delimiter must be non-empty");
50 Self {
51 delimiter,
52 max_payload: usize::MAX,
53 }
54 }
55
56 #[inline]
59 #[must_use]
60 pub const fn with_max_payload(mut self, max: usize) -> Self {
61 self.max_payload = max;
62 self
63 }
64
65 #[inline]
67 pub const fn delimiter(&self) -> &'d [u8] {
68 self.delimiter
69 }
70
71 #[inline]
73 pub const fn max_payload(&self) -> usize {
74 self.max_payload
75 }
76
77 fn find_delimiter(&self, haystack: &[u8]) -> Option<usize> {
78 let needle = self.delimiter;
79 if haystack.len() < needle.len() {
80 return None;
81 }
82 let last = haystack.len() - needle.len();
83 let mut i = 0;
84 while i <= last {
85 if &haystack[i..i + needle.len()] == needle {
86 return Some(i);
87 }
88 i += 1;
89 }
90 None
91 }
92}
93
94impl<'d> Framer for Delimited<'d> {
95 fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
96 let window_cap = self.max_payload.saturating_add(self.delimiter.len());
99 let scan_window = if input.len() > window_cap {
100 &input[..window_cap]
101 } else {
102 input
103 };
104 match self.find_delimiter(scan_window) {
105 Some(pos) => {
106 if pos > self.max_payload {
107 return Err(Error::FrameTooLarge {
108 len: pos,
109 limit: self.max_payload,
110 });
111 }
112 let consumed = pos + self.delimiter.len();
113 Ok(Some(Frame::new(&input[..pos], consumed)))
114 }
115 None => {
116 if input.len() > window_cap {
119 return Err(Error::FrameTooLarge {
120 len: input.len(),
121 limit: self.max_payload,
122 });
123 }
124 Ok(None)
125 }
126 }
127 }
128
129 fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
130 if payload.len() > self.max_payload {
131 return Err(Error::FrameTooLarge {
132 len: payload.len(),
133 limit: self.max_payload,
134 });
135 }
136 out.write_bytes(payload)?;
137 out.write_bytes(self.delimiter)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn newline_round_trip() {
147 let framer = Delimited::new(b"\n");
148 let mut storage = [0u8; 32];
149 let mut buf = WriteBuf::new(&mut storage);
150 framer.write_frame(b"hello", &mut buf).unwrap();
151 let n = buf.position();
152 assert_eq!(&storage[..n], b"hello\n");
153
154 let frame = framer.next_frame(&storage[..n]).unwrap().unwrap();
155 assert_eq!(frame.payload(), b"hello");
156 assert_eq!(frame.consumed(), 6);
157 }
158
159 #[test]
160 fn multi_byte_delimiter() {
161 let framer = Delimited::new(b"\r\n");
162 let frame = framer.next_frame(b"GET /\r\nrest").unwrap().unwrap();
163 assert_eq!(frame.payload(), b"GET /");
164 assert_eq!(frame.consumed(), 7);
165 }
166
167 #[test]
168 fn no_delimiter_returns_none() {
169 let framer = Delimited::new(b"\n");
170 assert_eq!(framer.next_frame(b"no terminator here").unwrap(), None);
171 }
172
173 #[test]
174 fn exceeds_max_payload_short_circuits_search() {
175 let framer = Delimited::new(b"\n").with_max_payload(3);
179 let result = framer.next_frame(b"abcde\n");
180 assert!(matches!(result, Err(Error::FrameTooLarge { limit: 3, .. })));
181 }
182
183 #[test]
184 fn empty_payload_is_valid() {
185 let framer = Delimited::new(b"\n");
186 let frame = framer.next_frame(b"\nrest").unwrap().unwrap();
187 assert_eq!(frame.payload(), b"");
188 assert_eq!(frame.consumed(), 1);
189 }
190}