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;
8
9use super::{entry, id_field, malformed, text_field, unsigned, Record, RecordError, RecordType};
10
11/// A procedure advertisement's provider authorization, as macula_record's
12/// read_authorization/1 reads it.
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub enum Authorization {
15    /// The advertisement carries none.
16    #[default]
17    None,
18    /// The realm's org directory and the org's procedure delegation, the one
19    /// form macula 12 has, as the records' wire forms.
20    Delegation {
21        org_directory: Vec<u8>,
22        procedure_delegation: Vec<u8>,
23    },
24    /// A map of any other fields, a certificate chain among them.
25    Unsupported,
26    /// Not a map, or an org directory and a delegation that are not both
27    /// byte strings.
28    Malformed,
29}
30
31/// A procedure advertisement's optional fields: its authorization, and
32/// `ttl_ms`, 0 for the default and maximum, 5 minutes.
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
34pub struct ProcedureAdvertisementOptions {
35    pub authorization: Authorization,
36    pub ttl_ms: u64,
37}
38
39/// An unsigned advertisement, by `advertiser_node`, which signs it, of
40/// `procedure` in `realm_id`, served through `serving_station`. It builds no
41/// authorization but an org directory and a procedure delegation.
42pub fn new_procedure_advertisement(
43    advertiser_node: &[u8; 32],
44    realm_id: &[u8; 32],
45    procedure: &str,
46    serving_station: &[u8; 32],
47    opts: &ProcedureAdvertisementOptions,
48) -> Result<Record, RecordError> {
49    let mut entries = vec![
50        entry("realm_id", Value::Bytes(realm_id.to_vec())),
51        entry("procedure", Value::text(procedure)),
52        entry("advertiser_node", Value::Bytes(advertiser_node.to_vec())),
53        entry("serving_station", Value::Bytes(serving_station.to_vec())),
54    ];
55    match &opts.authorization {
56        Authorization::None => {}
57        Authorization::Delegation {
58            org_directory,
59            procedure_delegation,
60        } => entries.push(entry(
61            "authorization",
62            Value::Map(vec![
63                entry("org_directory", Value::Bytes(org_directory.clone())),
64                entry(
65                    "procedure_delegation",
66                    Value::Bytes(procedure_delegation.clone()),
67                ),
68            ]),
69        )),
70        Authorization::Unsupported => return Err(RecordError::AuthorizationFormUnsupported),
71        Authorization::Malformed => return Err(malformed("an authorization in no form")),
72    }
73    Ok(unsigned(
74        RecordType::PROCEDURE_ADVERTISEMENT,
75        Value::Map(entries),
76        opts.ttl_ms,
77    ))
78}
79
80/// A procedure advertisement's payload.
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct ProcedureAdvertisement {
83    pub realm_id: [u8; 32],
84    pub procedure: String,
85    pub advertiser_node: [u8; 32],
86    pub serving_station: [u8; 32],
87    pub authorization: Authorization,
88}
89
90/// Reads a procedure advertisement's payload.
91pub fn read_procedure_advertisement(r: &Record) -> Result<ProcedureAdvertisement, RecordError> {
92    if r.record_type != RecordType::PROCEDURE_ADVERTISEMENT {
93        return Err(malformed("not a procedure advertisement"));
94    }
95    let p = &r.payload;
96    Ok(ProcedureAdvertisement {
97        realm_id: id_field(p, "realm_id"),
98        procedure: text_field(p, "procedure"),
99        advertiser_node: id_field(p, "advertiser_node"),
100        serving_station: id_field(p, "serving_station"),
101        authorization: read_authorization(p),
102    })
103}
104
105fn read_authorization(payload: &Value) -> Authorization {
106    let Some(value) = payload.get("authorization") else {
107        return Authorization::None;
108    };
109    let Value::Map(pairs) = value else {
110        return Authorization::Malformed;
111    };
112    let (Some(directory), Some(delegation)) = (
113        value.get("org_directory"),
114        value.get("procedure_delegation"),
115    ) else {
116        return Authorization::Unsupported;
117    };
118    if pairs.len() != 2 {
119        return Authorization::Unsupported;
120    }
121    match (directory, delegation) {
122        (Value::Bytes(d), Value::Bytes(g)) => Authorization::Delegation {
123            org_directory: d.clone(),
124            procedure_delegation: g.clone(),
125        },
126        _ => Authorization::Malformed,
127    }
128}