1use std::io::{self, Read, Write};
14
15use prost::Message;
16
17pub use running_process_protocol::broker::v1::{Frame, FrameKind, PayloadEncoding};
18
19pub const FRAMING_VERSION_V1: u8 = 1;
21
22pub const MAX_FRAME_SIZE_BYTES: usize = 16 * 1024 * 1024;
24
25pub const MAX_HELLO_SIZE_BYTES: usize = 64 * 1024;
27
28pub const ENVELOPE_VERSION: u8 = FRAMING_VERSION_V1;
30
31pub const MAX_FRAME_BYTES: usize = MAX_FRAME_SIZE_BYTES;
33
34pub const MAX_HELLO_BYTES: usize = MAX_HELLO_SIZE_BYTES;
36
37pub const FRAME_HEADER_BYTES: usize = 5;
39
40#[derive(Debug, thiserror::Error)]
43pub enum FramingError {
44 #[error("unsupported framing version: got {got}, expected {expected}")]
46 UnsupportedFramingVersion {
47 got: u8,
49 expected: u8,
51 },
52
53 #[error("frame body too large: {body_length} bytes exceeds cap {cap}")]
55 FrameTooLarge {
56 body_length: usize,
58 cap: usize,
60 },
61
62 #[error("unexpected EOF while reading frame ({context})")]
64 UnexpectedEof {
65 context: &'static str,
67 },
68
69 #[error("I/O error: {0}")]
71 Io(#[from] io::Error),
72
73 #[error("failed to decode Frame body: {0}")]
75 Decode(#[from] prost::DecodeError),
76}
77
78pub fn encode_framed(frame: &Frame) -> Result<Vec<u8>, FramingError> {
85 let body_len = frame.encoded_len();
86 if body_len > MAX_FRAME_BYTES {
87 return Err(FramingError::FrameTooLarge {
88 body_length: body_len,
89 cap: MAX_FRAME_BYTES,
90 });
91 }
92 let mut wire = Vec::with_capacity(FRAME_HEADER_BYTES + body_len);
93 wire.push(ENVELOPE_VERSION);
94 wire.extend_from_slice(&(body_len as u32).to_le_bytes());
95 frame
96 .encode(&mut wire)
97 .expect("prost encoding into Vec cannot fail because Vec writes are infallible");
98 Ok(wire)
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub struct DecodedFramed {
104 pub frame: Frame,
106 pub consumed: usize,
108}
109
110pub fn try_decode_framed(buf: &[u8]) -> Result<Option<DecodedFramed>, FramingError> {
116 if buf.is_empty() {
117 return Ok(None);
118 }
119 if buf[0] != ENVELOPE_VERSION {
120 return Err(FramingError::UnsupportedFramingVersion {
121 got: buf[0],
122 expected: ENVELOPE_VERSION,
123 });
124 }
125 if buf.len() < FRAME_HEADER_BYTES {
126 return Ok(None);
127 }
128 let body_len = u32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize;
129 if body_len > MAX_FRAME_BYTES {
130 return Err(FramingError::FrameTooLarge {
131 body_length: body_len,
132 cap: MAX_FRAME_BYTES,
133 });
134 }
135 let total = FRAME_HEADER_BYTES + body_len;
136 if buf.len() < total {
137 return Ok(None);
138 }
139 let frame = Frame::decode(&buf[FRAME_HEADER_BYTES..total])?;
140 Ok(Some(DecodedFramed {
141 frame,
142 consumed: total,
143 }))
144}
145
146pub fn read_frame<R: Read>(reader: &mut R) -> Result<Vec<u8>, FramingError> {
148 read_frame_with_cap(reader, MAX_FRAME_BYTES)
149}
150
151pub fn read_frame_with_cap<R: Read>(
154 reader: &mut R,
155 max_bytes: usize,
156) -> Result<Vec<u8>, FramingError> {
157 let mut version_buf = [0_u8; 1];
158 read_exact_or_eof(reader, &mut version_buf, "framing byte")?;
159 let version = version_buf[0];
160 if version != ENVELOPE_VERSION {
161 return Err(FramingError::UnsupportedFramingVersion {
162 got: version,
163 expected: ENVELOPE_VERSION,
164 });
165 }
166
167 let mut len_buf = [0_u8; 4];
168 read_exact_or_eof(reader, &mut len_buf, "body length header")?;
169 let body_length = u32::from_le_bytes(len_buf) as usize;
170 if body_length > max_bytes {
171 return Err(FramingError::FrameTooLarge {
172 body_length,
173 cap: max_bytes,
174 });
175 }
176
177 let mut body = vec![0_u8; body_length];
178 if body_length != 0 {
179 read_exact_or_eof(reader, &mut body, "frame body")?;
180 }
181 Ok(body)
182}
183
184pub fn write_frame<W: Write>(writer: &mut W, body: &[u8]) -> Result<usize, FramingError> {
186 if body.len() > MAX_FRAME_BYTES {
187 return Err(FramingError::FrameTooLarge {
188 body_length: body.len(),
189 cap: MAX_FRAME_BYTES,
190 });
191 }
192
193 let body_len = body.len() as u32;
194 let header = [
195 ENVELOPE_VERSION,
196 (body_len & 0xFF) as u8,
197 ((body_len >> 8) & 0xFF) as u8,
198 ((body_len >> 16) & 0xFF) as u8,
199 ((body_len >> 24) & 0xFF) as u8,
200 ];
201 writer.write_all(&header)?;
202 if !body.is_empty() {
203 writer.write_all(body)?;
204 }
205 writer.flush()?;
206 Ok(header.len() + body.len())
207}
208
209fn read_exact_or_eof<R: Read>(
210 reader: &mut R,
211 buf: &mut [u8],
212 context: &'static str,
213) -> Result<(), FramingError> {
214 match reader.read_exact(buf) {
215 Ok(()) => Ok(()),
216 Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => {
217 Err(FramingError::UnexpectedEof { context })
218 }
219 Err(error) => Err(FramingError::Io(error)),
220 }
221}
222
223pub mod registry {
225 pub const PROTOCOL_VERSION: u32 = 1;
228
229 pub const CONTROL_PAYLOAD_PROTOCOL: u32 = 0x00;
231 pub const ADMIN_PAYLOAD_PROTOCOL: u32 = 0xAD01;
233 pub const BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL: u32 = 0xB232;
235 pub const HANDOFF_PAYLOAD_PROTOCOL: u32 = 0xD0FF;
237 pub const SESSION_PAYLOAD_PROTOCOL: u32 = 0x5350;
239
240 pub const CONSUMER_PAYLOAD_PROTOCOL_MIN: u32 = 0x7000;
242 pub const CONSUMER_PAYLOAD_PROTOCOL_MAX: u32 = 0x7EFF;
244 pub const PRIVATE_USE_PAYLOAD_PROTOCOL_MIN: u32 = 0xF000;
246 pub const PRIVATE_USE_PAYLOAD_PROTOCOL_MAX: u32 = 0xFFFF;
248
249 pub const ZCCACHE_PAYLOAD_PROTOCOL: u32 = 0x7A63;
251 pub const CLUD_PAYLOAD_PROTOCOL: u32 = 0x7C4C;
253 pub const FBUILD_PAYLOAD_PROTOCOL: u32 = 0x7EB1;
255
256 pub const FIRST_PARTY_PAYLOAD_PROTOCOLS: [u32; 4] = [
258 CONTROL_PAYLOAD_PROTOCOL,
259 ADMIN_PAYLOAD_PROTOCOL,
260 BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL,
261 HANDOFF_PAYLOAD_PROTOCOL,
262 ];
263
264 pub const fn is_first_party(id: u32) -> bool {
266 let mut index = 0;
267 while index < FIRST_PARTY_PAYLOAD_PROTOCOLS.len() {
268 if FIRST_PARTY_PAYLOAD_PROTOCOLS[index] == id {
269 return true;
270 }
271 index += 1;
272 }
273 false
274 }
275
276 pub const fn is_registered_consumer_id(id: u32) -> bool {
278 id >= CONSUMER_PAYLOAD_PROTOCOL_MIN && id <= CONSUMER_PAYLOAD_PROTOCOL_MAX
279 }
280
281 pub const fn is_private_use_id(id: u32) -> bool {
283 id >= PRIVATE_USE_PAYLOAD_PROTOCOL_MIN && id <= PRIVATE_USE_PAYLOAD_PROTOCOL_MAX
284 }
285}
286
287pub use registry::{
288 ADMIN_PAYLOAD_PROTOCOL, BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL, CLUD_PAYLOAD_PROTOCOL,
289 CONTROL_PAYLOAD_PROTOCOL, FBUILD_PAYLOAD_PROTOCOL, HANDOFF_PAYLOAD_PROTOCOL, PROTOCOL_VERSION,
290 SESSION_PAYLOAD_PROTOCOL, ZCCACHE_PAYLOAD_PROTOCOL,
291};
292
293#[macro_export]
300macro_rules! register_payload_protocol {
301 ($(#[$meta:meta])* $vis:vis const $name:ident: u32 = $value:expr;) => {
302 $(#[$meta])*
303 $vis const $name: u32 = $value;
304
305 const _: () = {
306 assert!(
307 !$crate::frame_v1::registry::is_first_party($name),
308 concat!(
309 stringify!($name),
310 " collides with a first-party running-process payload protocol",
311 ),
312 );
313 assert!(
314 $crate::frame_v1::registry::is_registered_consumer_id($name)
315 || $crate::frame_v1::registry::is_private_use_id($name),
316 concat!(
317 stringify!($name),
318 " must lie in the registered-consumer range (0x7000..=0x7EFF) ",
319 "or the private-use range (0xF000..=0xFFFF)",
320 ),
321 );
322 };
323 };
324}
325
326#[cfg(test)]
327mod tests {
328 use super::registry::{
329 is_first_party, is_private_use_id, is_registered_consumer_id, ADMIN_PAYLOAD_PROTOCOL,
330 BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL, CLUD_PAYLOAD_PROTOCOL,
331 CONSUMER_PAYLOAD_PROTOCOL_MAX, CONSUMER_PAYLOAD_PROTOCOL_MIN, CONTROL_PAYLOAD_PROTOCOL,
332 FBUILD_PAYLOAD_PROTOCOL, HANDOFF_PAYLOAD_PROTOCOL, PRIVATE_USE_PAYLOAD_PROTOCOL_MAX,
333 PRIVATE_USE_PAYLOAD_PROTOCOL_MIN, PROTOCOL_VERSION, ZCCACHE_PAYLOAD_PROTOCOL,
334 };
335 use super::{
336 encode_framed, try_decode_framed, Frame, FrameKind, FramingError, PayloadEncoding,
337 ENVELOPE_VERSION, MAX_FRAME_BYTES,
338 };
339
340 crate::register_payload_protocol! {
341 const MACRO_CONSUMER_RANGE_EXAMPLE: u32 = 0x7001;
343 }
344 crate::register_payload_protocol! {
345 const MACRO_PRIVATE_RANGE_EXAMPLE: u32 = 0xF00D;
347 }
348
349 #[test]
350 fn payload_protocol_ids_are_pairwise_distinct() {
351 let registered: [(u32, &str); 4] = [
352 (CONTROL_PAYLOAD_PROTOCOL, "CONTROL_PAYLOAD_PROTOCOL"),
353 (ADMIN_PAYLOAD_PROTOCOL, "ADMIN_PAYLOAD_PROTOCOL"),
354 (
355 BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL,
356 "BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL",
357 ),
358 (HANDOFF_PAYLOAD_PROTOCOL, "HANDOFF_PAYLOAD_PROTOCOL"),
359 ];
360 for (left_index, (left_id, left_name)) in registered.iter().enumerate() {
361 for (right_id, right_name) in ®istered[left_index + 1..] {
362 assert_ne!(
363 left_id, right_id,
364 "{left_name} and {right_name} share payload-protocol id {left_id:#06X}"
365 );
366 }
367 }
368 }
369
370 #[test]
371 fn frozen_v1_wire_values() {
372 assert_eq!(PROTOCOL_VERSION, 1);
373 assert_eq!(CONTROL_PAYLOAD_PROTOCOL, 0x00);
374 assert_eq!(ADMIN_PAYLOAD_PROTOCOL, 0xAD01);
375 assert_eq!(BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL, 0xB232);
376 assert_eq!(HANDOFF_PAYLOAD_PROTOCOL, 0xD0FF);
377 assert_eq!(u32::from(super::FRAMING_VERSION_V1), 1);
378 }
379
380 #[test]
381 fn frozen_consumer_registry_values() {
382 assert_eq!(CONSUMER_PAYLOAD_PROTOCOL_MIN, 0x7000);
383 assert_eq!(CONSUMER_PAYLOAD_PROTOCOL_MAX, 0x7EFF);
384 assert_eq!(PRIVATE_USE_PAYLOAD_PROTOCOL_MIN, 0xF000);
385 assert_eq!(PRIVATE_USE_PAYLOAD_PROTOCOL_MAX, 0xFFFF);
386 assert_eq!(ZCCACHE_PAYLOAD_PROTOCOL, 0x7A63);
387 assert_eq!(CLUD_PAYLOAD_PROTOCOL, 0x7C4C);
388 assert_eq!(FBUILD_PAYLOAD_PROTOCOL, 0x7EB1);
389 assert!(is_first_party(BACKEND_HANDLE_PROBE_PAYLOAD_PROTOCOL));
390 assert!(is_registered_consumer_id(ZCCACHE_PAYLOAD_PROTOCOL));
391 assert!(is_private_use_id(0xF412));
392 }
393
394 #[test]
395 fn register_macro_defines_usable_constants() {
396 assert_eq!(MACRO_CONSUMER_RANGE_EXAMPLE, 0x7001);
397 assert_eq!(MACRO_PRIVATE_RANGE_EXAMPLE, 0xF00D);
398 }
399
400 #[test]
401 fn root_client_reexports_keep_frame_extensions_and_codecs() {
402 let frame = Frame::request(0x7A63, b"ping".to_vec()).with_request_id(42);
403 assert_eq!(frame.kind, FrameKind::Request as i32);
404 assert_eq!(frame.payload_encoding, PayloadEncoding::None as i32);
405
406 let wire = encode_framed(&frame).expect("encode");
407 let decoded = try_decode_framed(&wire)
408 .expect("decode")
409 .expect("complete frame");
410 assert_eq!(decoded.frame, frame);
411 assert_eq!(decoded.consumed, wire.len());
412 }
413
414 #[test]
415 fn try_decode_framed_waits_for_complete_frames() {
416 let wire = encode_framed(&Frame::request(0x7001, b"abc".to_vec())).expect("encode");
417
418 assert!(
419 try_decode_framed(&[])
420 .expect("empty buffer is not an error")
421 .is_none(),
422 "an empty buffer must ask for more bytes, not decode"
423 );
424 for cut in 1..wire.len() {
425 assert!(
426 try_decode_framed(&wire[..cut])
427 .expect("a prefix is not an error")
428 .is_none(),
429 "partial frame of {cut} of {} bytes must not decode",
430 wire.len()
431 );
432 }
433
434 let mut two = wire.clone();
435 two.extend_from_slice(&wire);
436 let first = try_decode_framed(&two)
437 .expect("decode")
438 .expect("first frame is complete");
439 assert_eq!(first.consumed, wire.len());
440 }
441
442 #[test]
443 fn try_decode_framed_rejects_foreign_version_and_oversize() {
444 let foreign = ENVELOPE_VERSION.wrapping_add(1);
445 assert!(matches!(
446 try_decode_framed(&[foreign, 0, 0, 0, 0]),
447 Err(FramingError::UnsupportedFramingVersion { got, expected })
448 if got == foreign && expected == ENVELOPE_VERSION
449 ));
450
451 let mut oversize = vec![ENVELOPE_VERSION];
452 let claimed = u32::try_from(MAX_FRAME_BYTES).expect("cap fits u32") + 1;
453 oversize.extend_from_slice(&claimed.to_le_bytes());
454 assert!(matches!(
455 try_decode_framed(&oversize),
456 Err(FramingError::FrameTooLarge { body_length, cap })
457 if body_length == claimed as usize && cap == MAX_FRAME_BYTES
458 ));
459 }
460
461 #[cfg(feature = "client")]
462 #[test]
463 fn root_client_reexports_keep_endpoint_extensions_and_errors() {
464 use crate::broker::protocol::{Endpoint, EndpointNameError};
465
466 assert!(Endpoint::unix_socket("svc", "/tmp/svc.sock").is_ok());
467 assert_eq!(
468 Endpoint::windows_pipe("svc", r"\\.\pipe\svc-pipe"),
469 Err(EndpointNameError::PrefixedPipeName {
470 got: r"\\.\pipe\svc-pipe".to_owned(),
471 })
472 );
473 }
474}