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