use std::{
borrow::Cow,
fmt::{Debug, Display},
hash::Hash,
};
use sha2::{Digest, Sha256};
#[derive(Clone)]
pub struct Label<'a> {
inner: LabelInner<'a>,
}
#[derive(Clone, Debug)]
enum LabelInner<'a> {
Anon(u128),
Label {
value: Cow<'a, str>,
key: Cow<'a, str>,
},
}
impl<'a> Eq for Label<'a> {
fn assert_receiver_is_total_eq(&self) {}
}
impl<'a> PartialEq for Label<'a> {
fn eq(&self, other: &Self) -> bool {
self.anon() == other.anon()
}
}
impl<'a> Hash for Label<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u128(self.anon())
}
}
impl From<u128> for Label<'static> {
fn from(value: u128) -> Self {
Self {
inner: LabelInner::Anon(value),
}
}
}
impl<'a> Label<'a> {
pub fn new(key: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
Self {
inner: LabelInner::Label {
key: key.into(),
value: value.into(),
},
}
}
pub fn anon_key(&self) -> u32 {
match &self.inner {
LabelInner::Anon(v) => *v as u32,
LabelInner::Label { key, value: _ } => {
let mut hash = Sha256::default();
hash.update(key.as_ref());
let hash = hash.finalize();
let mut result = 0;
for (i, byte) in hash.into_iter().enumerate() {
result ^= (byte as u32) << 8 * (3 - i % 4);
}
result
}
}
}
fn anon_value(&self) -> u128 {
self.anon() >> 32
}
pub fn anon(&self) -> u128 {
match &self.inner {
LabelInner::Anon(v) => *v,
LabelInner::Label { key: _, ref value } => Self::value_to_anon(self.anon_key(), value),
}
}
pub fn to_anon(&self) -> Label<'static> {
Label::from(self.anon())
}
pub fn from_anon(label: u128) -> Label<'static> {
Label::from(label)
}
pub fn with_value(&self, new_value: impl Into<Cow<'a, str>>) -> Self {
match &self.inner {
LabelInner::Anon(v) => Label::from(*v),
LabelInner::Label { value: _, key } => Label::new(key.clone(), new_value),
}
}
pub fn set_value(&mut self, new_value: impl Into<Cow<'a, str>>) {
match self.inner {
LabelInner::Anon(_) => {
*self = Label::from(Self::value_to_anon(self.anon_key(), &new_value.into()));
}
LabelInner::Label {
key: _,
ref mut value,
} => *value = new_value.into(),
}
}
fn value_to_anon(key: u32, value: &Cow<'a, str>) -> u128 {
let mut hash = Sha256::default();
hash.update(key.to_be_bytes());
hash.update(value.as_bytes());
let hash = hash.finalize();
let mut result = 0;
for (i, byte) in hash.into_iter().enumerate() {
result ^= (byte as u128) << 8 * (11 - i % 12);
}
result = (result << 32) | key as u128;
result &= 0xFFFFFFFF_FFFFFFFF_3FFFFFFF_FFFFFFFF;
result |= 0x00000000_00000000_80000000_00000000;
result &= 0xFFFFFFFF_FFFF0FFF_FFFFFFFF_FFFFFFFF;
result |= 0x00000000_00004000_00000000_00000000;
result
}
}
impl<'a> Debug for Label<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inner {
LabelInner::Anon(_) => f
.debug_struct("Label")
.field("value", &(self.anon_value()))
.field("key", &self.anon_key())
.finish(),
LabelInner::Label { .. } => Debug::fmt(&self.inner, f),
}
}
}
impl<'a> Display for Label<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:024x}({:08x})", self.anon_value(), self.anon_key())
}
}
#[test]
fn test_u128_alignment() {
let test = 0xFEDCBA98_76543210_01234567_89ABCDEF;
let sut = Label::from(test);
let key = sut.anon_key();
assert_eq!(key, 0x89ABCDEF);
}
#[test]
fn test_anonymization() {
assert_eq!(
format!("{:032X}", Label::new("a", "b").anon()),
"903BE7F26B4D4642B634B58FC5F94F92"
);
assert_eq!(
format!("{:032X}", Label::new("a", "B").anon()),
"0EC733AFD7AA474D9DCDA488C5F94F92"
);
assert_eq!(
format!("{:032X}", Label::new("A", "B").anon()),
"C10EE7A388B74940AA3A7F7AD316D17E"
);
assert_eq!(
format!("{:032X}", Label::new("A", "b").anon()),
"F3094A165F504E818B96005BD316D17E"
);
}
#[test]
fn test_empty_value() {
assert_eq!(
format!("{:032X}", Label::new("A", "").anon()),
"DDED29E4EDD9419D97E7C44BD316D17E"
);
}
#[test]
fn test_set_value_anon() {
let mut sut = Label::new("A", "").to_anon();
sut.set_value("X");
assert_eq!(
format!("{:032X}", sut.anon()),
"37EECB7183EF4A479B500B31D316D17E"
);
}
#[test]
fn test_set_value() {
let mut sut = Label::new("A", "");
sut.set_value("X");
assert_eq!(
format!("{:032X}", sut.anon()),
"37EECB7183EF4A479B500B31D316D17E"
);
}
#[test]
fn test_uuid_compatibility() {
assert_eq!(
uuid::Uuid::from_u128(Label::new("anything", "you want").anon()).get_version_num(),
4
);
assert_eq!(
uuid::Uuid::from_u128(Label::new("anything", "you need").anon()).get_variant(),
uuid::Variant::RFC4122
);
}
#[test]
fn test_debug_anon() {
let sut = Label::new("anything", "you want").to_anon();
assert_eq!(
format!("{:x?}", sut),
"Label { value: 6b24144371ab423cab17608f, key: 42454c30 }"
);
}
#[test]
fn test_debug() {
let sut = Label::new("anything", "you want");
assert_eq!(
format!("{:?}", sut),
"Label { value: \"you want\", key: \"anything\" }"
);
}
#[test]
fn test_display_anon() {
let sut = Label::new("anything", "you want").to_anon();
assert_eq!(format!("{}", sut), "6b24144371ab423cab17608f(42454c30)");
}
#[test]
fn test_display() {
let sut = Label::new("anything", "you want");
assert_eq!(format!("{}", sut), "6b24144371ab423cab17608f(42454c30)");
}