1mod check_payload;
12mod control;
13mod envelope;
14mod publication;
15mod register_ack;
16mod reply;
17mod request;
18mod sealed;
19mod session;
20mod stream;
21#[cfg(test)]
22mod wire_1_0_tests;
23mod wire_counters;
24
25pub use check_payload::{
26 check_frame, check_payload, FRAME_RESERVED_ELEMENTS, MAX_PAYLOAD_ELEMENTS, MAX_PAYLOAD_NESTING,
27};
28pub use control::{
29 advertise_frame, goodbye_frame, is_control_frame, read_control, subscribe_frame,
30 unadvertise_frame, unsubscribe_frame,
31};
32pub use envelope::{
33 act_on_ext, check_ext, read_envelope, unknown_frame_type, Envelope, MAX_EXTENSIONS,
34 MAX_EXTENSION_NAME_BYTES, MAX_FRAME_TYPE_BYTES, QUIC_CODE_UNKNOWN_FRAME_TYPE,
35};
36pub use publication::{
37 delivered_via, sign_publish, verify_publication, PublicationSpec, VerifiedPublication,
38};
39pub use register_ack::{read_register_ack, Answered, RegisterAck, RegisterKind};
40pub use reply::{
41 claimed_reply_ids, sign_provider_error, sign_relay_error, sign_result,
42 sign_sealed_provider_error, sign_sealed_result, verify_relay_error, verify_reply,
43 RelayErrorSpec, RelayErrorType, ReplyType, VerifiedRelayError, VerifiedReply,
44};
45pub use request::{
46 request_fields_accepted, sign_call, sign_stream_open, verify_request, RequestSpec, RequestType,
47 VerifiedRequest, MAX_PROOFS, MAX_PROOFS_BYTES,
48};
49pub use sealed::{Sealed, SEALED_SCHEME};
50pub use session::{
51 liveness_nonce, liveness_ping_frame, liveness_pong_frame, Liveness, LIVENESS_NONCE_SIZE,
52};
53pub use stream::{
54 open_stream, sign_caller_stream, sign_provider_stream, verify_caller_stream,
55 verify_provider_stream, StreamEncoding, StreamFields, StreamMode, StreamRole, StreamState,
56 VerifiedStreamFrame,
57};
58pub(crate) use wire_counters::count_enum_fallback;
59pub use wire_counters::{wire_counters, WireCounts};
60
61use std::fmt;
62
63use crate::cbor::{self, Value};
64use crate::node_key::{KeyError, NodeKey, Purpose};
65use crate::signed_object::ObjectError;
66
67pub const PROTOCOL_VERSION: i64 = 5;
70
71pub const MAX_FRAME_BYTES: usize = 0x00FF_FFFF;
73
74const REQUEST_LABEL: &str = "MACULA-PQ-REQUEST-V1";
76const REPLY_LABEL: &str = "MACULA-PQ-REPLY-V1";
77const RELAY_ERROR_LABEL: &str = "MACULA-PQ-RELAY-ERROR-V1";
78const STREAM_LABEL: &str = "MACULA-PQ-STREAM-V1";
79const CALLER_STREAM_LABEL: &str = "MACULA-PQ-CALLER-STREAM-V1";
80const PUBLICATION_LABEL: &str = "MACULA-PQ-PUBLICATION-V1";
81
82const MAX_PROTOCOL_INT: u64 = 1 << 53;
85const MAX_PROCEDURE_BYTES: usize = 512;
86const MAX_ERROR_CODE_BYTES: usize = 64;
87const MAX_ERROR_TEXT_BYTES: usize = 256;
88const MAX_TOPIC_BYTES: usize = 512;
89const MAX_ENUM_VALUE_BYTES: usize = 64;
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum FrameError {
95 TooLarge(usize),
97 Malformed,
100 Payload(String),
102 BreaksDecodingRule(String),
104 ProofsOutOfBound,
106 KeyIdMismatch,
108 RequestMismatch,
110 NotTheTarget,
113 NotTheConnection,
115 Unsignable,
117 TextTooLong(String),
119 InvalidText(String),
121 RelayCodeOutsideItsSet,
123 OutOfRange(String),
125 SealedShape,
130 NotAllowed(String),
132 SeqMismatch,
134 StreamEnded,
136 NotYetValid(i64),
138 Expired(i64),
140 SignatureInvalid,
142 ExtensionUnsupported(String),
145 EnumUnknown(String),
148 Key(KeyError),
150}
151
152impl fmt::Display for FrameError {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 FrameError::TooLarge(n) => write!(
156 f,
157 "a frame of {n} bytes, over the {MAX_FRAME_BYTES}-byte cap"
158 ),
159 FrameError::Malformed => f.write_str("malformed frame"),
160 FrameError::Payload(why) | FrameError::BreaksDecodingRule(why) => f.write_str(why),
161 FrameError::ProofsOutOfBound => {
162 f.write_str("the request's proofs are outside the bound")
163 }
164 FrameError::KeyIdMismatch => {
165 f.write_str("the signer the frame names is not the key it verified with")
166 }
167 FrameError::RequestMismatch => f.write_str("the frame names another request"),
168 FrameError::NotTheTarget => f.write_str("the frame is not from the request's target"),
169 FrameError::NotTheConnection => {
170 f.write_str("the relay error is not from the connection's station")
171 }
172 FrameError::Unsignable => f.write_str("the key cannot sign this frame"),
173 FrameError::TextTooLong(what) => write!(f, "text longer than its bound: {what}"),
174 FrameError::InvalidText(what) => write!(f, "text that is not valid UTF-8: {what}"),
175 FrameError::RelayCodeOutsideItsSet => {
176 f.write_str("a relay error code outside its closed set")
177 }
178 FrameError::OutOfRange(what) => write!(f, "a field outside its range: {what}"),
179 FrameError::SealedShape => {
180 f.write_str("a sealed payload of another shape than its frame's")
181 }
182 FrameError::NotAllowed(what) => {
183 write!(f, "a stream frame its side does not send: {what}")
184 }
185 FrameError::SeqMismatch => f.write_str("a stream frame out of its side's order"),
186 FrameError::StreamEnded => f.write_str("a stream frame after its side's STREAM_END"),
187 FrameError::NotYetValid(ms) => write!(f, "a publication not yet valid, by {ms} ms"),
188 FrameError::Expired(ms) => write!(f, "a publication past its expiry, by {ms} ms"),
189 FrameError::SignatureInvalid => {
190 f.write_str("the signed object's signature does not verify")
191 }
192 FrameError::ExtensionUnsupported(name) => {
193 write!(f, "a critical extension this node does not know: {name}")
194 }
195 FrameError::EnumUnknown(field) => {
196 write!(f, "an enum value this node does not know, in {field}")
197 }
198 FrameError::Key(e) => write!(f, "{e}"),
199 }
200 }
201}
202
203impl std::error::Error for FrameError {}
204
205fn object_refusal(e: ObjectError) -> FrameError {
208 match e {
209 ObjectError::SignatureInvalid => FrameError::SignatureInvalid,
210 ObjectError::Key(k) => FrameError::Key(k),
211 _ => FrameError::Malformed,
212 }
213}
214
215pub fn encode(frame: &Value) -> Result<Vec<u8>, FrameError> {
218 let payload = cbor::encode(frame).map_err(|e| FrameError::Payload(e.to_string()))?;
219 if payload.len() > MAX_FRAME_BYTES {
220 return Err(FrameError::TooLarge(payload.len()));
221 }
222 let mut out = Vec::with_capacity(4 + payload.len());
223 out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
224 out.extend_from_slice(&payload);
225 Ok(out)
226}
227
228#[derive(Debug, Clone, PartialEq)]
230pub enum Decoded {
231 Complete { frame: Value, consumed: usize },
233 NeedMore(usize),
235}
236
237pub fn decode(buf: &[u8]) -> Result<Decoded, FrameError> {
240 let Some((header, rest)) = buf.split_first_chunk::<4>() else {
241 return Ok(Decoded::NeedMore(4 - buf.len()));
242 };
243 let length = u32::from_be_bytes(*header) as usize;
244 if length > MAX_FRAME_BYTES {
245 return Err(FrameError::TooLarge(length));
246 }
247 if rest.len() < length {
248 return Ok(Decoded::NeedMore(length - rest.len()));
249 }
250 let frame = cbor::decode(&rest[..length]).map_err(|_| FrameError::Malformed)?;
251 Ok(Decoded::Complete {
252 frame,
253 consumed: 4 + length,
254 })
255}
256
257#[derive(Debug, Clone, Copy)]
259enum Rule {
260 Any,
261 AnyBytes,
262 BytesOf(usize),
263 TextWithin(usize),
264 TextIn(&'static [&'static str]),
265 ProtocolUint,
266 ProtocolVersion,
267 CarriedObject,
268 HeldObject,
269 StreamObject,
270 Proofs,
271 Sealed(sealed::SealedContext),
272 EnumText,
275 ZeroOrOne,
277 Ext,
279}
280
281impl Rule {
282 fn accepts(self, v: &Value) -> bool {
283 match self {
284 Rule::Any => true,
285 Rule::AnyBytes => matches!(v, Value::Bytes(_)),
286 Rule::BytesOf(n) => matches!(v, Value::Bytes(b) if b.len() == n),
287 Rule::TextWithin(n) => matches!(v, Value::Text(t) if t.len() <= n),
288 Rule::TextIn(names) => matches!(v, Value::Text(t) if names.contains(&t.as_str())),
289 Rule::ProtocolUint => protocol_uint(v).is_some(),
290 Rule::ProtocolVersion => {
291 matches!(v, Value::Int(n) if *n == i128::from(PROTOCOL_VERSION))
292 }
293 Rule::CarriedObject => crate::signed_object::Object::from_value(v).is_ok(),
294 Rule::HeldObject => crate::signed_object::HeldObject::from_value(v).is_ok(),
295 Rule::StreamObject => Rule::CarriedObject.accepts(v) || Rule::HeldObject.accepts(v),
296 Rule::Proofs => request::proofs_within_bound(v),
297 Rule::Sealed(context) => sealed::read_sealed(v, context).is_some(),
298 Rule::ZeroOrOne => matches!(v, Value::Int(0 | 1)),
299 Rule::Ext => check_ext(v).is_ok(),
300 Rule::EnumText => {
301 matches!(v, Value::Text(t) if !t.is_empty() && t.len() <= MAX_ENUM_VALUE_BYTES)
302 }
303 }
304 }
305}
306
307type Fields = std::collections::HashMap<String, Value>;
309
310fn read_fields(v: &Value, table: &[(&str, Rule)]) -> Option<Fields> {
314 let Value::Map(pairs) = v else {
315 return None;
316 };
317 let mut fields = Fields::with_capacity(pairs.len());
318 for (key, value) in pairs {
319 let Value::Text(name) = key else {
320 return None;
321 };
322 let rule = table.iter().find(|(n, _)| *n == name)?.1;
323 if fields.contains_key(name) || !rule.accepts(value) {
324 return None;
325 }
326 fields.insert(name.clone(), value.clone());
327 }
328 Some(fields)
329}
330
331fn signed_ext(fields: &Fields) -> Result<(), FrameError> {
335 match fields.get("ext") {
336 Some(ext) => act_on_ext(ext),
337 None => Ok(()),
338 }
339}
340
341fn tbs_enum(fields: &Fields, field: &str, known: &[&str]) -> Result<(), FrameError> {
346 match fields.get(field) {
347 Some(Value::Text(t)) if !known.contains(&t.as_str()) => {
348 Err(FrameError::EnumUnknown(field.to_string()))
349 }
350 _ => Ok(()),
351 }
352}
353
354fn has_fields(fields: &Fields, names: &[&str]) -> bool {
355 names.iter().all(|n| fields.contains_key(*n))
356}
357
358fn received_frame(
363 v: &Value,
364 object_name: &str,
365 object_rule: Rule,
366 routes: &[(&str, Rule)],
367 types: &'static [&'static str],
368) -> Option<(String, Value)> {
369 let mut table = vec![
370 ("version", Rule::ProtocolVersion),
371 ("frame_type", Rule::TextIn(types)),
372 ("frame_id", Rule::BytesOf(16)),
373 ("sent_at_ms", Rule::ProtocolUint),
374 (object_name, object_rule),
375 ];
376 table.extend_from_slice(routes);
377 let fields = read_fields(v, &table)?;
378 if !has_fields(&fields, &["version", "frame_type", object_name]) {
379 return None;
380 }
381 Some((text_of(&fields["frame_type"]), fields[object_name].clone()))
382}
383
384fn bounded_text(field: &str, text: &[u8], max: usize) -> Result<(), FrameError> {
387 if text.len() > max {
388 return Err(FrameError::TextTooLong(format!(
389 "a {field} of {} bytes, over {max}",
390 text.len()
391 )));
392 }
393 if std::str::from_utf8(text).is_err() {
394 return Err(FrameError::InvalidText(format!("the {field}")));
395 }
396 Ok(())
397}
398
399fn identity_signer(key: &NodeKey) -> Result<(), FrameError> {
401 if key.purpose() != Purpose::Identity {
402 return Err(FrameError::Unsignable);
403 }
404 Ok(())
405}
406
407fn protocol_uint(v: &Value) -> Option<u64> {
408 match v {
409 Value::Int(n) if *n >= 0 && *n < i128::from(MAX_PROTOCOL_INT) => Some(*n as u64),
410 _ => None,
411 }
412}
413
414fn text_of(v: &Value) -> String {
415 match v {
416 Value::Text(t) => t.clone(),
417 _ => String::new(),
418 }
419}
420
421fn bytes_of(v: &Value) -> Vec<u8> {
422 match v {
423 Value::Bytes(b) => b.clone(),
424 _ => Vec::new(),
425 }
426}
427
428fn fixed<const N: usize>(v: &Value) -> [u8; N] {
429 let mut out = [0u8; N];
430 if let Value::Bytes(b) = v {
431 if b.len() == N {
432 out.copy_from_slice(b);
433 }
434 }
435 out
436}
437
438fn entry(name: &str, value: Value) -> (Value, Value) {
439 (Value::text(name), value)
440}
441
442fn uint(n: u64) -> Value {
443 Value::Int(i128::from(n))
444}
445
446fn names_request(fields: &Fields, request: &VerifiedRequest) -> bool {
449 fields.get("request_id") == Some(&Value::Bytes(request.request_id.to_vec()))
450 && fields.get("request_hash") == Some(&Value::Bytes(request.request_hash.to_vec()))
451}
452
453fn base(frame_type: &str) -> Vec<(Value, Value)> {
457 vec![
458 entry("version", Value::Int(i128::from(PROTOCOL_VERSION))),
459 entry("frame_type", Value::text(frame_type)),
460 entry("frame_id", Value::Bytes(crate::uuid_v7::new().to_vec())),
461 entry("sent_at_ms", uint(crate::uuid_v7::now_ms())),
462 ]
463}
464
465fn with_field(mut fields: Vec<(Value, Value)>, key: &str, value: Value) -> Vec<(Value, Value)> {
468 match fields.iter_mut().find(|(k, _)| *k == Value::text(key)) {
469 Some(slot) => slot.1 = value,
470 None => fields.push(entry(key, value)),
471 }
472 fields
473}