use crate::cbor::Value;
use crate::node_key::NodeKey;
use crate::profile::Profile;
use crate::signed_object::{sign_held_object, verify_held_object};
use super::{
base, bounded_text, entry, has_fields, object_refusal, protocol_uint, read_fields, uint,
with_field, FrameError, Rule, MAX_TOPIC_BYTES, PROTOCOL_VERSION,
};
const NEIGHBOUR_LABEL: &str = "MACULA-PQ-NEIGHBOUR-V1";
const MAX_GOODBYE_REASON_BYTES: usize = 256;
const MAX_GOODBYE_DETAIL_BYTES: usize = 256;
const NEIGHBOUR_SIGNED_TYPES: &[&str] = &[
"swim_ping",
"swim_ack",
"swim_suspect",
"swim_confirm",
"ping",
"pong",
"find_node",
"nodes",
"find_value",
"value",
"store",
"store_ack",
"advertise",
"unadvertise",
"subscribe",
"unsubscribe",
"overlay_relay",
"hyparview_join",
"hyparview_forward_join",
"hyparview_neighbor",
"hyparview_disconnect",
"hyparview_shuffle",
"hyparview_shuffle_reply",
"plumtree_ihave",
"plumtree_graft",
"plumtree_prune",
"goodbye",
];
pub fn neighbour_signed(profile: Profile, frame_type: &str) -> bool {
profile == Profile::PqHybrid && NEIGHBOUR_SIGNED_TYPES.contains(&frame_type)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NeighbourLink {
pub connection: [u8; 48],
pub seq: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeighbourPeer {
pub profile: Profile,
pub peer_key: Vec<u8>,
pub connection: [u8; 48],
pub seq: u64,
}
pub fn sign_neighbour(
frame: &Value,
key: &NodeKey,
link: &NeighbourLink,
) -> Result<Value, FrameError> {
let Value::Map(pairs) = frame else {
return Err(FrameError::Malformed);
};
let (frame_type, version, has_neighbour) = control_header(pairs);
if has_neighbour {
return Err(FrameError::NeighbourSigned);
}
if !neighbour_signed(key.profile(), &frame_type) {
return Ok(frame.clone());
}
let mut fields: Vec<(Value, Value)> = pairs
.iter()
.filter(|(k, _)| *k != Value::text("version"))
.cloned()
.collect();
fields.push(entry("connection", Value::Bytes(link.connection.to_vec())));
fields.push(entry("seq", uint(link.seq)));
let held = sign_held_object(NEIGHBOUR_LABEL, &fields, key).map_err(object_refusal)?;
Ok(Value::Map(vec![
entry("version", version),
entry("frame_type", Value::text(frame_type)),
entry("neighbour", held.to_value()),
]))
}
pub fn verify_neighbour(frame: &Value, peer: &NeighbourPeer) -> Result<Value, FrameError> {
let Value::Map(pairs) = frame else {
return Err(FrameError::Malformed);
};
let (frame_type, version, has_neighbour) = control_header(pairs);
if !neighbour_signed(peer.profile, &frame_type) {
return if has_neighbour {
Err(FrameError::Malformed)
} else {
Ok(frame.clone())
};
}
if pairs.len() != 3 || !has_neighbour {
return Err(FrameError::Malformed);
}
let neighbour = frame.get("neighbour").ok_or(FrameError::Malformed)?;
let verified = verify_held_object(NEIGHBOUR_LABEL, neighbour, &peer.peer_key, peer.profile)
.map_err(object_refusal)?;
opened_control_frame(&verified.fields, &frame_type, version, peer)
}
fn opened_control_frame(
tbs: &Value,
frame_type: &str,
version: Value,
peer: &NeighbourPeer,
) -> Result<Value, FrameError> {
let mut table = control_table(frame_type).ok_or(FrameError::Malformed)?;
table.extend([
("alg", Rule::Any),
("connection", Rule::BytesOf(48)),
("seq", Rule::ProtocolUint),
]);
let fields = read_fields(tbs, &table).ok_or(FrameError::Malformed)?;
if !has_fields(&fields, &["frame_type", "alg", "connection", "seq"])
|| fields["frame_type"] != Value::text(frame_type)
|| fields["connection"] != Value::Bytes(peer.connection.to_vec())
|| protocol_uint(&fields["seq"]) != Some(peer.seq)
{
return Err(FrameError::Malformed);
}
let Value::Map(pairs) = tbs else {
return Err(FrameError::Malformed);
};
let mut opened = vec![entry("version", version)];
opened.extend(
pairs
.iter()
.filter(|(k, _)| !matches!(k, Value::Text(t) if t == "alg" || t == "connection" || t == "seq"))
.cloned(),
);
Ok(Value::Map(opened))
}
fn control_table(frame_type: &str) -> Option<Vec<(&'static str, Rule)>> {
let own: &[(&'static str, Rule)] = match frame_type {
"advertise" => &[("advertisement", Rule::AnyBytes)],
"unadvertise" => &[("withdrawal", Rule::AnyBytes)],
"subscribe" => &[
("topic", Rule::Any),
("subscriber", Rule::BytesOf(32)),
("options", Rule::Any),
],
"unsubscribe" => &[("topic", Rule::Any), ("subscriber", Rule::BytesOf(32))],
"goodbye" => &[
("reason", Rule::TextWithin(MAX_GOODBYE_REASON_BYTES)),
("detail", Rule::Any),
],
_ => return None,
};
let types: &'static [&'static str] = match frame_type {
"advertise" => &["advertise"],
"unadvertise" => &["unadvertise"],
"subscribe" => &["subscribe"],
"unsubscribe" => &["unsubscribe"],
_ => &["goodbye"],
};
let mut table = vec![
("frame_type", Rule::TextIn(types)),
("frame_id", Rule::Any),
("sent_at_ms", Rule::ProtocolUint),
("capabilities", Rule::ProtocolUint),
("realm", Rule::Any),
("call_id", Rule::Any),
("source_route", Rule::Any),
];
table.extend_from_slice(own);
Some(table)
}
pub(super) fn control_header(pairs: &[(Value, Value)]) -> (String, Value, bool) {
let mut frame_type = String::new();
let mut version = Value::Int(i128::from(PROTOCOL_VERSION));
let mut has_neighbour = false;
for (k, v) in pairs {
match (k, v) {
(Value::Text(n), Value::Text(t)) if n == "frame_type" => frame_type = t.clone(),
(Value::Text(n), _) if n == "version" => version = v.clone(),
(Value::Text(n), _) if n == "neighbour" => has_neighbour = true,
_ => {}
}
}
(frame_type, version, has_neighbour)
}
pub fn advertise_frame(advertisement: &[u8]) -> Value {
let mut fields = base("advertise");
fields.push(entry("advertisement", Value::Bytes(advertisement.to_vec())));
Value::Map(fields)
}
pub fn unadvertise_frame(withdrawal: &[u8]) -> Value {
let mut fields = base("unadvertise");
fields.push(entry("withdrawal", Value::Bytes(withdrawal.to_vec())));
Value::Map(fields)
}
pub fn subscribe_frame(
topic: &[u8],
realm: &[u8; 32],
subscriber: &[u8; 32],
) -> Result<Value, FrameError> {
let mut fields = topic_frame("subscribe", topic, realm, subscriber)?;
fields.push(entry("options", Value::Map(Vec::new())));
Ok(Value::Map(fields))
}
pub fn unsubscribe_frame(
topic: &[u8],
realm: &[u8; 32],
subscriber: &[u8; 32],
) -> Result<Value, FrameError> {
topic_frame("unsubscribe", topic, realm, subscriber).map(Value::Map)
}
fn topic_frame(
frame_type: &str,
topic: &[u8],
realm: &[u8; 32],
subscriber: &[u8; 32],
) -> Result<Vec<(Value, Value)>, FrameError> {
bounded_text("topic", topic, MAX_TOPIC_BYTES)?;
let mut fields = with_field(base(frame_type), "realm", Value::Bytes(realm.to_vec()));
fields.push(entry("topic", Value::Bytes(topic.to_vec())));
fields.push(entry("subscriber", Value::Bytes(subscriber.to_vec())));
Ok(fields)
}
pub fn goodbye_frame(reason: &str, detail: Option<&[u8]>) -> Result<Value, FrameError> {
bounded_text("reason", reason.as_bytes(), MAX_GOODBYE_REASON_BYTES)?;
let detail = match detail {
Some(d) => {
bounded_text("detail", d, MAX_GOODBYE_DETAIL_BYTES)?;
Value::Bytes(d.to_vec())
}
None => Value::Null,
};
let mut fields = base("goodbye");
fields.push(entry("reason", Value::text(reason)));
fields.push(entry("detail", detail));
Ok(Value::Map(fields))
}