use crate::cbor::Value;
use super::wire_counters::count_enum_fallback;
use super::{fixed, has_fields, read_fields, text_of, FrameError, Rule, MAX_TOPIC_BYTES};
const REASONS: &[&str] = &[
"too_many_providers",
"advertiser_quota",
"name_too_long",
"record_refused",
"not_authorized",
"too_many_realms",
"refused",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegisterKind {
Advertise,
Unadvertise,
Subscribe,
Unsubscribe,
}
impl RegisterKind {
fn parse(name: &str) -> Option<RegisterKind> {
match name {
"advertise" => Some(RegisterKind::Advertise),
"unadvertise" => Some(RegisterKind::Unadvertise),
"subscribe" => Some(RegisterKind::Subscribe),
"unsubscribe" => Some(RegisterKind::Unsubscribe),
_ => None,
}
}
fn own_key(self) -> &'static str {
match self {
RegisterKind::Advertise | RegisterKind::Unadvertise => "record_version",
RegisterKind::Subscribe | RegisterKind::Unsubscribe => "subscriber",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Answered {
Advertisement { record_version: [u8; 16] },
Subscription { subscriber: [u8; 32] },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegisterAck {
pub kind: RegisterKind,
pub realm: [u8; 32],
pub name: String,
pub answered: Answered,
pub refusal: Option<String>,
}
pub fn read_register_ack(frame: &Value) -> Result<Option<RegisterAck>, FrameError> {
let table = [
("version", Rule::ProtocolVersion),
("frame_type", Rule::TextIn(&["register_ack"])),
("frame_id", Rule::BytesOf(16)),
("sent_at_ms", Rule::ProtocolUint),
("kind", Rule::EnumText),
("realm", Rule::BytesOf(32)),
("name", Rule::TextWithin(MAX_TOPIC_BYTES)),
("record_version", Rule::BytesOf(16)),
("subscriber", Rule::BytesOf(32)),
("accepted", Rule::ZeroOrOne),
("reason", Rule::EnumText),
];
let fields = read_fields(frame, &table).ok_or(FrameError::Malformed)?;
let accepted = fields.get("accepted") == Some(&Value::Int(1));
if !has_fields(&fields, &["kind", "realm", "name", "accepted"])
|| text_of(&fields["name"]).is_empty()
|| accepted == fields.contains_key("reason")
{
return Err(FrameError::Malformed);
}
let Some(kind) = RegisterKind::parse(&text_of(&fields["kind"])) else {
count_enum_fallback("kind");
return Ok(None);
};
let own = kind.own_key();
let other = if own == "subscriber" {
"record_version"
} else {
"subscriber"
};
if !fields.contains_key(own) || fields.contains_key(other) {
return Err(FrameError::Malformed);
}
let answered = match kind.own_key() {
"record_version" => Answered::Advertisement {
record_version: fixed(&fields["record_version"]),
},
_ => Answered::Subscription {
subscriber: fixed(&fields["subscriber"]),
},
};
Ok(Some(RegisterAck {
kind,
realm: fixed(&fields["realm"]),
name: text_of(&fields["name"]),
answered,
refusal: fields.get("reason").map(|r| reason(&text_of(r))),
}))
}
fn reason(name: &str) -> String {
if REASONS.contains(&name) {
return name.to_string();
}
count_enum_fallback("reason");
"refused".to_string()
}