Skip to main content

macula_rust/record/
procedure_advertisement.rs

1//! Procedure advertisements: a node's statement that it serves a procedure in
2//! a realm, through a station, signed by the node. A procedure with an org
3//! namespace carries its provider authorization inside the payload: the
4//! realm's org directory and the org's procedure delegation, as their wire
5//! forms (see `authorization`).
6
7use crate::cbor::Value;
8use crate::seal::{self, KEY_ID_SIZE};
9
10use super::{entry, id_field, malformed, text_field, unsigned, Record, RecordError, RecordType};
11
12/// A procedure advertisement's provider authorization, as macula_record's
13/// read_authorization/1 reads it.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub enum Authorization {
16    /// The advertisement carries none.
17    #[default]
18    None,
19    /// The realm's org directory and the org's procedure delegation, the one
20    /// form macula 12 has, as the records' wire forms.
21    Delegation {
22        org_directory: Vec<u8>,
23        procedure_delegation: Vec<u8>,
24    },
25    /// A map of any other fields, a certificate chain among them.
26    Unsupported,
27    /// Not a map, or an org directory and a delegation that are not both
28    /// byte strings.
29    Malformed,
30}
31
32/// A procedure advertisement's optional fields: its authorization, the
33/// provider's KEM key as carried, which the advertisement names with its id
34/// (macula 13, E2E design amendment A1), and `ttl_ms`, 0 for the default and
35/// maximum, 5 minutes.
36#[derive(Debug, Clone, Default, PartialEq, Eq)]
37pub struct ProcedureAdvertisementOptions {
38    pub authorization: Authorization,
39    pub kem_key: Option<Vec<u8>>,
40    pub ttl_ms: u64,
41}
42
43/// An unsigned advertisement, by `advertiser_node`, which signs it, of
44/// `procedure` in `realm_id`, served through `serving_station`. It builds no
45/// authorization but an org directory and a procedure delegation.
46pub fn new_procedure_advertisement(
47    advertiser_node: &[u8; 32],
48    realm_id: &[u8; 32],
49    procedure: &str,
50    serving_station: &[u8; 32],
51    opts: &ProcedureAdvertisementOptions,
52) -> Result<Record, RecordError> {
53    let mut entries = vec![
54        entry("realm_id", Value::Bytes(realm_id.to_vec())),
55        entry("procedure", Value::text(procedure)),
56        entry("advertiser_node", Value::Bytes(advertiser_node.to_vec())),
57        entry("serving_station", Value::Bytes(serving_station.to_vec())),
58    ];
59    match &opts.authorization {
60        Authorization::None => {}
61        Authorization::Delegation {
62            org_directory,
63            procedure_delegation,
64        } => entries.push(entry(
65            "authorization",
66            Value::Map(vec![
67                entry("org_directory", Value::Bytes(org_directory.clone())),
68                entry(
69                    "procedure_delegation",
70                    Value::Bytes(procedure_delegation.clone()),
71                ),
72            ]),
73        )),
74        Authorization::Unsupported => return Err(RecordError::AuthorizationFormUnsupported),
75        Authorization::Malformed => return Err(malformed("an authorization in no form")),
76    }
77    if let Some(key) = &opts.kem_key {
78        if !seal::is_carried_key_size(key.len()) {
79            return Err(malformed("a KEM key of no profile's size"));
80        }
81        entries.push(entry("kem_key", Value::Bytes(key.clone())));
82        entries.push(entry(
83            "kem_key_id",
84            Value::Bytes(seal::key_id(key).to_vec()),
85        ));
86    }
87    Ok(unsigned(
88        RecordType::PROCEDURE_ADVERTISEMENT,
89        Value::Map(entries),
90        opts.ttl_ms,
91    ))
92}
93
94/// A procedure advertisement's payload. `kem_key` is the provider's KEM key
95/// as carried and its id, when the advertisement names one: a verified
96/// record's pair is well formed, the key's id its own.
97#[derive(Debug, Clone, Default, PartialEq, Eq)]
98pub struct ProcedureAdvertisement {
99    pub realm_id: [u8; 32],
100    pub procedure: String,
101    pub advertiser_node: [u8; 32],
102    pub serving_station: [u8; 32],
103    pub authorization: Authorization,
104    pub kem_key: Option<(Vec<u8>, [u8; KEY_ID_SIZE])>,
105}
106
107/// Reads a procedure advertisement's payload.
108pub fn read_procedure_advertisement(r: &Record) -> Result<ProcedureAdvertisement, RecordError> {
109    if r.record_type != RecordType::PROCEDURE_ADVERTISEMENT {
110        return Err(malformed("not a procedure advertisement"));
111    }
112    let p = &r.payload;
113    Ok(ProcedureAdvertisement {
114        realm_id: id_field(p, "realm_id"),
115        procedure: text_field(p, "procedure"),
116        advertiser_node: id_field(p, "advertiser_node"),
117        serving_station: id_field(p, "serving_station"),
118        authorization: read_authorization(p),
119        kem_key: read_kem_key(p),
120    })
121}
122
123fn read_kem_key(payload: &Value) -> Option<(Vec<u8>, [u8; KEY_ID_SIZE])> {
124    match (payload.get("kem_key"), payload.get("kem_key_id")) {
125        (Some(Value::Bytes(key)), Some(Value::Bytes(id))) => {
126            Some((key.clone(), id.as_slice().try_into().ok()?))
127        }
128        _ => None,
129    }
130}
131
132fn read_authorization(payload: &Value) -> Authorization {
133    let Some(value) = payload.get("authorization") else {
134        return Authorization::None;
135    };
136    let Value::Map(pairs) = value else {
137        return Authorization::Malformed;
138    };
139    let (Some(directory), Some(delegation)) = (
140        value.get("org_directory"),
141        value.get("procedure_delegation"),
142    ) else {
143        return Authorization::Unsupported;
144    };
145    if pairs.len() != 2 {
146        return Authorization::Unsupported;
147    }
148    match (directory, delegation) {
149        (Value::Bytes(d), Value::Bytes(g)) => Authorization::Delegation {
150            org_directory: d.clone(),
151            procedure_delegation: g.clone(),
152        },
153        _ => Authorization::Malformed,
154    }
155}