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
976
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22
31232
        for v in self.0 {
23
31232
            write!(f, "{v:02x}")?;
24
        }
25
976
        Ok(())
26
976
    }
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
    #![allow(clippy::string_slice)] // See arti#2571
77
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
78
    use super::*;
79
    use itertools::{Itertools, chain};
80

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

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

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