use core::fmt;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::id::{ChangeId, EdgeId, NodeId};
pub const PROLLY_KEY_BYTES: usize = 16;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProllyKey(pub [u8; PROLLY_KEY_BYTES]);
impl ProllyKey {
#[must_use]
pub const fn new(bytes: [u8; PROLLY_KEY_BYTES]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; PROLLY_KEY_BYTES] {
&self.0
}
#[must_use]
pub const fn into_bytes(self) -> [u8; PROLLY_KEY_BYTES] {
self.0
}
}
impl fmt::Debug for ProllyKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ProllyKey(")?;
for b in &self.0 {
write!(f, "{b:02x}")?;
}
f.write_str(")")
}
}
impl From<NodeId> for ProllyKey {
fn from(v: NodeId) -> Self {
Self(v.into_bytes())
}
}
impl From<EdgeId> for ProllyKey {
fn from(v: EdgeId) -> Self {
Self(v.into_bytes())
}
}
impl From<ChangeId> for ProllyKey {
fn from(v: ChangeId) -> Self {
Self(v.into_bytes())
}
}
impl Serialize for ProllyKey {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(&self.0)
}
}
struct ProllyKeyVisitor;
impl<'de> Visitor<'de> for ProllyKeyVisitor {
type Value = ProllyKey;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a 16-byte prolly key byte string")
}
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
if v.len() != PROLLY_KEY_BYTES {
return Err(E::invalid_length(v.len(), &"16"));
}
let mut arr = [0u8; PROLLY_KEY_BYTES];
arr.copy_from_slice(v);
Ok(ProllyKey(arr))
}
fn visit_borrowed_bytes<E: de::Error>(self, v: &'de [u8]) -> Result<Self::Value, E> {
self.visit_bytes(v)
}
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
self.visit_bytes(&v)
}
}
impl<'de> Deserialize<'de> for ProllyKey {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_bytes(ProllyKeyVisitor)
}
}
pub const ROLLING_WINDOW_BYTES: usize = 64;
pub const ROLLING_KEY: [u8; 32] = [
0x6d, 0x6e, 0x65, 0x6d, 0x2d, 0x70, 0x72, 0x6f, 0x6c, 0x6c, 0x79, 0x2d, 0x72, 0x68, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
pub const MIN_ENTRIES_PER_CHUNK: usize = 16;
pub const TARGET_AVG_ENTRIES_PER_CHUNK: usize = 64;
pub const MAX_ENTRIES_PER_CHUNK: usize = 512;
pub const THRESHOLD: u64 = u64::MAX / 48;
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{from_canonical_bytes, to_canonical_bytes};
#[test]
fn rolling_key_is_ascii_prefix_plus_zeros() {
assert_eq!(&ROLLING_KEY[..16], b"mnem-prolly-rh-1");
assert!(ROLLING_KEY[16..].iter().all(|&b| b == 0));
}
#[test]
fn threshold_probability_is_about_one_in_48() {
let ratio = THRESHOLD as f64 / u64::MAX as f64;
assert!((ratio - 1.0 / 48.0).abs() < 1e-9, "ratio was {ratio}");
}
#[test]
fn bounds_are_ordered() {
assert!(MIN_ENTRIES_PER_CHUNK < TARGET_AVG_ENTRIES_PER_CHUNK);
assert!(TARGET_AVG_ENTRIES_PER_CHUNK < MAX_ENTRIES_PER_CHUNK);
}
#[test]
fn prolly_key_cbor_round_trip_as_byte_string() {
let original = ProllyKey([0xAB; PROLLY_KEY_BYTES]);
let bytes = to_canonical_bytes(&original).expect("encode");
assert_eq!(bytes[0], 0x50, "expected CBOR byte-string-16 prefix");
assert_eq!(bytes.len(), 17);
let decoded: ProllyKey = from_canonical_bytes(&bytes).expect("decode");
assert_eq!(original, decoded);
}
#[test]
fn node_id_converts_to_prolly_key() {
let n = NodeId::from_bytes_raw([7u8; 16]);
let k: ProllyKey = n.into();
assert_eq!(k.as_bytes(), &[7u8; 16]);
}
}