use bairelay_neolink_core::bcudp::{
model::{BcUdp, UdpDiscovery},
xml::UdpXml,
};
use bytes::BytesMut;
use rand::Rng;
use crate::WakeServerError;
const MAX_UID_LEN: usize = 64;
pub fn decode_discovery(buf: &[u8]) -> Result<(u32, UdpXml), WakeServerError> {
let mut bm = BytesMut::from(buf);
let pkt = BcUdp::deserialize(&mut bm)?;
match pkt {
BcUdp::Discovery(UdpDiscovery { tid, payload }) => {
validate_uid_bounds(&payload)?;
Ok((tid, payload))
}
BcUdp::Ack(_) => Err(WakeServerError::UnexpectedPacketKind { kind: "Ack" }),
BcUdp::Data(_) => Err(WakeServerError::UnexpectedPacketKind { kind: "Data" }),
}
}
fn validate_uid_bounds(payload: &UdpXml) -> Result<(), WakeServerError> {
let uid: Option<&str> = match payload {
UdpXml::C2mQ(q) => Some(q.uid.as_str()),
UdpXml::D2mQ(q) => Some(q.uid.as_str()),
UdpXml::C2rC(c) => Some(c.uid.as_str()),
UdpXml::D2rHb(hb) => Some(hb.uid.as_str()),
UdpXml::D2rR(r) => Some(r.uid.as_str()),
_ => None,
};
if let Some(u) = uid {
if u.len() > MAX_UID_LEN {
return Err(WakeServerError::UnexpectedPacketKind {
kind: "uid-too-long",
});
}
}
Ok(())
}
pub fn encode_discovery(tid: u32, payload: UdpXml) -> Result<Vec<u8>, WakeServerError> {
let pkt = BcUdp::Discovery(UdpDiscovery { tid, payload });
let buf = Vec::<u8>::new();
let buf = pkt.serialize(buf)?;
Ok(buf)
}
pub fn random_sid() -> u32 {
rand::thread_rng().gen()
}
#[cfg(test)]
mod tests {
use super::*;
use bairelay_neolink_core::bcudp::xml::{C2mQ, IpPort, M2cQr};
#[test]
fn encode_decode_round_trip_preserves_payload() {
let payload = UdpXml::C2mQ(C2mQ {
uid: "TESTUID".into(),
os: "MAC".into(),
});
let bytes = encode_discovery(0xdeadbeef, payload.clone()).unwrap();
let (tid, decoded) = decode_discovery(&bytes).unwrap();
assert_eq!(tid, 0xdeadbeef);
assert_eq!(decoded, payload);
}
#[test]
fn decode_rejects_random_garbage() {
assert!(decode_discovery(&[0u8; 4]).is_err());
assert!(decode_discovery(&[0xff; 32]).is_err());
}
#[test]
fn decode_rejects_ack_packet_with_kind_label() {
use bairelay_neolink_core::bcudp::model::{BcUdp, UdpAck};
let ack = BcUdp::Ack(UdpAck::empty(7));
let bytes = ack.serialize(Vec::new()).expect("serialize ack");
let err = decode_discovery(&bytes).expect_err("decode should reject ack");
match err {
WakeServerError::UnexpectedPacketKind { kind } => assert_eq!(kind, "Ack"),
other => panic!("expected UnexpectedPacketKind, got {other:?}"),
}
}
#[test]
fn decode_rejects_data_packet_with_kind_label() {
use bairelay_neolink_core::bcudp::model::{BcUdp, UdpData};
let data = BcUdp::Data(UdpData {
connection_id: 7,
packet_id: 1,
payload: vec![0xab, 0xcd],
});
let bytes = data.serialize(Vec::new()).expect("serialize data");
let err = decode_discovery(&bytes).expect_err("decode should reject data");
match err {
WakeServerError::UnexpectedPacketKind { kind } => assert_eq!(kind, "Data"),
other => panic!("expected UnexpectedPacketKind, got {other:?}"),
}
}
#[test]
fn encode_discovery_round_trips_m2c_q_r() {
let payload = UdpXml::M2cQr(M2cQr {
reg: Some(IpPort {
ip: "127.0.0.1".into(),
port: 58200,
}),
relay: Some(IpPort {
ip: "127.0.0.1".into(),
port: 58200,
}),
log: Some(IpPort {
ip: "127.0.0.1".into(),
port: 58200,
}),
t: Some(IpPort {
ip: "127.0.0.1".into(),
port: 58200,
}),
});
let bytes = encode_discovery(0x42, payload.clone()).unwrap();
let (tid, decoded) = decode_discovery(&bytes).unwrap();
assert_eq!(tid, 0x42);
assert_eq!(decoded, payload);
}
}