conmonrs/streaming_server/
channel.rs1use crate::streaming_server::web::Message;
7use bytes::{Buf, BufMut, Bytes, BytesMut};
8use data_encoding::{BASE64, Encoding};
9use std::sync::LazyLock;
10use tracing::debug;
11
12static BASE64_LENIENT: LazyLock<Encoding> = LazyLock::new(|| {
18 let mut spec = BASE64.specification();
19 spec.ignore.push_str("\r\n");
20 spec.check_trailing_bits = false;
21 spec.encoding().unwrap_or(BASE64)
24});
25
26pub(super) fn message(base64: bool, channel: u8, data: &[u8]) -> Message {
28 if base64 {
29 let mut text = String::with_capacity(1 + data.len().div_ceil(3) * 4);
30 text.push(char::from(b'0' + channel));
31 BASE64.encode_append(data, &mut text);
32 Message::Text(text.into())
33 } else {
34 let mut frame = BytesMut::with_capacity(1 + data.len());
35 frame.put_u8(channel);
36 frame.put_slice(data);
37 Message::Binary(frame.freeze())
38 }
39}
40
41pub(super) fn decode(base64: bool, mut message: Bytes) -> Option<(u8, Bytes)> {
44 let &channel = message.first()?;
45 if !base64 {
46 message.advance(1);
47 return Some((channel, message));
48 }
49 match BASE64_LENIENT.decode(&message[1..]) {
50 Ok(payload) => Some((channel.wrapping_sub(b'0'), payload.into())),
51 Err(e) => {
52 debug!("Ignoring message with invalid base64: {e}");
53 None
54 }
55 }
56}
57
58#[cfg(any(test, feature = "fuzzing"))]
60pub(super) mod fuzzing {
61 use super::*;
62
63 pub fn channel(base64: bool, input: &[u8]) -> bool {
68 let decoded = decode(base64, Bytes::copy_from_slice(input)).is_some();
69 let Some((&channel, payload)) = input.split_first() else {
70 return decoded;
71 };
72 if base64 && channel > 0x7f - b'0' {
73 return decoded;
74 }
75 let message = match message(base64, channel, payload) {
76 Message::Binary(data) => data,
77 Message::Text(text) => Bytes::from(text),
78 message => panic!("unexpected message {message:?}"),
79 };
80 assert_eq!(
81 decode(base64, message),
82 Some((channel, Bytes::copy_from_slice(payload)))
83 );
84 decoded
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn messages_get_framed() {
94 assert_eq!(
95 message(false, 1, b"out"),
96 Message::Binary(Bytes::from_static(b"\x01out"))
97 );
98 assert_eq!(message(true, 2, b"err"), Message::Text("2ZXJy".into()));
99 assert_eq!(message(true, 1, b""), Message::Text("1".into()));
100 }
101
102 #[test]
103 fn messages_get_decoded() {
104 assert_eq!(
105 decode(false, Bytes::from_static(b"\x00ls")),
106 Some((0, Bytes::from_static(b"ls")))
107 );
108 assert_eq!(
109 decode(true, Bytes::from_static(b"0bHM=")),
110 Some((0, Bytes::from_static(b"ls")))
111 );
112 assert_eq!(decode(false, Bytes::new()), None);
113 assert_eq!(decode(true, Bytes::from_static(b"0!")), None);
114 }
115
116 #[test]
118 fn base64_gets_decoded_like_go() {
119 let ls = Some((0, Bytes::from_static(b"ls")));
120 assert_eq!(decode(true, Bytes::from_static(b"0bH\nM=")), ls);
122 assert_eq!(decode(true, Bytes::from_static(b"0b\r\nHM=\r\n")), ls);
123 assert_eq!(
124 decode(true, Bytes::from_static(b"0\n")),
125 Some((0, Bytes::new()))
126 );
127 assert_eq!(decode(true, Bytes::from_static(b"0bHN=")), ls);
129 assert_eq!(
130 decode(true, Bytes::from_static(b"0bB==")),
131 Some((0, Bytes::from_static(b"l")))
132 );
133 assert_eq!(decode(true, Bytes::from_static(b"0bH M=")), None);
135 assert_eq!(decode(true, Bytes::from_static(b"0bHM")), None);
136 }
137}