zerodds_grpc_bridge/
frame.rs1use alloc::vec::Vec;
7use core::fmt;
8
9pub const HEADER_LEN: usize = 5;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum FrameError {
27 HeaderTooShort,
29 BodyTruncated,
31 MessageTooLarge,
33}
34
35impl fmt::Display for FrameError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::HeaderTooShort => f.write_str("LPM header < 5 bytes"),
39 Self::BodyTruncated => f.write_str("LPM body truncated"),
40 Self::MessageTooLarge => f.write_str("message length exceeds u32"),
41 }
42 }
43}
44
45#[cfg(feature = "std")]
46impl std::error::Error for FrameError {}
47
48pub fn encode_message(payload: &[u8], compressed: bool) -> Result<Vec<u8>, FrameError> {
57 if payload.len() > u32::MAX as usize {
58 return Err(FrameError::MessageTooLarge);
59 }
60 let mut out = Vec::with_capacity(HEADER_LEN + payload.len());
61 out.push(if compressed { 1 } else { 0 });
62 #[allow(clippy::cast_possible_truncation)]
63 out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
64 out.extend_from_slice(payload);
65 Ok(out)
66}
67
68pub fn encode_web_trailers(trailers: &[u8]) -> Result<Vec<u8>, FrameError> {
76 if trailers.len() > u32::MAX as usize {
77 return Err(FrameError::MessageTooLarge);
78 }
79 let mut out = Vec::with_capacity(HEADER_LEN + trailers.len());
80 out.push(0x80); #[allow(clippy::cast_possible_truncation)]
82 out.extend_from_slice(&(trailers.len() as u32).to_be_bytes());
83 out.extend_from_slice(trailers);
84 Ok(out)
85}
86
87pub fn decode_message(bytes: &[u8]) -> Result<(u8, Vec<u8>, usize), FrameError> {
93 if bytes.len() < HEADER_LEN {
94 return Err(FrameError::HeaderTooShort);
95 }
96 let flag = bytes[0];
97 let len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
98 let total = HEADER_LEN + len;
99 if bytes.len() < total {
100 return Err(FrameError::BodyTruncated);
101 }
102 Ok((flag, bytes[HEADER_LEN..total].to_vec(), total))
103}
104
105#[cfg(test)]
106#[allow(clippy::expect_used)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn empty_message_encodes_to_5_byte_header() {
112 let bytes = encode_message(&[], false).expect("encode");
114 assert_eq!(bytes, alloc::vec![0, 0, 0, 0, 0]);
115 }
116
117 #[test]
118 fn uncompressed_message_has_compressed_flag_zero() {
119 let bytes = encode_message(b"hello", false).expect("encode");
120 assert_eq!(bytes[0], 0);
121 assert_eq!(&bytes[1..5], &5u32.to_be_bytes());
122 assert_eq!(&bytes[5..], b"hello");
123 }
124
125 #[test]
126 fn compressed_message_has_compressed_flag_one() {
127 let bytes = encode_message(b"compressed", true).expect("encode");
128 assert_eq!(bytes[0], 1);
129 }
130
131 #[test]
132 fn round_trip_message() {
133 for payload in [
134 alloc::vec![],
135 alloc::vec![0u8],
136 alloc::vec![1, 2, 3, 4],
137 alloc::vec![0xAB; 1000],
138 ] {
139 let bytes = encode_message(&payload, false).expect("encode");
140 let (flag, decoded, consumed) = decode_message(&bytes).expect("decode");
141 assert_eq!(flag, 0);
142 assert_eq!(decoded, payload);
143 assert_eq!(consumed, bytes.len());
144 }
145 }
146
147 #[test]
148 fn message_length_uses_big_endian_4_bytes() {
149 let bytes = encode_message(&alloc::vec![0; 256], false).expect("encode");
151 assert_eq!(&bytes[1..5], &[0x00, 0x00, 0x01, 0x00]);
153 }
154
155 #[test]
156 fn header_too_short_decode_fails() {
157 assert_eq!(decode_message(&[]), Err(FrameError::HeaderTooShort));
158 assert_eq!(decode_message(&[0; 4]), Err(FrameError::HeaderTooShort));
159 }
160
161 #[test]
162 fn body_truncated_decode_fails() {
163 let bytes = [0u8, 0, 0, 0, 10, 1, 2, 3];
165 assert_eq!(decode_message(&bytes), Err(FrameError::BodyTruncated));
166 }
167
168 #[test]
169 fn web_trailers_encoded_with_msb_set() {
170 let trailers = b"grpc-status: 0\r\n";
172 let bytes = encode_web_trailers(trailers).expect("encode");
173 assert_eq!(bytes[0], 0x80);
174 assert_eq!(&bytes[1..5], &(trailers.len() as u32).to_be_bytes());
175 }
176
177 #[test]
178 fn back_to_back_messages_can_be_decoded_sequentially() {
179 let m1 = encode_message(b"first", false).expect("encode");
181 let m2 = encode_message(b"second", false).expect("encode");
182 let mut combined = m1.clone();
183 combined.extend_from_slice(&m2);
184
185 let (_, decoded1, consumed1) = decode_message(&combined).expect("decode 1");
186 assert_eq!(decoded1, b"first");
187 let (_, decoded2, _) = decode_message(&combined[consumed1..]).expect("decode 2");
188 assert_eq!(decoded2, b"second");
189 }
190}