Skip to main content

efema_proto/
ids.rs

1//! Identities and numbers that travel on the wire: which incarnation of a
2//! stream, which history, which format.
3
4use std::fmt;
5use std::str::FromStr;
6
7use minicbor::decode::{Decoder, Error as DecodeError};
8use minicbor::encode::{Encoder, Error as EncodeError, Write};
9use minicbor::{Decode, Encode};
10
11use crate::hex;
12
13/// Why a text is not an identity or a hash.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct ParseIdError {
16    what: &'static str,
17    digits: usize,
18}
19
20impl fmt::Display for ParseIdError {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(f, "a {} is {} lowercase hexadecimal digits", self.what, self.digits)
23    }
24}
25
26impl std::error::Error for ParseIdError {}
27
28/// Which incarnation of a stream name this is.
29///
30/// The relay draws sixteen random bytes when a stream is created and never
31/// changes them. A name can outlive its stream - a relay's data wiped, a stream
32/// deleted and written again - and a position means nothing across that gap:
33/// position 40 of the old stream and position 40 of the new one are unrelated
34/// entries. The identity is what lets a reader tell the two apart, instead of
35/// reading on from a cursor that points into a history that no longer exists.
36#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct StreamId([u8; 16]);
38
39impl StreamId {
40    /// Wraps sixteen bytes. The relay draws them at random; anything else is
41    /// for tests.
42    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
43        Self(bytes)
44    }
45
46    /// The sixteen bytes.
47    pub const fn as_bytes(&self) -> &[u8; 16] {
48        &self.0
49    }
50}
51
52impl fmt::Display for StreamId {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        hex::write(&self.0, f)
55    }
56}
57
58impl fmt::Debug for StreamId {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "StreamId({self})")
61    }
62}
63
64impl FromStr for StreamId {
65    type Err = ParseIdError;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        hex::decode::<16>(s).map(Self).ok_or(ParseIdError { what: "stream identity", digits: 32 })
69    }
70}
71
72/// A link in a stream's hash chain: SHA-256 over the previous link and the
73/// entry at this position (see [`crate::chain`]).
74#[derive(Clone, Copy, PartialEq, Eq, Hash)]
75pub struct Hash([u8; 32]);
76
77impl Hash {
78    /// Wraps thirty-two bytes.
79    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
80        Self(bytes)
81    }
82
83    /// The thirty-two bytes.
84    pub const fn as_bytes(&self) -> &[u8; 32] {
85        &self.0
86    }
87
88    /// The first eight hex digits - enough to tell hashes apart in a listing,
89    /// never enough to compare them.
90    pub fn short(&self) -> String {
91        hex::encode(&self.0[..4])
92    }
93}
94
95impl fmt::Display for Hash {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        hex::write(&self.0, f)
98    }
99}
100
101impl fmt::Debug for Hash {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "Hash({self})")
104    }
105}
106
107impl FromStr for Hash {
108    type Err = ParseIdError;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        hex::decode::<32>(s).map(Self).ok_or(ParseIdError { what: "hash", digits: 64 })
112    }
113}
114
115/// The version of the format an app writes into a stream.
116///
117/// The relay does not know what an epoch means - for it an epoch is a number
118/// that only goes up. An app raises it when it changes the format of its own
119/// batches; from then on the relay refuses writes from anyone still on the
120/// older epoch, and every entry says which epoch it was written in, so a reader
121/// that does not know a newer format can stop before it instead of
122/// misreading it.
123#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
124#[cbor(transparent)]
125pub struct Epoch(pub u32);
126
127impl fmt::Display for Epoch {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        self.0.fmt(f)
130    }
131}
132
133// Identities and hashes travel as CBOR byte strings of exactly their length.
134// A shorter or longer one is an error at the decoder, not a value padded or
135// cut to fit.
136macro_rules! fixed_bytes {
137    ($type:ident, $len:literal) => {
138        impl<C> Encode<C> for $type {
139            fn encode<W: Write>(&self, e: &mut Encoder<W>, _: &mut C) -> Result<(), EncodeError<W::Error>> {
140                e.bytes(&self.0)?.ok()
141            }
142        }
143
144        impl<'b, C> Decode<'b, C> for $type {
145            fn decode(d: &mut Decoder<'b>, _: &mut C) -> Result<Self, DecodeError> {
146                let position = d.position();
147                let bytes = d.bytes()?;
148                <[u8; $len]>::try_from(bytes).map(Self).map_err(|_| {
149                    DecodeError::message(concat!("expected exactly ", stringify!($len), " bytes")).at(position)
150                })
151            }
152        }
153    };
154}
155
156fixed_bytes!(StreamId, 16);
157fixed_bytes!(Hash, 32);
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn identities_and_hashes_round_trip_through_text() {
165        let id = StreamId::from_bytes([0xab; 16]);
166        assert_eq!(id.to_string(), "ab".repeat(16));
167        assert_eq!(id.to_string().parse::<StreamId>(), Ok(id));
168
169        let hash = Hash::from_bytes([0x01; 32]);
170        assert_eq!(hash.to_string().parse::<Hash>(), Ok(hash));
171        assert_eq!(hash.short(), "01010101");
172    }
173
174    #[test]
175    fn a_byte_string_of_the_wrong_length_is_refused() {
176        let bytes = |len: usize| {
177            let mut e = Encoder::new(Vec::new());
178            e.bytes(&vec![0u8; len]).unwrap();
179            e.into_writer()
180        };
181        assert!(minicbor::decode::<StreamId>(&bytes(15)).is_err());
182        assert!(minicbor::decode::<StreamId>(&bytes(16)).is_ok());
183        assert!(minicbor::decode::<Hash>(&bytes(33)).is_err());
184        assert!(minicbor::decode::<Hash>(&bytes(32)).is_ok());
185    }
186
187    #[test]
188    fn an_array_of_numbers_is_not_a_byte_string() {
189        // What a careless encoder makes of `[u8; 16]`: an array of sixteen
190        // integers. It must not decode as an identity.
191        let array = minicbor::to_vec([0u8; 16]).unwrap();
192        assert!(minicbor::decode::<StreamId>(&array).is_err());
193    }
194}