1
//! [`IptLocalId`]
2

            
3
use rand::{Rng, RngExt};
4

            
5
use crate::internal_prelude::*;
6

            
7
/// Persistent local identifier for an introduction point
8
///
9
/// Changes when the IPT relay changes, or the IPT key material changes.
10
/// (Different for different `.onion` services, obviously)
11
///
12
/// Is a randomly-generated byte string, currently 32 long.
13
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deftly)]
14
#[derive_deftly(SerdeStringOrTransparent)]
15
#[cfg_attr(test, derive(derive_more::From))]
16
pub(crate) struct IptLocalId([u8; 32]);
17

            
18
impl_debug_hex!(IptLocalId.0);
19

            
20
impl Display for IptLocalId {
21
928
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22
29696
        for v in self.0 {
23
29696
            write!(f, "{v:02x}")?;
24
        }
25
928
        Ok(())
26
928
    }
27
}
28

            
29
/// Invalid [`IptLocalId`] - for example bad string representation
30
#[derive(Debug, Error, Clone, Eq, PartialEq)]
31
#[error("invalid IptLocalId")]
32
#[non_exhaustive]
33
pub(crate) struct InvalidIptLocalId {}
34

            
35
impl FromStr for IptLocalId {
36
    type Err = InvalidIptLocalId;
37
282
    fn from_str(s: &str) -> Result<Self, Self::Err> {
38
282
        let mut b = [0; 32];
39
282
        hex::decode_to_slice(s, &mut b).map_err(|_: hex::FromHexError| InvalidIptLocalId {})?;
40
282
        Ok(IptLocalId(b))
41
282
    }
42
}
43

            
44
impl KeySpecifierComponentViaDisplayFromStr for IptLocalId {}
45

            
46
impl IptLocalId {
47
    /// Return a fixed dummy `IptLocalId`, for testing etc.
48
    ///
49
    /// The id is made by repeating `which` 32 times.
50
    #[cfg(test)]
51
18
    pub(crate) fn dummy(which: u8) -> Self {
52
18
        IptLocalId([which; 32]) // I can't think of a good way not to specify 32 again here
53
18
    }
54
}
55

            
56
impl rand::distr::Distribution<IptLocalId> for rand::distr::StandardUniform {
57
28
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> IptLocalId {
58
28
        IptLocalId(rng.random())
59
28
    }
60
}
61

            
62
#[cfg(test)]
63
pub(crate) mod test {
64
    // @@ begin test lint list maintained by maint/add_warning @@
65
    #![allow(clippy::bool_assert_comparison)]
66
    #![allow(clippy::clone_on_copy)]
67
    #![allow(clippy::dbg_macro)]
68
    #![allow(clippy::mixed_attributes_style)]
69
    #![allow(clippy::print_stderr)]
70
    #![allow(clippy::print_stdout)]
71
    #![allow(clippy::single_char_pattern)]
72
    #![allow(clippy::unwrap_used)]
73
    #![allow(clippy::unchecked_time_subtraction)]
74
    #![allow(clippy::useless_vec)]
75
    #![allow(clippy::needless_pass_by_value)]
76
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
77
    use super::*;
78
    use itertools::{Itertools, chain};
79

            
80
    #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
81
    struct IptLidTest {
82
        lid: IptLocalId,
83
    }
84

            
85
    #[test]
86
    fn lid_serde() {
87
        let t = IptLidTest {
88
            lid: IptLocalId::dummy(7),
89
        };
90
        let json = serde_json::to_string(&t).unwrap();
91
        assert_eq!(
92
            json,
93
            // This also tests <IptLocalId as Display> since that's how we serialise it
94
            r#"{"lid":"0707070707070707070707070707070707070707070707070707070707070707"}"#,
95
        );
96
        let u: IptLidTest = serde_json::from_str(&json).unwrap();
97
        assert_eq!(t, u);
98

            
99
        let mpack = rmp_serde::to_vec_named(&t).unwrap();
100
        assert_eq!(
101
            mpack,
102
            chain!(&[129, 163], b"lid", &[220, 0, 32], &[0x07; 32],)
103
                .cloned()
104
                .collect_vec()
105
        );
106
        let u: IptLidTest = rmp_serde::from_slice(&mpack).unwrap();
107
        assert_eq!(t, u);
108
    }
109
}