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