use crate::{CMD_ID_DISCOVERY, CMD_ID_METRICS};
pub const CMD_ID_FIELD_SEP: u8 = 0x1F;
const FNV_OFFSET_BASIS: u32 = 0x811c9dc5;
const FNV_PRIME: u32 = 0x01000193;
const fn fnv1a_32_continue(mut hash: u32, bytes: &[u8]) -> u32 {
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u32;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
const fn fnv1a_32(bytes: &[u8]) -> u32 {
fnv1a_32_continue(FNV_OFFSET_BASIS, bytes)
}
const fn xor_fold(h: u32) -> u16 {
((h >> 16) as u16) ^ (h as u16)
}
pub const fn fnv1a_16(bytes: &[u8]) -> u16 {
xor_fold(fnv1a_32(bytes))
}
pub const fn derive_cmd_id(name: &str, args_type: &str, ret_type: &str) -> u16 {
let h = fnv1a_32_continue(FNV_OFFSET_BASIS, name.as_bytes());
let h = fnv1a_32_continue(h, &[CMD_ID_FIELD_SEP]);
let h = fnv1a_32_continue(h, args_type.as_bytes());
let h = fnv1a_32_continue(h, &[CMD_ID_FIELD_SEP]);
let h = fnv1a_32_continue(h, ret_type.as_bytes());
let mut id = xor_fold(h);
let mut h = h;
let mut salt = 0xFFu8;
while id == CMD_ID_DISCOVERY || id == CMD_ID_METRICS {
h = fnv1a_32_continue(h, &[salt]);
id = xor_fold(h);
if salt > 0 {
salt -= 1;
} else {
return 0x0001;
}
}
id
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CMD_ID_DISCOVERY, CMD_ID_METRICS};
#[test]
fn fnv1a_16_empty() {
assert_eq!(fnv1a_16(b""), 0x1cd9);
}
#[test]
fn fnv1a_32_known_vector_a() {
assert_eq!(fnv1a_32(b"a"), 0xe40c292c);
}
#[test]
fn fnv1a_16_known_vector_a() {
assert_eq!(fnv1a_16(b"a"), 0xcd20);
}
#[test]
fn fnv1a_32_known_vector_foobar() {
assert_eq!(fnv1a_32(b"foobar"), 0xbf9cf968);
}
#[test]
fn fnv1a_16_known_vector_foobar() {
assert_eq!(fnv1a_16(b"foobar"), 0x46f4);
}
#[test]
fn derive_cmd_id_ping_smoke() {
let id = derive_cmd_id("ping", "()", "u32");
assert_ne!(id, CMD_ID_DISCOVERY);
assert_eq!(id, derive_cmd_id("ping", "()", "u32"));
}
#[test]
fn derive_cmd_id_differs_on_name() {
assert_ne!(
derive_cmd_id("ping", "()", "u32"),
derive_cmd_id("pong", "()", "u32"),
);
}
#[test]
fn derive_cmd_id_differs_on_ret() {
assert_ne!(
derive_cmd_id("f", "()", "u32"),
derive_cmd_id("f", "()", "u64"),
);
}
#[test]
fn derive_cmd_id_differs_on_args() {
assert_ne!(
derive_cmd_id("f", "(u8,)", "u32"),
derive_cmd_id("f", "(u16,)", "u32"),
);
}
#[test]
fn cmd_id_metrics_value() {
assert_eq!(CMD_ID_METRICS, 0xFFFE);
}
#[test]
fn derive_cmd_id_never_returns_metrics_id() {
let cases = [
("ping", "()", "u32"),
("get_value", "(u8,)", "u32"),
("set_value", "(u8, u16)", "Result<(), ()>"),
("", "", ""),
("a", "b", "c"),
];
for (name, args, ret) in cases {
assert_ne!(
derive_cmd_id(name, args, ret),
CMD_ID_METRICS,
"({name:?}, {args:?}, {ret:?}) mapped to reserved CMD_ID_METRICS",
);
}
}
#[test]
fn derive_cmd_id_never_returns_discovery_id() {
let cases = [
("ping", "()", "u32"),
("get_value", "(u8,)", "u32"),
("set_value", "(u8, u16)", "Result<(), ()>"),
("", "", ""),
("a", "b", "c"),
];
for (name, args, ret) in cases {
assert_ne!(
derive_cmd_id(name, args, ret),
CMD_ID_DISCOVERY,
"({name:?}, {args:?}, {ret:?}) mapped to reserved CMD_ID_DISCOVERY",
);
}
}
#[test]
fn const_context() {
const _: u16 = fnv1a_16(b"x");
const _: u16 = derive_cmd_id("f", "()", "u32");
}
#[test]
fn derive_cmd_id_salt_rehash_when_raw_is_zero() {
for i in 0u32..200_000 {
let mut buf = [0u8; 10];
let len = encode_decimal(i, &mut buf);
let h = fnv1a_32_continue(FNV_OFFSET_BASIS, &buf[..len]);
let h = fnv1a_32_continue(h, &[CMD_ID_FIELD_SEP]);
let h = fnv1a_32_continue(h, b"()");
let h = fnv1a_32_continue(h, &[CMD_ID_FIELD_SEP]);
let h = fnv1a_32_continue(h, b"u32");
if xor_fold(h) == CMD_ID_DISCOVERY {
let salted = xor_fold(fnv1a_32_continue(h, &[0xFF]));
assert_ne!(salted, CMD_ID_DISCOVERY);
return;
}
}
}
fn encode_decimal(mut n: u32, buf: &mut [u8; 10]) -> usize {
if n == 0 {
buf[0] = b'0';
return 1;
}
let mut end = 0;
while n > 0 {
buf[end] = b'0' + (n % 10) as u8;
end += 1;
n /= 10;
}
buf[..end].reverse();
end
}
}