1use crate::cbor::Value;
14use crate::node_key::NodeKey;
15use crate::profile::Profile;
16use crate::signed_object::{sign_held_object, verify_held_object};
17
18use super::{
19 base, bounded_text, entry, has_fields, object_refusal, protocol_uint, read_fields, uint,
20 with_field, FrameError, Rule, MAX_TOPIC_BYTES, PROTOCOL_VERSION,
21};
22
23const NEIGHBOUR_LABEL: &str = "MACULA-PQ-NEIGHBOUR-V1";
24const MAX_GOODBYE_REASON_BYTES: usize = 256;
25const MAX_GOODBYE_DETAIL_BYTES: usize = 256;
26
27const NEIGHBOUR_SIGNED_TYPES: &[&str] = &[
30 "swim_ping",
31 "swim_ack",
32 "swim_suspect",
33 "swim_confirm",
34 "ping",
35 "pong",
36 "find_node",
37 "nodes",
38 "find_value",
39 "value",
40 "store",
41 "store_ack",
42 "advertise",
43 "unadvertise",
44 "subscribe",
45 "unsubscribe",
46 "overlay_relay",
47 "hyparview_join",
48 "hyparview_forward_join",
49 "hyparview_neighbor",
50 "hyparview_disconnect",
51 "hyparview_shuffle",
52 "hyparview_shuffle_reply",
53 "plumtree_ihave",
54 "plumtree_graft",
55 "plumtree_prune",
56 "goodbye",
57];
58
59pub fn neighbour_signed(profile: Profile, frame_type: &str) -> bool {
62 profile == Profile::PqHybrid && NEIGHBOUR_SIGNED_TYPES.contains(&frame_type)
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct NeighbourLink {
69 pub connection: [u8; 48],
70 pub seq: u64,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct NeighbourPeer {
78 pub profile: Profile,
79 pub peer_key: Vec<u8>,
80 pub connection: [u8; 48],
81 pub seq: u64,
82}
83
84pub fn sign_neighbour(
88 frame: &Value,
89 key: &NodeKey,
90 link: &NeighbourLink,
91) -> Result<Value, FrameError> {
92 let Value::Map(pairs) = frame else {
93 return Err(FrameError::Malformed);
94 };
95 let (frame_type, version, has_neighbour) = control_header(pairs);
96 if has_neighbour {
97 return Err(FrameError::NeighbourSigned);
98 }
99 if !neighbour_signed(key.profile(), &frame_type) {
100 return Ok(frame.clone());
101 }
102 let mut fields: Vec<(Value, Value)> = pairs
103 .iter()
104 .filter(|(k, _)| *k != Value::text("version"))
105 .cloned()
106 .collect();
107 fields.push(entry("connection", Value::Bytes(link.connection.to_vec())));
108 fields.push(entry("seq", uint(link.seq)));
109 let held = sign_held_object(NEIGHBOUR_LABEL, &fields, key).map_err(object_refusal)?;
110 Ok(Value::Map(vec![
111 entry("version", version),
112 entry("frame_type", Value::text(frame_type)),
113 entry("neighbour", held.to_value()),
114 ]))
115}
116
117pub fn verify_neighbour(frame: &Value, peer: &NeighbourPeer) -> Result<Value, FrameError> {
123 let Value::Map(pairs) = frame else {
124 return Err(FrameError::Malformed);
125 };
126 let (frame_type, version, has_neighbour) = control_header(pairs);
127 if !neighbour_signed(peer.profile, &frame_type) {
128 return if has_neighbour {
129 Err(FrameError::Malformed)
130 } else {
131 Ok(frame.clone())
132 };
133 }
134 if pairs.len() != 3 || !has_neighbour {
135 return Err(FrameError::Malformed);
136 }
137 let neighbour = frame.get("neighbour").ok_or(FrameError::Malformed)?;
138 let verified = verify_held_object(NEIGHBOUR_LABEL, neighbour, &peer.peer_key, peer.profile)
139 .map_err(object_refusal)?;
140 opened_control_frame(&verified.fields, &frame_type, version, peer)
141}
142
143fn opened_control_frame(
147 tbs: &Value,
148 frame_type: &str,
149 version: Value,
150 peer: &NeighbourPeer,
151) -> Result<Value, FrameError> {
152 let mut table = control_table(frame_type).ok_or(FrameError::Malformed)?;
153 table.extend([
154 ("alg", Rule::Any),
155 ("connection", Rule::BytesOf(48)),
156 ("seq", Rule::ProtocolUint),
157 ]);
158 let fields = read_fields(tbs, &table).ok_or(FrameError::Malformed)?;
159 if !has_fields(&fields, &["frame_type", "alg", "connection", "seq"])
160 || fields["frame_type"] != Value::text(frame_type)
161 || fields["connection"] != Value::Bytes(peer.connection.to_vec())
162 || protocol_uint(&fields["seq"]) != Some(peer.seq)
163 {
164 return Err(FrameError::Malformed);
165 }
166 let Value::Map(pairs) = tbs else {
167 return Err(FrameError::Malformed);
168 };
169 let mut opened = vec![entry("version", version)];
170 opened.extend(
171 pairs
172 .iter()
173 .filter(|(k, _)| !matches!(k, Value::Text(t) if t == "alg" || t == "connection" || t == "seq"))
174 .cloned(),
175 );
176 Ok(Value::Map(opened))
177}
178
179fn control_table(frame_type: &str) -> Option<Vec<(&'static str, Rule)>> {
182 let own: &[(&'static str, Rule)] = match frame_type {
183 "advertise" => &[("advertisement", Rule::AnyBytes)],
184 "unadvertise" => &[("withdrawal", Rule::AnyBytes)],
185 "subscribe" => &[
186 ("topic", Rule::Any),
187 ("subscriber", Rule::BytesOf(32)),
188 ("options", Rule::Any),
189 ],
190 "unsubscribe" => &[("topic", Rule::Any), ("subscriber", Rule::BytesOf(32))],
191 "goodbye" => &[
192 ("reason", Rule::TextWithin(MAX_GOODBYE_REASON_BYTES)),
193 ("detail", Rule::Any),
194 ],
195 _ => return None,
196 };
197 let types: &'static [&'static str] = match frame_type {
198 "advertise" => &["advertise"],
199 "unadvertise" => &["unadvertise"],
200 "subscribe" => &["subscribe"],
201 "unsubscribe" => &["unsubscribe"],
202 _ => &["goodbye"],
203 };
204 let mut table = vec![
205 ("frame_type", Rule::TextIn(types)),
206 ("frame_id", Rule::Any),
207 ("sent_at_ms", Rule::ProtocolUint),
208 ("capabilities", Rule::ProtocolUint),
209 ("realm", Rule::Any),
210 ("call_id", Rule::Any),
211 ("source_route", Rule::Any),
212 ];
213 table.extend_from_slice(own);
214 Some(table)
215}
216
217fn control_header(pairs: &[(Value, Value)]) -> (String, Value, bool) {
219 let mut frame_type = String::new();
220 let mut version = Value::Int(i128::from(PROTOCOL_VERSION));
221 let mut has_neighbour = false;
222 for (k, v) in pairs {
223 match (k, v) {
224 (Value::Text(n), Value::Text(t)) if n == "frame_type" => frame_type = t.clone(),
225 (Value::Text(n), _) if n == "version" => version = v.clone(),
226 (Value::Text(n), _) if n == "neighbour" => has_neighbour = true,
227 _ => {}
228 }
229 }
230 (frame_type, version, has_neighbour)
231}
232
233pub fn advertise_frame(advertisement: &[u8]) -> Value {
236 let mut fields = base("advertise");
237 fields.push(entry("advertisement", Value::Bytes(advertisement.to_vec())));
238 Value::Map(fields)
239}
240
241pub fn unadvertise_frame(withdrawal: &[u8]) -> Value {
243 let mut fields = base("unadvertise");
244 fields.push(entry("withdrawal", Value::Bytes(withdrawal.to_vec())));
245 Value::Map(fields)
246}
247
248pub fn subscribe_frame(
251 topic: &[u8],
252 realm: &[u8; 32],
253 subscriber: &[u8; 32],
254) -> Result<Value, FrameError> {
255 let mut fields = topic_frame("subscribe", topic, realm, subscriber)?;
256 fields.push(entry("options", Value::Map(Vec::new())));
257 Ok(Value::Map(fields))
258}
259
260pub fn unsubscribe_frame(
263 topic: &[u8],
264 realm: &[u8; 32],
265 subscriber: &[u8; 32],
266) -> Result<Value, FrameError> {
267 topic_frame("unsubscribe", topic, realm, subscriber).map(Value::Map)
268}
269
270fn topic_frame(
271 frame_type: &str,
272 topic: &[u8],
273 realm: &[u8; 32],
274 subscriber: &[u8; 32],
275) -> Result<Vec<(Value, Value)>, FrameError> {
276 bounded_text("topic", topic, MAX_TOPIC_BYTES)?;
277 let mut fields = with_field(base(frame_type), "realm", Value::Bytes(realm.to_vec()));
278 fields.push(entry("topic", Value::Bytes(topic.to_vec())));
279 fields.push(entry("subscriber", Value::Bytes(subscriber.to_vec())));
280 Ok(fields)
281}
282
283pub fn goodbye_frame(reason: &str, detail: Option<&[u8]>) -> Result<Value, FrameError> {
286 bounded_text("reason", reason.as_bytes(), MAX_GOODBYE_REASON_BYTES)?;
287 let detail = match detail {
288 Some(d) => {
289 bounded_text("detail", d, MAX_GOODBYE_DETAIL_BYTES)?;
290 Value::Bytes(d.to_vec())
291 }
292 None => Value::Null,
293 };
294 let mut fields = base("goodbye");
295 fields.push(entry("reason", Value::text(reason)));
296 fields.push(entry("detail", detail));
297 Ok(Value::Map(fields))
298}