1use 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub enum Authorization {
16 #[default]
18 None,
19 Delegation {
22 org_directory: Vec<u8>,
23 procedure_delegation: Vec<u8>,
24 },
25 Unsupported,
27 Malformed,
30}
31
32#[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
43pub 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#[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
107pub 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}