Skip to main content

macula_rust/frame/
reply.rs

1//! Replies and relay errors (D25): a provider's RESULT or ERROR, signed under
2//! MACULA-PQ-REPLY-V1 by the request's target, and a station's relay ERROR or
3//! STREAM_ERROR, signed under MACULA-PQ-RELAY-ERROR-V1 with a code from a
4//! closed set and no free text.
5
6use crate::cbor::{self, Value};
7use crate::node_key::{node_id_of, NodeKey};
8use crate::profile::Profile;
9use crate::signed_object::{sign_object, verify_object, Object};
10
11use super::sealed::{clear_or_sealed, sealed_field, Sealed, SealedContext};
12use super::{
13    bounded_text, check_payload, entry, fixed, has_fields, identity_signer, names_request,
14    object_refusal, read_fields, received_frame, text_of, FrameError, Rule, VerifiedRequest,
15    MAX_ERROR_CODE_BYTES, MAX_ERROR_TEXT_BYTES, PROTOCOL_VERSION, RELAY_ERROR_LABEL, REPLY_LABEL,
16};
17
18const RESULT: &str = "result";
19const ERROR: &str = "error";
20const STREAM_ERROR: &str = "stream_error";
21
22/// The closed set of relay error codes, disjoint from every provider code.
23/// The relay codes, a closed set disjoint from every provider code
24/// (DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS, macula#54).
25const RELAY_CODES: &[&str] = &["unknown_next_peer", "overloaded"];
26
27/// A reply's frame type.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ReplyType {
30    Result,
31    Error,
32}
33
34/// A relay error's frame type.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum RelayErrorType {
37    Error,
38    StreamError,
39}
40
41impl RelayErrorType {
42    fn name(self) -> &'static str {
43        match self {
44            RelayErrorType::Error => ERROR,
45            RelayErrorType::StreamError => STREAM_ERROR,
46        }
47    }
48}
49
50/// A provider's RESULT or ERROR that verified for its request: the node that
51/// responded, a RESULT's payload, and an ERROR's code and detail; or, sealed
52/// end to end, `sealed` in their place.
53#[derive(Debug, Clone, PartialEq)]
54pub struct VerifiedReply {
55    pub frame_type: ReplyType,
56    pub responded_by: [u8; 32],
57    pub payload: Option<Value>,
58    pub code: Option<String>,
59    pub detail: Option<String>,
60    pub sealed: Option<Sealed>,
61}
62
63/// A station's relay error as it gives it: for a pending verified request, a
64/// code from the closed set, the hop that failed, and a routing field outside
65/// the signature.
66#[derive(Debug, Clone, PartialEq)]
67pub struct RelayErrorSpec {
68    pub frame_type: RelayErrorType,
69    pub request: VerifiedRequest,
70    pub code: String,
71    pub offending_hop: Option<[u8; 32]>,
72    pub source_route_partial: Option<Vec<u8>>,
73}
74
75/// A relay error that verified for its request: the station that reported
76/// it, its code, and the hop that failed.
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct VerifiedRelayError {
79    pub frame_type: RelayErrorType,
80    pub reported_by: [u8; 32],
81    pub code: String,
82    pub offending_hop: Option<[u8; 32]>,
83}
84
85/// Signs a provider's RESULT for a verified request: responded_by is the
86/// key's key id, which must be the request's target, and the payload one the
87/// wire carries. `source_route_reverse` rides outside the signature.
88pub fn sign_result(
89    request: &VerifiedRequest,
90    payload: &Value,
91    source_route_reverse: Option<Vec<u8>>,
92    key: &NodeKey,
93) -> Result<Value, FrameError> {
94    reply_signer(request, key)?;
95    check_payload(payload)?;
96    sign_reply(
97        ReplyType::Result,
98        request,
99        vec![entry("payload", payload.clone())],
100        source_route_reverse,
101        key,
102    )
103}
104
105/// Signs a provider's ERROR for a verified request, with [`sign_result`]'s
106/// key check: a code of at most 64 bytes and a detail of at most 256.
107pub fn sign_provider_error(
108    request: &VerifiedRequest,
109    code: &str,
110    detail: Option<&str>,
111    source_route_reverse: Option<Vec<u8>>,
112    key: &NodeKey,
113) -> Result<Value, FrameError> {
114    reply_signer(request, key)?;
115    bounded_text("code", code.as_bytes(), MAX_ERROR_CODE_BYTES)?;
116    let mut fields = vec![entry("code", Value::text(code))];
117    if let Some(detail) = detail {
118        bounded_text("detail", detail.as_bytes(), MAX_ERROR_TEXT_BYTES)?;
119        fields.push(entry("detail", Value::text(detail)));
120    }
121    sign_reply(ReplyType::Error, request, fields, source_route_reverse, key)
122}
123
124/// Signs a provider's RESULT whose payload is sealed end to end, carried in
125/// place of the payload, with [`sign_result`]'s key check.
126pub fn sign_sealed_result(
127    request: &VerifiedRequest,
128    sealed: &Sealed,
129    source_route_reverse: Option<Vec<u8>>,
130    key: &NodeKey,
131) -> Result<Value, FrameError> {
132    sign_sealed_reply(
133        ReplyType::Result,
134        request,
135        sealed,
136        source_route_reverse,
137        key,
138    )
139}
140
141/// Signs a provider's ERROR whose code and detail are sealed end to end, as
142/// cbor([code, detail]), carried in their place.
143pub fn sign_sealed_provider_error(
144    request: &VerifiedRequest,
145    sealed: &Sealed,
146    source_route_reverse: Option<Vec<u8>>,
147    key: &NodeKey,
148) -> Result<Value, FrameError> {
149    sign_sealed_reply(ReplyType::Error, request, sealed, source_route_reverse, key)
150}
151
152fn sign_sealed_reply(
153    frame_type: ReplyType,
154    request: &VerifiedRequest,
155    sealed: &Sealed,
156    source_route_reverse: Option<Vec<u8>>,
157    key: &NodeKey,
158) -> Result<Value, FrameError> {
159    reply_signer(request, key)?;
160    if !sealed.shaped(SealedContext::Reply) {
161        return Err(FrameError::SealedShape);
162    }
163    sign_reply(
164        frame_type,
165        request,
166        vec![entry("sealed", sealed.value())],
167        source_route_reverse,
168        key,
169    )
170}
171
172fn reply_signer(request: &VerifiedRequest, key: &NodeKey) -> Result<(), FrameError> {
173    identity_signer(key)?;
174    if key.key_id() != request.target {
175        return Err(FrameError::Unsignable);
176    }
177    Ok(())
178}
179
180fn sign_reply(
181    frame_type: ReplyType,
182    request: &VerifiedRequest,
183    mut fields: Vec<(Value, Value)>,
184    source_route_reverse: Option<Vec<u8>>,
185    key: &NodeKey,
186) -> Result<Value, FrameError> {
187    let name = match frame_type {
188        ReplyType::Result => RESULT,
189        ReplyType::Error => ERROR,
190    };
191    fields.extend([
192        entry("frame_type", Value::text(name)),
193        entry("request_id", Value::Bytes(request.request_id.to_vec())),
194        entry("request_hash", Value::Bytes(request.request_hash.to_vec())),
195        entry("responded_by", Value::Bytes(key.key_id().to_vec())),
196    ]);
197    let reply = sign_object(REPLY_LABEL, &fields, key).map_err(object_refusal)?;
198    Ok(routed_frame(
199        name,
200        "reply",
201        &reply,
202        "source_route_reverse",
203        source_route_reverse,
204    ))
205}
206
207pub(super) const REPLY_ROUTES: &[(&str, Rule)] = &[("source_route_reverse", Rule::AnyBytes)];
208pub(super) const RELAY_ERROR_ROUTES: &[(&str, Rule)] = &[("source_route_partial", Rule::AnyBytes)];
209
210/// Verifies a received RESULT or provider ERROR for the request it answers:
211/// the frame's shape, the reply's signature and fields, responded_by as the
212/// key id of its key, the request's request_id and request_hash, and
213/// responded_by as the request's target.
214pub fn verify_reply(
215    frame: &Value,
216    request: &VerifiedRequest,
217    profile: Profile,
218) -> Result<VerifiedReply, FrameError> {
219    let (frame_type, object) = received_frame(
220        frame,
221        "reply",
222        Rule::CarriedObject,
223        REPLY_ROUTES,
224        &[RESULT, ERROR],
225    )
226    .ok_or(FrameError::Malformed)?;
227    let verified = verify_object(REPLY_LABEL, &object, profile).map_err(object_refusal)?;
228    let fields =
229        read_fields(&verified.fields, &reply_table(&frame_type)).ok_or(FrameError::Malformed)?;
230    let (has_payload, has_code, has_detail, has_sealed) = (
231        fields.contains_key("payload"),
232        fields.contains_key("code"),
233        fields.contains_key("detail"),
234        fields.contains_key("sealed"),
235    );
236    let shaped = match (frame_type == RESULT, has_sealed) {
237        (true, _) => clear_or_sealed(&fields, "payload") && !has_code && !has_detail,
238        (false, true) => !has_code && !has_detail && !has_payload,
239        (false, false) => has_code && !has_payload,
240    };
241    if !has_fields(
242        &fields,
243        &["frame_type", "request_id", "request_hash", "responded_by"],
244    ) || !shaped
245    {
246        return Err(FrameError::Malformed);
247    }
248    super::signed_ext(&fields)?;
249    let reply = VerifiedReply {
250        frame_type: if frame_type == RESULT {
251            ReplyType::Result
252        } else {
253            ReplyType::Error
254        },
255        responded_by: fixed(&fields["responded_by"]),
256        payload: fields.get("payload").cloned(),
257        code: fields.get("code").map(text_of),
258        detail: fields.get("detail").map(text_of),
259        sealed: sealed_field(&fields, SealedContext::Reply),
260    };
261    if reply.responded_by != node_id_of(&verified.key, profile) {
262        return Err(FrameError::KeyIdMismatch);
263    }
264    if !names_request(&fields, request) {
265        return Err(FrameError::RequestMismatch);
266    }
267    if reply.responded_by != request.target {
268        return Err(FrameError::NotTheTarget);
269    }
270    Ok(reply)
271}
272
273fn reply_table(frame_type: &str) -> Vec<(&'static str, Rule)> {
274    vec![
275        (
276            "frame_type",
277            Rule::TextIn(if frame_type == RESULT {
278                &[RESULT]
279            } else {
280                &[ERROR]
281            }),
282        ),
283        ("alg", Rule::Any),
284        ("request_id", Rule::BytesOf(16)),
285        ("request_hash", Rule::BytesOf(48)),
286        ("responded_by", Rule::BytesOf(32)),
287        ("payload", Rule::Any),
288        ("code", Rule::TextWithin(MAX_ERROR_CODE_BYTES)),
289        ("detail", Rule::TextWithin(MAX_ERROR_TEXT_BYTES)),
290        ("sealed", Rule::Sealed(SealedContext::Reply)),
291        ("ext", Rule::Ext),
292    ]
293}
294
295/// Signs a station's relay error with its identity key: reported_by is the
296/// key's key id. Refused, in this order: a key that is not an identity key, a
297/// code outside the closed set.
298pub fn sign_relay_error(spec: &RelayErrorSpec, key: &NodeKey) -> Result<Value, FrameError> {
299    identity_signer(key)?;
300    if !RELAY_CODES.contains(&spec.code.as_str()) {
301        return Err(FrameError::RelayCodeOutsideItsSet);
302    }
303    let name = spec.frame_type.name();
304    let mut fields = vec![
305        entry("frame_type", Value::text(name)),
306        entry("request_id", Value::Bytes(spec.request.request_id.to_vec())),
307        entry(
308            "request_hash",
309            Value::Bytes(spec.request.request_hash.to_vec()),
310        ),
311        entry("reported_by", Value::Bytes(key.key_id().to_vec())),
312        entry("code", Value::text(spec.code.clone())),
313    ];
314    if let Some(hop) = spec.offending_hop {
315        fields.push(entry("offending_hop", Value::Bytes(hop.to_vec())));
316    }
317    let relay_error = sign_object(RELAY_ERROR_LABEL, &fields, key).map_err(object_refusal)?;
318    Ok(routed_frame(
319        name,
320        "relay_error",
321        &relay_error,
322        "source_route_partial",
323        spec.source_route_partial.clone(),
324    ))
325}
326
327/// Verifies a received relay error for the pending request it names, from
328/// the station the connection authenticated, `expected_reporter`.
329pub fn verify_relay_error(
330    frame: &Value,
331    request: &VerifiedRequest,
332    profile: Profile,
333    expected_reporter: &[u8; 32],
334) -> Result<VerifiedRelayError, FrameError> {
335    let (frame_type, object) = received_frame(
336        frame,
337        "relay_error",
338        Rule::CarriedObject,
339        RELAY_ERROR_ROUTES,
340        &[ERROR, STREAM_ERROR],
341    )
342    .ok_or(FrameError::Malformed)?;
343    let verified = verify_object(RELAY_ERROR_LABEL, &object, profile).map_err(object_refusal)?;
344    let fields = read_fields(&verified.fields, &relay_error_table(&frame_type))
345        .ok_or(FrameError::Malformed)?;
346    if !has_fields(
347        &fields,
348        &[
349            "frame_type",
350            "request_id",
351            "request_hash",
352            "reported_by",
353            "code",
354        ],
355    ) {
356        return Err(FrameError::Malformed);
357    }
358    super::tbs_enum(&fields, "code", RELAY_CODES)?;
359    super::signed_ext(&fields)?;
360    let relay_error = VerifiedRelayError {
361        frame_type: if frame_type == ERROR {
362            RelayErrorType::Error
363        } else {
364            RelayErrorType::StreamError
365        },
366        reported_by: fixed(&fields["reported_by"]),
367        code: text_of(&fields["code"]),
368        offending_hop: fields.get("offending_hop").map(fixed),
369    };
370    if relay_error.reported_by != node_id_of(&verified.key, profile) {
371        return Err(FrameError::KeyIdMismatch);
372    }
373    if !names_request(&fields, request) {
374        return Err(FrameError::RequestMismatch);
375    }
376    if &relay_error.reported_by != expected_reporter {
377        return Err(FrameError::NotTheConnection);
378    }
379    Ok(relay_error)
380}
381
382fn relay_error_table(frame_type: &str) -> Vec<(&'static str, Rule)> {
383    vec![
384        (
385            "frame_type",
386            Rule::TextIn(if frame_type == ERROR {
387                &[ERROR]
388            } else {
389                &[STREAM_ERROR]
390            }),
391        ),
392        ("alg", Rule::Any),
393        ("request_id", Rule::BytesOf(16)),
394        ("request_hash", Rule::BytesOf(48)),
395        ("reported_by", Rule::BytesOf(32)),
396        ("code", Rule::EnumText),
397        ("offending_hop", Rule::BytesOf(32)),
398        ("ext", Rule::Ext),
399    ]
400}
401
402/// The request_id and request_hash a received reply or relay error names,
403/// read without verifying it: a key for finding the pending request and
404/// nothing more. The frame's fields and the signed object's shape are checked
405/// as the verifiers check them, so ids of another length or shape never come
406/// back.
407pub fn claimed_reply_ids(frame: &Value) -> Result<([u8; 16], [u8; 48]), FrameError> {
408    let frame_type = frame.get("frame_type").map(text_of).unwrap_or_default();
409    let (object_name, routes, table) = match (frame.get("reply"), frame.get("relay_error")) {
410        (Some(_), _) if frame_type == RESULT || frame_type == ERROR => {
411            ("reply", REPLY_ROUTES, reply_table(&frame_type))
412        }
413        (_, Some(_)) if frame_type == ERROR || frame_type == STREAM_ERROR => (
414            "relay_error",
415            RELAY_ERROR_ROUTES,
416            relay_error_table(&frame_type),
417        ),
418        _ => return Err(FrameError::Malformed),
419    };
420    let types: &'static [&'static str] = match frame_type.as_str() {
421        RESULT => &[RESULT],
422        ERROR => &[ERROR],
423        _ => &[STREAM_ERROR],
424    };
425    let (_, object) = received_frame(frame, object_name, Rule::CarriedObject, routes, types)
426        .ok_or(FrameError::Malformed)?;
427    let parsed = Object::from_value(&object).map_err(|_| FrameError::Malformed)?;
428    let tbs = cbor::decode(&parsed.tbs).map_err(|_| FrameError::Malformed)?;
429    let fields = read_fields(&tbs, &table).ok_or(FrameError::Malformed)?;
430    if !has_fields(&fields, &["frame_type", "request_id", "request_hash"]) {
431        return Err(FrameError::Malformed);
432    }
433    Ok((fixed(&fields["request_id"]), fixed(&fields["request_hash"])))
434}
435
436/// A frame of `frame_type` carrying `object` under `object_name`, with the
437/// routing field `route_name` when there is one.
438fn routed_frame(
439    frame_type: &str,
440    object_name: &str,
441    object: &Object,
442    route_name: &str,
443    route: Option<Vec<u8>>,
444) -> Value {
445    let mut entries = vec![
446        entry("version", Value::Int(i128::from(PROTOCOL_VERSION))),
447        entry("frame_type", Value::text(frame_type)),
448        entry(object_name, object.to_value()),
449    ];
450    if let Some(route) = route {
451        entries.push(entry(route_name, Value::Bytes(route)));
452    }
453    Value::Map(entries)
454}