1use crate::cbor::Value;
9use crate::seal::{self, KEY_ID_SIZE};
10
11use super::payload::MAX_SERVING_STATIONS;
12use super::{entry, id_field, malformed, text_field, unsigned, Record, RecordError, RecordType};
13
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
17pub enum Authorization {
18 #[default]
20 None,
21 Delegation {
24 org_directory: Vec<u8>,
25 procedure_delegation: Vec<u8>,
26 },
27 Unsupported,
29 Malformed,
32}
33
34#[derive(Debug, Clone, Default, PartialEq, Eq)]
39pub struct ProcedureAdvertisementOptions {
40 pub authorization: Authorization,
41 pub kem_key: Option<Vec<u8>>,
42 pub ttl_ms: u64,
43}
44
45pub fn new_procedure_advertisement(
50 advertiser_node: &[u8; 32],
51 realm_id: &[u8; 32],
52 procedure: &str,
53 serving_stations: &[[u8; 32]],
54 opts: &ProcedureAdvertisementOptions,
55) -> Result<Record, RecordError> {
56 let mut stations = serving_stations.to_vec();
57 stations.sort_unstable();
58 stations.dedup();
59 if stations.is_empty() || stations.len() > MAX_SERVING_STATIONS {
60 return Err(malformed("an advertisement names 1 to 8 serving stations"));
61 }
62 let mut entries = vec![
63 entry("realm_id", Value::Bytes(realm_id.to_vec())),
64 entry("procedure", Value::text(procedure)),
65 entry("advertiser_node", Value::Bytes(advertiser_node.to_vec())),
66 entry(
67 "serving_stations",
68 Value::List(stations.iter().map(|s| Value::Bytes(s.to_vec())).collect()),
69 ),
70 ];
71 match &opts.authorization {
72 Authorization::None => {}
73 Authorization::Delegation {
74 org_directory,
75 procedure_delegation,
76 } => entries.push(entry(
77 "authorization",
78 Value::Map(vec![
79 entry("org_directory", Value::Bytes(org_directory.clone())),
80 entry(
81 "procedure_delegation",
82 Value::Bytes(procedure_delegation.clone()),
83 ),
84 ]),
85 )),
86 Authorization::Unsupported => return Err(RecordError::AuthorizationFormUnsupported),
87 Authorization::Malformed => return Err(malformed("an authorization in no form")),
88 }
89 if let Some(key) = &opts.kem_key {
90 if !seal::is_carried_key_size(key.len()) {
91 return Err(malformed("a KEM key of no profile's size"));
92 }
93 entries.push(entry("kem_key", Value::Bytes(key.clone())));
94 entries.push(entry(
95 "kem_key_id",
96 Value::Bytes(seal::key_id(key).to_vec()),
97 ));
98 }
99 Ok(unsigned(
100 RecordType::PROCEDURE_ADVERTISEMENT,
101 Value::Map(entries),
102 opts.ttl_ms,
103 ))
104}
105
106#[derive(Debug, Clone, Default, PartialEq, Eq)]
110pub struct ProcedureAdvertisement {
111 pub realm_id: [u8; 32],
112 pub procedure: String,
113 pub advertiser_node: [u8; 32],
114 pub serving_stations: Vec<[u8; 32]>,
115 pub authorization: Authorization,
116 pub kem_key: Option<(Vec<u8>, [u8; KEY_ID_SIZE])>,
117}
118
119pub fn read_procedure_advertisement(r: &Record) -> Result<ProcedureAdvertisement, RecordError> {
121 if r.record_type != RecordType::PROCEDURE_ADVERTISEMENT {
122 return Err(malformed("not a procedure advertisement"));
123 }
124 let p = &r.payload;
125 Ok(ProcedureAdvertisement {
126 realm_id: id_field(p, "realm_id"),
127 procedure: text_field(p, "procedure"),
128 advertiser_node: id_field(p, "advertiser_node"),
129 serving_stations: match p.get("serving_stations") {
130 Some(Value::List(items)) => items
131 .iter()
132 .filter_map(|s| match s {
133 Value::Bytes(b) => b.as_slice().try_into().ok(),
134 _ => None,
135 })
136 .collect(),
137 _ => Vec::new(),
138 },
139 authorization: read_authorization(p),
140 kem_key: read_kem_key(p),
141 })
142}
143
144fn read_kem_key(payload: &Value) -> Option<(Vec<u8>, [u8; KEY_ID_SIZE])> {
145 match (payload.get("kem_key"), payload.get("kem_key_id")) {
146 (Some(Value::Bytes(key)), Some(Value::Bytes(id))) => {
147 Some((key.clone(), id.as_slice().try_into().ok()?))
148 }
149 _ => None,
150 }
151}
152
153fn read_authorization(payload: &Value) -> Authorization {
154 let Some(value) = payload.get("authorization") else {
155 return Authorization::None;
156 };
157 let Value::Map(pairs) = value else {
158 return Authorization::Malformed;
159 };
160 let (Some(directory), Some(delegation)) = (
161 value.get("org_directory"),
162 value.get("procedure_delegation"),
163 ) else {
164 return Authorization::Unsupported;
165 };
166 if pairs.len() != 2 {
167 return Authorization::Unsupported;
168 }
169 match (directory, delegation) {
170 (Value::Bytes(d), Value::Bytes(g)) => Authorization::Delegation {
171 org_directory: d.clone(),
172 procedure_delegation: g.clone(),
173 },
174 _ => Authorization::Malformed,
175 }
176}