webui_protocol/plugin/
fast.rs1use crate::{ProtocolError, Result};
5
6const FAST_BINDING_COUNT_LEN: usize = 4;
7const FAST_V2_ELEMENT_DATA_LEN: usize = FAST_BINDING_COUNT_LEN + 1;
8const FAST_V3_ELEMENT_DATA_LEN: usize = FAST_BINDING_COUNT_LEN;
9const FAST_V2_RESET_CHILD_INDEX: u8 = 1;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct FastElementData {
14 pub binding_count: u32,
16}
17
18impl FastElementData {
19 #[must_use]
21 pub fn encode_v3(self) -> [u8; FAST_V3_ELEMENT_DATA_LEN] {
22 self.binding_count.to_le_bytes()
23 }
24
25 #[must_use]
27 pub fn encode_v2(self, reset_child_index: bool) -> [u8; FAST_V2_ELEMENT_DATA_LEN] {
28 let mut data = [0; FAST_V2_ELEMENT_DATA_LEN];
29 data[..FAST_BINDING_COUNT_LEN].copy_from_slice(&self.binding_count.to_le_bytes());
30 if reset_child_index {
31 data[FAST_BINDING_COUNT_LEN] = FAST_V2_RESET_CHILD_INDEX;
32 }
33 data
34 }
35
36 pub fn decode_v3(bytes: &[u8]) -> Result<Self> {
42 if bytes.len() != FAST_V3_ELEMENT_DATA_LEN {
43 return Err(ProtocolError::Validation(format!(
44 "FAST 3 element data must be {FAST_V3_ELEMENT_DATA_LEN} bytes, received {}",
45 bytes.len()
46 )));
47 }
48
49 Ok(Self {
50 binding_count: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
51 })
52 }
53
54 pub fn decode_v2(bytes: &[u8]) -> Result<(Self, bool)> {
60 let [a, b, c, d, flags] = bytes else {
61 return Err(ProtocolError::Validation(format!(
62 "FAST 2 element data must be {FAST_V2_ELEMENT_DATA_LEN} bytes, received {}",
63 bytes.len()
64 )));
65 };
66 let binding_count = u32::from_le_bytes([*a, *b, *c, *d]);
67 if *flags & !FAST_V2_RESET_CHILD_INDEX != 0 {
68 return Err(ProtocolError::Validation(format!(
69 "FAST 2 element data contains unsupported flags: 0x{flags:02x}"
70 )));
71 }
72 Ok((
73 Self { binding_count },
74 *flags & FAST_V2_RESET_CHILD_INDEX != 0,
75 ))
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::FastElementData;
82 use crate::ProtocolError;
83
84 #[test]
85 fn test_fast_v3_element_data_roundtrip() {
86 let encoded = FastElementData { binding_count: 3 }.encode_v3();
87 let decoded = FastElementData::decode_v3(&encoded).expect("decode should succeed");
88 assert_eq!(decoded.binding_count, 3);
89 }
90
91 #[test]
92 fn test_fast_v3_element_data_rejects_invalid_length() {
93 let result = FastElementData::decode_v3(&[1, 2]);
94 assert!(
95 matches!(result, Err(ProtocolError::Validation(ref msg)) if msg.contains("4 bytes")),
96 "invalid payload length should be rejected: {result:?}"
97 );
98 }
99
100 #[test]
101 fn test_fast_v2_element_data_roundtrip() {
102 let encoded = FastElementData { binding_count: 3 }.encode_v2(true);
103 let decoded = FastElementData::decode_v2(&encoded).expect("decode should succeed");
104 assert_eq!(decoded, (FastElementData { binding_count: 3 }, true));
105 }
106
107 #[test]
108 fn test_fast_v2_element_data_rejects_four_byte_count() {
109 let result = FastElementData::decode_v2(&3u32.to_le_bytes());
110 assert!(
111 matches!(result, Err(ProtocolError::Validation(ref msg)) if msg.contains("5 bytes")),
112 "four-byte FAST 2 data should be rejected: {result:?}"
113 );
114 }
115
116 #[test]
117 fn test_fast_v2_element_data_rejects_unknown_flags() {
118 let result = FastElementData::decode_v2(&[3, 0, 0, 0, 2]);
119 assert!(
120 matches!(result, Err(ProtocolError::Validation(ref msg)) if msg.contains("unsupported flags")),
121 "unknown flags should be rejected: {result:?}"
122 );
123 }
124}