1mod check_payload;
12mod neighbour;
13mod publication;
14mod reply;
15mod request;
16mod sealed;
17mod session;
18mod stream;
19
20pub use check_payload::{
21 check_frame, check_payload, FRAME_RESERVED_ELEMENTS, MAX_PAYLOAD_ELEMENTS, MAX_PAYLOAD_NESTING,
22};
23pub use neighbour::{
24 advertise_frame, goodbye_frame, neighbour_signed, sign_neighbour, subscribe_frame,
25 unadvertise_frame, unsubscribe_frame, verify_neighbour, NeighbourLink, NeighbourPeer,
26};
27pub use publication::{sign_publish, verify_publication, PublicationSpec, VerifiedPublication};
28pub use reply::{
29 claimed_reply_ids, sign_provider_error, sign_relay_error, sign_result,
30 sign_sealed_provider_error, sign_sealed_result, verify_relay_error, verify_reply,
31 RelayErrorSpec, RelayErrorType, ReplyType, VerifiedRelayError, VerifiedReply,
32};
33pub use request::{
34 request_fields_accepted, sign_call, sign_stream_open, verify_request, RequestSpec, RequestType,
35 VerifiedRequest, MAX_PROOFS, MAX_PROOFS_BYTES,
36};
37pub use sealed::{Sealed, SEALED_SCHEME};
38pub use session::{
39 liveness_nonce, liveness_ping_frame, liveness_pong_frame, verify_session_frame, Liveness,
40 LIVENESS_NONCE_SIZE,
41};
42pub use stream::{
43 open_stream, sign_caller_stream, sign_provider_stream, verify_caller_stream,
44 verify_provider_stream, StreamEncoding, StreamFields, StreamMode, StreamRole, StreamState,
45 VerifiedStreamFrame,
46};
47
48use std::fmt;
49
50use crate::cbor::{self, Value};
51use crate::node_key::{KeyError, NodeKey, Purpose};
52use crate::signed_object::ObjectError;
53
54pub const PROTOCOL_VERSION: i64 = 2;
56
57pub const MAX_FRAME_BYTES: usize = 0x00FF_FFFF;
59
60const REQUEST_LABEL: &str = "MACULA-PQ-REQUEST-V1";
62const REPLY_LABEL: &str = "MACULA-PQ-REPLY-V1";
63const RELAY_ERROR_LABEL: &str = "MACULA-PQ-RELAY-ERROR-V1";
64const STREAM_LABEL: &str = "MACULA-PQ-STREAM-V1";
65const CALLER_STREAM_LABEL: &str = "MACULA-PQ-CALLER-STREAM-V1";
66const PUBLICATION_LABEL: &str = "MACULA-PQ-PUBLICATION-V1";
67
68const MAX_PROTOCOL_INT: u64 = 1 << 53;
71const MAX_PROCEDURE_BYTES: usize = 512;
72const MAX_ERROR_CODE_BYTES: usize = 64;
73const MAX_ERROR_TEXT_BYTES: usize = 256;
74const MAX_TOPIC_BYTES: usize = 512;
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum FrameError {
79 TooLarge(usize),
81 Malformed,
84 Payload(String),
86 BreaksDecodingRule(String),
88 ProofsOutOfBound,
90 KeyIdMismatch,
92 RequestMismatch,
94 NotTheTarget,
97 NotTheConnection,
99 Unsignable,
101 TextTooLong(String),
103 InvalidText(String),
105 RelayCodeOutsideItsSet,
107 OutOfRange(String),
109 SealedShape,
114 NotAllowed(String),
116 SeqMismatch,
118 StreamEnded,
120 NeighbourSigned,
122 NotYetValid(i64),
124 Expired(i64),
126 SignatureInvalid,
128 Key(KeyError),
130}
131
132impl fmt::Display for FrameError {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 FrameError::TooLarge(n) => write!(
136 f,
137 "a frame of {n} bytes, over the {MAX_FRAME_BYTES}-byte cap"
138 ),
139 FrameError::Malformed => f.write_str("malformed frame"),
140 FrameError::Payload(why) | FrameError::BreaksDecodingRule(why) => f.write_str(why),
141 FrameError::ProofsOutOfBound => {
142 f.write_str("the request's proofs are outside the bound")
143 }
144 FrameError::KeyIdMismatch => {
145 f.write_str("the signer the frame names is not the key it verified with")
146 }
147 FrameError::RequestMismatch => f.write_str("the frame names another request"),
148 FrameError::NotTheTarget => f.write_str("the frame is not from the request's target"),
149 FrameError::NotTheConnection => {
150 f.write_str("the relay error is not from the connection's station")
151 }
152 FrameError::Unsignable => f.write_str("the key cannot sign this frame"),
153 FrameError::TextTooLong(what) => write!(f, "text longer than its bound: {what}"),
154 FrameError::InvalidText(what) => write!(f, "text that is not valid UTF-8: {what}"),
155 FrameError::RelayCodeOutsideItsSet => {
156 f.write_str("a relay error code outside its closed set")
157 }
158 FrameError::OutOfRange(what) => write!(f, "a field outside its range: {what}"),
159 FrameError::SealedShape => {
160 f.write_str("a sealed payload of another shape than its frame's")
161 }
162 FrameError::NotAllowed(what) => {
163 write!(f, "a stream frame its side does not send: {what}")
164 }
165 FrameError::SeqMismatch => f.write_str("a stream frame out of its side's order"),
166 FrameError::StreamEnded => f.write_str("a stream frame after its side's STREAM_END"),
167 FrameError::NeighbourSigned => {
168 f.write_str("the frame already carries a neighbour signature")
169 }
170 FrameError::NotYetValid(ms) => write!(f, "a publication not yet valid, by {ms} ms"),
171 FrameError::Expired(ms) => write!(f, "a publication past its expiry, by {ms} ms"),
172 FrameError::SignatureInvalid => {
173 f.write_str("the signed object's signature does not verify")
174 }
175 FrameError::Key(e) => write!(f, "{e}"),
176 }
177 }
178}
179
180impl std::error::Error for FrameError {}
181
182fn object_refusal(e: ObjectError) -> FrameError {
185 match e {
186 ObjectError::SignatureInvalid => FrameError::SignatureInvalid,
187 ObjectError::Key(k) => FrameError::Key(k),
188 _ => FrameError::Malformed,
189 }
190}
191
192pub fn encode(frame: &Value) -> Result<Vec<u8>, FrameError> {
195 let payload = cbor::encode(frame).map_err(|e| FrameError::Payload(e.to_string()))?;
196 if payload.len() > MAX_FRAME_BYTES {
197 return Err(FrameError::TooLarge(payload.len()));
198 }
199 let mut out = Vec::with_capacity(4 + payload.len());
200 out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
201 out.extend_from_slice(&payload);
202 Ok(out)
203}
204
205#[derive(Debug, Clone, PartialEq)]
207pub enum Decoded {
208 Complete { frame: Value, consumed: usize },
210 NeedMore(usize),
212}
213
214pub fn decode(buf: &[u8]) -> Result<Decoded, FrameError> {
217 let Some((header, rest)) = buf.split_first_chunk::<4>() else {
218 return Ok(Decoded::NeedMore(4 - buf.len()));
219 };
220 let length = u32::from_be_bytes(*header) as usize;
221 if length > MAX_FRAME_BYTES {
222 return Err(FrameError::TooLarge(length));
223 }
224 if rest.len() < length {
225 return Ok(Decoded::NeedMore(length - rest.len()));
226 }
227 let frame = cbor::decode(&rest[..length]).map_err(|_| FrameError::Malformed)?;
228 Ok(Decoded::Complete {
229 frame,
230 consumed: 4 + length,
231 })
232}
233
234#[derive(Debug, Clone, Copy)]
236enum Rule {
237 Any,
238 AnyBytes,
239 BytesOf(usize),
240 TextWithin(usize),
241 TextIn(&'static [&'static str]),
242 ProtocolUint,
243 ProtocolVersion,
244 CarriedObject,
245 HeldObject,
246 StreamObject,
247 Proofs,
248 Sealed(sealed::SealedContext),
249}
250
251impl Rule {
252 fn accepts(self, v: &Value) -> bool {
253 match self {
254 Rule::Any => true,
255 Rule::AnyBytes => matches!(v, Value::Bytes(_)),
256 Rule::BytesOf(n) => matches!(v, Value::Bytes(b) if b.len() == n),
257 Rule::TextWithin(n) => matches!(v, Value::Text(t) if t.len() <= n),
258 Rule::TextIn(names) => matches!(v, Value::Text(t) if names.contains(&t.as_str())),
259 Rule::ProtocolUint => protocol_uint(v).is_some(),
260 Rule::ProtocolVersion => {
261 matches!(v, Value::Int(n) if *n == i128::from(PROTOCOL_VERSION))
262 }
263 Rule::CarriedObject => crate::signed_object::Object::from_value(v).is_ok(),
264 Rule::HeldObject => crate::signed_object::HeldObject::from_value(v).is_ok(),
265 Rule::StreamObject => Rule::CarriedObject.accepts(v) || Rule::HeldObject.accepts(v),
266 Rule::Proofs => request::proofs_within_bound(v),
267 Rule::Sealed(context) => sealed::read_sealed(v, context).is_some(),
268 }
269 }
270}
271
272type Fields = std::collections::HashMap<String, Value>;
274
275fn read_fields(v: &Value, table: &[(&str, Rule)]) -> Option<Fields> {
279 let Value::Map(pairs) = v else {
280 return None;
281 };
282 let mut fields = Fields::with_capacity(pairs.len());
283 for (key, value) in pairs {
284 let Value::Text(name) = key else {
285 return None;
286 };
287 let rule = table.iter().find(|(n, _)| *n == name)?.1;
288 if fields.contains_key(name) || !rule.accepts(value) {
289 return None;
290 }
291 fields.insert(name.clone(), value.clone());
292 }
293 Some(fields)
294}
295
296fn has_fields(fields: &Fields, names: &[&str]) -> bool {
297 names.iter().all(|n| fields.contains_key(*n))
298}
299
300fn received_frame(
305 v: &Value,
306 object_name: &str,
307 object_rule: Rule,
308 routes: &[(&str, Rule)],
309 types: &'static [&'static str],
310) -> Option<(String, Value)> {
311 let mut table = vec![
312 ("version", Rule::ProtocolVersion),
313 ("frame_type", Rule::TextIn(types)),
314 (object_name, object_rule),
315 ];
316 table.extend_from_slice(routes);
317 let fields = read_fields(v, &table)?;
318 if !has_fields(&fields, &["version", "frame_type", object_name]) {
319 return None;
320 }
321 Some((text_of(&fields["frame_type"]), fields[object_name].clone()))
322}
323
324fn bounded_text(field: &str, text: &[u8], max: usize) -> Result<(), FrameError> {
327 if text.len() > max {
328 return Err(FrameError::TextTooLong(format!(
329 "a {field} of {} bytes, over {max}",
330 text.len()
331 )));
332 }
333 if std::str::from_utf8(text).is_err() {
334 return Err(FrameError::InvalidText(format!("the {field}")));
335 }
336 Ok(())
337}
338
339fn identity_signer(key: &NodeKey) -> Result<(), FrameError> {
341 if key.purpose() != Purpose::Identity {
342 return Err(FrameError::Unsignable);
343 }
344 Ok(())
345}
346
347fn protocol_uint(v: &Value) -> Option<u64> {
348 match v {
349 Value::Int(n) if *n >= 0 && *n < i128::from(MAX_PROTOCOL_INT) => Some(*n as u64),
350 _ => None,
351 }
352}
353
354fn text_of(v: &Value) -> String {
355 match v {
356 Value::Text(t) => t.clone(),
357 _ => String::new(),
358 }
359}
360
361fn bytes_of(v: &Value) -> Vec<u8> {
362 match v {
363 Value::Bytes(b) => b.clone(),
364 _ => Vec::new(),
365 }
366}
367
368fn fixed<const N: usize>(v: &Value) -> [u8; N] {
369 let mut out = [0u8; N];
370 if let Value::Bytes(b) = v {
371 if b.len() == N {
372 out.copy_from_slice(b);
373 }
374 }
375 out
376}
377
378fn entry(name: &str, value: Value) -> (Value, Value) {
379 (Value::text(name), value)
380}
381
382fn uint(n: u64) -> Value {
383 Value::Int(i128::from(n))
384}
385
386fn names_request(fields: &Fields, request: &VerifiedRequest) -> bool {
389 fields.get("request_id") == Some(&Value::Bytes(request.request_id.to_vec()))
390 && fields.get("request_hash") == Some(&Value::Bytes(request.request_hash.to_vec()))
391}
392
393fn base(frame_type: &str) -> Vec<(Value, Value)> {
397 vec![
398 entry("version", Value::Int(i128::from(PROTOCOL_VERSION))),
399 entry("frame_type", Value::text(frame_type)),
400 entry("frame_id", Value::Bytes(crate::uuid_v7::new().to_vec())),
401 entry("sent_at_ms", uint(crate::uuid_v7::now_ms())),
402 entry("capabilities", uint(0)),
403 entry("realm", Value::Null),
404 entry("call_id", Value::Null),
405 entry("source_route", Value::Null),
406 ]
407}
408
409fn with_field(mut fields: Vec<(Value, Value)>, key: &str, value: Value) -> Vec<(Value, Value)> {
412 match fields.iter_mut().find(|(k, _)| *k == Value::text(key)) {
413 Some(slot) => slot.1 = value,
414 None => fields.push(entry(key, value)),
415 }
416 fields
417}