use serde::{Deserialize, Serialize};
pub(crate) const BLOB_VERSION: u8 = 1;
pub(crate) const AM_ID_BASE: u16 = 0x5645;
pub(crate) const AM_KIND_PING: u8 = 5;
pub(crate) const AM_KIND_PONG: u8 = 6;
pub(crate) const AM_KIND_COUNT: u8 = 7;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct UcxEndpoint {
pub v: u8,
pub am_id_base: u16,
pub eager_max: u32,
pub incarnation: u64,
#[serde(with = "serde_bytes")]
pub worker_addr: Vec<u8>,
}
impl UcxEndpoint {
pub fn encode(&self) -> anyhow::Result<Vec<u8>> {
Ok(rmp_serde::to_vec(self)?)
}
pub fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
let ep: UcxEndpoint = rmp_serde::from_slice(bytes)?;
anyhow::ensure!(
ep.v == BLOB_VERSION,
"unsupported ucx blob version {} (expected {BLOB_VERSION})",
ep.v
);
anyhow::ensure!(
ep.am_id_base == AM_ID_BASE,
"peer uses AM id base {:#x}, this build uses {AM_ID_BASE:#x}",
ep.am_id_base
);
anyhow::ensure!(!ep.worker_addr.is_empty(), "empty ucp worker address");
Ok(ep)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blob_roundtrip() {
let ep = UcxEndpoint {
v: BLOB_VERSION,
am_id_base: AM_ID_BASE,
eager_max: 1 << 20,
incarnation: 0xDEAD_BEEF,
worker_addr: vec![1, 2, 3, 4, 5],
};
let bytes = ep.encode().unwrap();
let back = UcxEndpoint::decode(&bytes).unwrap();
assert_eq!(back.eager_max, 1 << 20);
assert_eq!(back.incarnation, 0xDEAD_BEEF);
assert_eq!(back.worker_addr, vec![1, 2, 3, 4, 5]);
}
#[test]
fn blob_rejects_wrong_base() {
let ep = UcxEndpoint {
v: BLOB_VERSION,
am_id_base: AM_ID_BASE + 1,
eager_max: 0,
incarnation: 0,
worker_addr: vec![0],
};
let bytes = rmp_serde::to_vec(&ep).unwrap();
assert!(UcxEndpoint::decode(&bytes).is_err());
}
#[test]
fn blob_rejects_wrong_version() {
let ep = UcxEndpoint {
v: BLOB_VERSION + 1,
am_id_base: AM_ID_BASE,
eager_max: 0,
incarnation: 0,
worker_addr: vec![0],
};
let bytes = rmp_serde::to_vec(&ep).unwrap();
assert!(UcxEndpoint::decode(&bytes).is_err());
}
}