macula_rust/frame/
envelope.rs1use crate::cbor::Value;
7
8use super::wire_counters::{count_ext_ignored, count_ext_refused, count_unknown_frame_type};
9use super::{protocol_uint, FrameError, PROTOCOL_VERSION};
10
11pub const MAX_FRAME_TYPE_BYTES: usize = 64;
13pub const MAX_EXTENSION_NAME_BYTES: usize = 64;
14pub const MAX_EXTENSIONS: usize = 16;
15
16pub const QUIC_CODE_UNKNOWN_FRAME_TYPE: u32 = 5;
20
21const KNOWN_EXTENSIONS: &[&str] = &[];
24
25#[derive(Debug, Clone, PartialEq)]
28pub struct Envelope {
29 pub frame_type: String,
30 pub frame: Value,
31}
32
33pub fn read_envelope(v: &Value) -> Result<Envelope, FrameError> {
42 let Value::Map(pairs) = v else {
43 return Err(FrameError::Malformed);
44 };
45 let mut frame_type = None;
46 let mut has_version = false;
47 let mut kept = Vec::with_capacity(pairs.len());
48 for (key, value) in pairs {
49 let Value::Text(name) = key else {
50 return Err(FrameError::Malformed);
51 };
52 match name.as_str() {
53 "version" if *value == Value::Int(i128::from(PROTOCOL_VERSION)) => has_version = true,
54 "frame_type" => match value {
55 Value::Text(t) if !t.is_empty() && t.len() <= MAX_FRAME_TYPE_BYTES => {
56 frame_type = Some(t.clone())
57 }
58 _ => return Err(FrameError::Malformed),
59 },
60 "ext" => {
61 act_on_ext(value)?;
62 continue;
63 }
64 "version" | "capabilities" => return Err(FrameError::Malformed),
65 other if !shared_key_accepts(other, value) => return Err(FrameError::Malformed),
66 _ => {}
67 }
68 kept.push((key.clone(), value.clone()));
69 }
70 match (has_version, frame_type) {
71 (true, Some(frame_type)) => Ok(Envelope {
72 frame_type,
73 frame: Value::Map(kept),
74 }),
75 _ => Err(FrameError::Malformed),
76 }
77}
78
79pub fn unknown_frame_type(envelope: &Envelope, known: &[&str]) -> Option<String> {
83 if known.contains(&envelope.frame_type.as_str()) {
84 return None;
85 }
86 count_unknown_frame_type();
87 Some(envelope.frame_type.clone())
88}
89
90fn shared_key_accepts(name: &str, v: &Value) -> bool {
93 match name {
94 "frame_id" => matches!(v, Value::Bytes(b) if b.len() == 16),
95 "sent_at_ms" => protocol_uint(v).is_some(),
96 "realm" => matches!(v, Value::Bytes(b) if b.len() == 32),
97 "call_id" => matches!(v, Value::Bytes(b) if b.len() == 16),
98 "source_route" => matches!(v, Value::Bytes(_)),
99 _ => true,
100 }
101}
102
103pub fn check_ext(v: &Value) -> Result<(), FrameError> {
108 ext_entries(v).map(|_| ())
109}
110
111pub fn act_on_ext(v: &Value) -> Result<(), FrameError> {
116 let entries = ext_entries(v)?;
117 let refused: Vec<&String> = entries
118 .iter()
119 .filter(|(name, critical)| *critical && !KNOWN_EXTENSIONS.contains(&name.as_str()))
120 .map(|(name, _)| name)
121 .collect();
122 if let Some(first) = refused.first() {
123 refused.iter().for_each(|name| count_ext_refused(name));
124 return Err(FrameError::ExtensionUnsupported((*first).clone()));
125 }
126 for (name, _) in entries
127 .iter()
128 .filter(|(name, _)| !KNOWN_EXTENSIONS.contains(&name.as_str()))
129 {
130 count_ext_ignored(name);
131 }
132 Ok(())
133}
134
135fn ext_entries(v: &Value) -> Result<Vec<(String, bool)>, FrameError> {
137 let Value::Map(pairs) = v else {
138 return Err(FrameError::Malformed);
139 };
140 if pairs.is_empty() || pairs.len() > MAX_EXTENSIONS {
141 return Err(FrameError::Malformed);
142 }
143 pairs
144 .iter()
145 .map(|(key, entry)| match (key, extension_critical(entry)) {
146 (Value::Text(name), Some(critical)) if extension_name(name) => {
147 Ok((name.clone(), critical))
148 }
149 _ => Err(FrameError::Malformed),
150 })
151 .collect()
152}
153
154fn extension_critical(entry: &Value) -> Option<bool> {
157 let Value::Map(fields) = entry else {
158 return None;
159 };
160 let named = |n: &str| fields.iter().filter(|(k, _)| *k == Value::text(n)).count() == 1;
161 if fields.len() != 2 || !named("critical") || !named("value") {
162 return None;
163 }
164 match entry.get("critical") {
165 Some(Value::Int(0)) => Some(false),
166 Some(Value::Int(1)) => Some(true),
167 _ => None,
168 }
169}
170
171fn extension_name(name: &str) -> bool {
172 !name.is_empty()
173 && name.len() <= MAX_EXTENSION_NAME_BYTES
174 && name
175 .bytes()
176 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'_' || c == b'.')
177}