extern crate alloc;
use alloc::vec::Vec;
use zerodds_foundation::md5;
pub const KEY_HASH_LEN: usize = 16;
#[must_use]
pub fn compute_key_hash(
plain_cdr2_be_bytes: &[u8],
key_holder_max_size: usize,
) -> [u8; KEY_HASH_LEN] {
if key_holder_max_size <= KEY_HASH_LEN {
let mut out = [0u8; KEY_HASH_LEN];
let n = core::cmp::min(plain_cdr2_be_bytes.len(), KEY_HASH_LEN);
out[..n].copy_from_slice(&plain_cdr2_be_bytes[..n]);
out
} else {
md5(plain_cdr2_be_bytes)
}
}
#[derive(Debug, Default)]
pub struct PlainCdr2BeKeyHolder {
bytes: Vec<u8>,
}
impl PlainCdr2BeKeyHolder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
fn pad_to(&mut self, align: usize) {
let pad = (align - (self.bytes.len() % align)) % align;
for _ in 0..pad {
self.bytes.push(0);
}
}
pub fn write_u8(&mut self, v: u8) {
self.bytes.push(v);
}
pub fn write_i8(&mut self, v: i8) {
self.bytes.push(v as u8);
}
pub fn write_u16(&mut self, v: u16) {
self.pad_to(2);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_i16(&mut self, v: i16) {
self.pad_to(2);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_u32(&mut self, v: u32) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_i32(&mut self, v: i32) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_u64(&mut self, v: u64) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_i64(&mut self, v: i64) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_f32(&mut self, v: f32) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_f64(&mut self, v: f64) {
self.pad_to(4);
self.bytes.extend_from_slice(&v.to_be_bytes());
}
pub fn write_string(&mut self, s: &str) {
self.pad_to(4);
let len = u32::try_from(s.len() + 1).unwrap_or(u32::MAX);
self.bytes.extend_from_slice(&len.to_be_bytes());
self.bytes.extend_from_slice(s.as_bytes());
self.bytes.push(0); }
pub fn write_bytes(&mut self, b: &[u8]) {
self.bytes.extend_from_slice(b);
}
}
#[must_use]
pub fn keyhash_cdr2_be(
members: &[(u32, alloc::vec::Vec<u8>)],
key_holder_max_size: usize,
) -> [u8; KEY_HASH_LEN] {
let mut sorted: alloc::vec::Vec<&(u32, alloc::vec::Vec<u8>)> = members.iter().collect();
sorted.sort_by_key(|(id, _)| *id);
let mut concat: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
for (_, bytes) in &sorted {
concat.extend_from_slice(bytes);
}
compute_key_hash(&concat, key_holder_max_size)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn small_keyholder_zero_padded_to_16() {
let bytes = 0x1234_5678u32.to_be_bytes();
let h = compute_key_hash(&bytes, 4);
assert_eq!(h[0..4], [0x12, 0x34, 0x56, 0x78]);
assert_eq!(h[4..16], [0u8; 12]);
}
#[test]
fn exactly_16_byte_keyholder_no_md5() {
let mut bytes = [0u8; 16];
for (i, b) in bytes.iter_mut().enumerate() {
*b = i as u8;
}
let h = compute_key_hash(&bytes, 16);
assert_eq!(h, bytes);
}
#[test]
fn input_shorter_than_max_zero_padded() {
let h = compute_key_hash(&[1, 2, 3, 4, 5], 16);
assert_eq!(&h[..5], &[1, 2, 3, 4, 5]);
assert_eq!(&h[5..], &[0u8; 11]);
}
#[test]
fn large_keyholder_md5_hashed() {
let bytes = [0u8; 20];
let h = compute_key_hash(&bytes, 20);
assert_eq!(
h,
[
0x44, 0x10, 0x18, 0x52, 0x52, 0x08, 0x45, 0x77, 0x05, 0xbf, 0x09, 0xa8, 0xee, 0x3c,
0x10, 0x93
]
);
}
#[test]
fn md5_known_vector_empty_string() {
let h = compute_key_hash(&[], 17);
assert_eq!(
h,
[
0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8,
0x42, 0x7e
]
);
}
#[test]
fn md5_known_vector_abc() {
let h = compute_key_hash(b"abc", 17);
assert_eq!(
h,
[
0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1,
0x7f, 0x72
]
);
}
#[test]
fn keyholder_writes_u32_be() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u32(0x1234_5678);
assert_eq!(h.into_bytes(), vec![0x12, 0x34, 0x56, 0x78]);
}
#[test]
fn keyholder_pads_to_4_for_u32_after_u8() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xAA);
h.write_u32(0x1234_5678);
assert_eq!(h.into_bytes(), vec![0xAA, 0, 0, 0, 0x12, 0x34, 0x56, 0x78]);
}
#[test]
fn keyholder_u64_aligned_to_4_not_8() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(1);
h.write_u64(0x1122_3344_5566_7788);
assert_eq!(h.len(), 1 + 3 + 8);
}
#[test]
fn keyholder_string_length_prefixed_with_nul() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_string("hi");
assert_eq!(h.into_bytes(), vec![0x00, 0x00, 0x00, 0x03, b'h', b'i', 0]);
}
#[test]
fn keyholder_struct_member_order() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u32(42); h.write_string("foo"); let bytes = h.into_bytes();
assert_eq!(bytes.len(), 12);
assert_eq!(&bytes[0..4], &[0, 0, 0, 42]);
assert_eq!(&bytes[4..8], &[0, 0, 0, 4]);
assert_eq!(&bytes[8..12], &[b'f', b'o', b'o', 0]);
}
#[test]
fn keyholder_full_keyhash_pipeline_zero_pad() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u32(0x1122_3344);
let h_bytes = h.into_bytes();
let key = compute_key_hash(&h_bytes, 4);
assert_eq!(&key[..4], &[0x11, 0x22, 0x33, 0x44]);
assert_eq!(&key[4..], &[0u8; 12]);
}
#[test]
fn keyholder_full_keyhash_pipeline_md5() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_string("hello-world-this-is-a-long-key-name-exceeding-16");
let h_bytes = h.into_bytes();
let key = compute_key_hash(&h_bytes, usize::MAX);
assert_ne!(key, [0u8; 16]);
}
#[test]
fn keyhash_cdr2_be_member_order_independent() {
let m_3 = (3u32, vec![0xAAu8, 0xBB, 0xCC, 0xDD]);
let m_5 = (5u32, vec![0x11u8, 0x22, 0x33, 0x44]);
let m_7 = (7u32, vec![0xDEu8, 0xAD, 0xBE, 0xEF]);
let order_a = vec![m_3.clone(), m_5.clone(), m_7.clone()];
let order_b = vec![m_7.clone(), m_3.clone(), m_5.clone()];
let order_c = vec![m_5.clone(), m_7.clone(), m_3.clone()];
let h_a = keyhash_cdr2_be(&order_a, 12);
let h_b = keyhash_cdr2_be(&order_b, 12);
let h_c = keyhash_cdr2_be(&order_c, 12);
assert_eq!(h_a, h_b);
assert_eq!(h_a, h_c);
let expected = {
let mut v = Vec::new();
v.extend_from_slice(&m_3.1);
v.extend_from_slice(&m_5.1);
v.extend_from_slice(&m_7.1);
v.resize(16, 0);
v
};
assert_eq!(h_a.as_slice(), expected.as_slice());
}
#[test]
fn keyhash_cdr2_be_md5_path_also_order_independent() {
let m_1 = (1u32, b"alpha-content-1".to_vec());
let m_2 = (2u32, b"beta-content-22".to_vec());
let m_3 = (3u32, b"gamma-content-333".to_vec());
let m_4 = (4u32, b"delta-content-4444".to_vec());
let order_a = vec![m_1.clone(), m_2.clone(), m_3.clone(), m_4.clone()];
let order_b = vec![m_4.clone(), m_3.clone(), m_2.clone(), m_1.clone()];
let h_a = keyhash_cdr2_be(&order_a, 1024);
let h_b = keyhash_cdr2_be(&order_b, 1024);
assert_eq!(h_a, h_b);
assert_ne!(h_a, [0u8; 16]);
}
#[test]
fn keyhash_cdr2_be_single_member_matches_compute_key_hash() {
let m = (42u32, vec![0xAB, 0xCD, 0xEF, 0x12]);
let h = keyhash_cdr2_be(&[m.clone()], 4);
let expected = compute_key_hash(&m.1, 4);
assert_eq!(h, expected);
}
#[test]
fn keyhash_cdr2_be_empty_member_set_yields_zero_padding() {
let h = keyhash_cdr2_be(&[], 4);
assert_eq!(h, [0u8; 16]);
}
#[test]
fn keyhash_cdr2_be_member_id_zero_sorts_first() {
let m_0 = (0u32, vec![0xFFu8, 0xEE, 0xDD, 0xCC]);
let m_99 = (99u32, vec![0x00u8, 0x11, 0x22, 0x33]);
let h_natural = keyhash_cdr2_be(&[m_0.clone(), m_99.clone()], 8);
let h_swapped = keyhash_cdr2_be(&[m_99.clone(), m_0.clone()], 8);
assert_eq!(h_natural, h_swapped);
let mut expected = [0u8; 16];
expected[..4].copy_from_slice(&m_0.1);
expected[4..8].copy_from_slice(&m_99.1);
assert_eq!(h_natural, expected);
}
#[test]
fn keyholder_is_empty_initially_and_after_write_not_empty() {
let mut h = PlainCdr2BeKeyHolder::new();
assert!(h.is_empty());
assert_eq!(h.len(), 0);
h.write_u8(0x01);
assert!(!h.is_empty());
assert_eq!(h.len(), 1);
}
#[test]
fn keyholder_as_bytes_returns_written_content() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xDE);
h.write_u8(0xAD);
h.write_u8(0xBE);
h.write_u8(0xEF);
assert_eq!(h.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(h.as_bytes(), &[0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn keyholder_write_i8_negative_two_complement() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_i8(-1);
h.write_i8(-128);
h.write_i8(127);
assert_eq!(h.into_bytes(), vec![0xFF, 0x80, 0x7F]);
}
#[test]
fn keyholder_write_u16_be_with_alignment() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xAA); h.write_u16(0x1234); assert_eq!(h.into_bytes(), vec![0xAA, 0x00, 0x12, 0x34]);
}
#[test]
fn keyholder_write_i16_be_with_alignment() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xAA);
h.write_i16(-1);
assert_eq!(h.into_bytes(), vec![0xAA, 0x00, 0xFF, 0xFF]);
}
#[test]
fn keyholder_write_i32_be_with_alignment() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xAA);
h.write_i32(-1);
assert_eq!(h.into_bytes(), vec![0xAA, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn keyholder_write_i64_be_with_4byte_alignment() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0xAA);
h.write_i64(-1);
let mut expected = vec![0xAA, 0, 0, 0];
expected.extend_from_slice(&[0xFF; 8]);
assert_eq!(h.into_bytes(), expected);
}
#[test]
fn keyholder_write_f32_be_known_pattern() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_f32(1.0_f32);
assert_eq!(h.into_bytes(), vec![0x3F, 0x80, 0x00, 0x00]);
}
#[test]
fn keyholder_write_f64_be_known_pattern() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_f64(1.0_f64);
assert_eq!(
h.into_bytes(),
vec![0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
);
}
#[test]
fn keyholder_write_bytes_appends_raw() {
let mut h = PlainCdr2BeKeyHolder::new();
h.write_u8(0x01);
h.write_bytes(&[0xDE, 0xAD, 0xBE, 0xEF]);
h.write_bytes(&[]);
h.write_bytes(&[0xFF]);
assert_eq!(h.into_bytes(), vec![0x01, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF]);
}
}