p2panda_rs/entry/
seq_num.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3use std::convert::TryFrom;
4use std::hash::Hash as StdHash;
5use std::str::FromStr;
6
7use bamboo_rs_core_ed25519_yasmf::lipmaa;
8use serde::{Deserialize, Serialize};
9
10use crate::entry::error::SeqNumError;
11use crate::serde::StringOrU64;
12
13/// Start counting entries from here.
14pub const FIRST_SEQ_NUM: u64 = 1;
15
16/// Sequence number describing the position of an entry in its append-only log.
17#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq, Ord, PartialOrd, StdHash)]
18pub struct SeqNum(u64);
19
20impl SeqNum {
21    /// Validates and wraps value into a new `SeqNum` instance.
22    ///
23    /// ## Example
24    ///
25    /// ```
26    /// # extern crate p2panda_rs;
27    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
28    /// use p2panda_rs::entry::SeqNum;
29    ///
30    /// // Generate new sequence number
31    /// let seq_num = SeqNum::new(2)?;
32    ///
33    /// # Ok(())
34    /// # }
35    /// ```
36    pub fn new(value: u64) -> Result<Self, SeqNumError> {
37        if value < FIRST_SEQ_NUM {
38            Err(SeqNumError::NotZeroOrNegative)
39        } else {
40            Ok(Self(value))
41        }
42    }
43
44    /// Return sequence number of the previous entry (backlink).
45    ///
46    /// ## Example
47    ///
48    /// ```
49    /// # extern crate p2panda_rs;
50    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
51    /// use p2panda_rs::entry::SeqNum;
52    ///
53    /// // Return backlink (sequence number of the previous entry)
54    /// let seq_num = SeqNum::new(2)?;
55    /// let backlink = seq_num.backlink_seq_num();
56    ///
57    /// assert_eq!(backlink, Some(SeqNum::new(1)?));
58    /// # Ok(())
59    /// # }
60    /// ```
61    pub fn backlink_seq_num(&self) -> Option<Self> {
62        Self::new(self.0 - 1).ok()
63    }
64
65    /// Return sequence number of the lipmaa entry (skiplink).
66    ///
67    /// See [Bamboo] specification for more details about how skiplinks are calculated.
68    ///
69    /// [Bamboo]: https://github.com/AljoschaMeyer/bamboo#links-and-entry-verification
70    pub fn skiplink_seq_num(&self) -> Option<Self> {
71        Some(Self(lipmaa(self.0)))
72    }
73
74    /// Returns true when sequence number marks first entry in log.
75    pub fn is_first(&self) -> bool {
76        self.0 == FIRST_SEQ_NUM
77    }
78
79    /// Returns `SeqNum` as u64 integer.
80    pub fn as_u64(&self) -> u64 {
81        self.0
82    }
83}
84
85impl Default for SeqNum {
86    fn default() -> Self {
87        Self::new(FIRST_SEQ_NUM).unwrap()
88    }
89}
90
91impl Iterator for SeqNum {
92    type Item = SeqNum;
93
94    fn next(&mut self) -> Option<Self::Item> {
95        match self.0 == std::u64::MAX {
96            true => None,
97            false => {
98                self.0 += 1;
99                Some(*self)
100            }
101        }
102    }
103}
104
105impl TryFrom<u64> for SeqNum {
106    type Error = SeqNumError;
107
108    fn try_from(value: u64) -> Result<Self, Self::Error> {
109        Self::new(value)
110    }
111}
112
113/// Convert any borrowed string representation of an u64 integer into an `SeqNum` instance.
114impl FromStr for SeqNum {
115    type Err = SeqNumError;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        Self::new(u64::from_str(s).map_err(|_| SeqNumError::InvalidU64String)?)
119    }
120}
121
122/// Convert any owned string representation of an u64 integer into an `SeqNum` instance.
123impl TryFrom<String> for SeqNum {
124    type Error = SeqNumError;
125
126    fn try_from(str: String) -> Result<Self, Self::Error> {
127        Self::new(u64::from_str(&str).map_err(|_| SeqNumError::InvalidU64String)?)
128    }
129}
130
131impl<'de> Deserialize<'de> for SeqNum {
132    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133    where
134        D: serde::Deserializer<'de>,
135    {
136        deserializer.deserialize_any(StringOrU64::<SeqNum>::new())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use std::convert::TryFrom;
143
144    use rstest::rstest;
145    use serde::Serialize;
146
147    use super::SeqNum;
148
149    #[test]
150    fn validate() {
151        assert!(SeqNum::new(0).is_err());
152        assert!(SeqNum::new(100).is_ok());
153    }
154
155    #[test]
156    fn iterator() {
157        let mut seq_num = SeqNum::new(1).unwrap();
158
159        assert_eq!(Some(SeqNum(2)), seq_num.next());
160        assert_eq!(Some(SeqNum(3)), seq_num.next());
161
162        seq_num = SeqNum(std::u64::MAX - 1);
163
164        assert_eq!(Some(SeqNum(std::u64::MAX)), seq_num.next());
165        assert_eq!(None, seq_num.next());
166    }
167
168    #[test]
169    fn skiplink_seq_num() {
170        assert_eq!(
171            SeqNum::new(13).unwrap().skiplink_seq_num().unwrap(),
172            SeqNum::new(4).unwrap()
173        );
174    }
175
176    #[test]
177    fn backlink_seq_num() {
178        assert_eq!(
179            SeqNum::new(12).unwrap().backlink_seq_num().unwrap(),
180            SeqNum::new(11).unwrap()
181        );
182
183        assert!(SeqNum::new(1).unwrap().backlink_seq_num().is_none());
184    }
185
186    #[test]
187    fn string_conversions() {
188        let large_number = "91772991776239";
189        let seq_num_from_str: SeqNum = large_number.parse().unwrap();
190        let seq_num_try_from = SeqNum::try_from(String::from(large_number)).unwrap();
191        assert_eq!(91772991776239, seq_num_from_str.as_u64());
192        assert_eq!(seq_num_from_str, seq_num_try_from);
193    }
194
195    #[rstest]
196    #[case("1", Some(SeqNum::new(1).unwrap()))]
197    #[case(12, Some(SeqNum::new(12).unwrap()))]
198    #[case("-1", None)]
199    #[case(-12, None)]
200    #[case("18446744073709551616", None)] // u64::MAX + 1
201    #[case("0", None)]
202    #[case("Not a sequence number", None)]
203    fn deserialize_str_and_u64(
204        #[case] value: impl Serialize + Sized,
205        #[case] expected_result: Option<SeqNum>,
206    ) {
207        fn convert<T: Serialize + Sized>(value: T) -> Result<SeqNum, Box<dyn std::error::Error>> {
208            let mut cbor_bytes = Vec::new();
209            ciborium::ser::into_writer(&value, &mut cbor_bytes)?;
210            let log_id: SeqNum = ciborium::de::from_reader(&cbor_bytes[..])?;
211            Ok(log_id)
212        }
213
214        match expected_result {
215            Some(result) => {
216                assert_eq!(convert(value).unwrap(), result);
217            }
218            None => {
219                assert!(convert(value).is_err());
220            }
221        }
222    }
223}