turngate 0.1.0

Prevent or reduce abuse of your public service
Documentation
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;

        // compatible with RFC4122 variant
        result &= 0xFFFFFFFF_FFFFFFFF_3FFFFFFF_FFFFFFFF;
        result |= 0x00000000_00000000_80000000_00000000;
        // compatible with v4 random UUID
        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)");
}