foundation_ur/ur/
mod.rs

1// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
2// SPDX-FileCopyrightText: © 2020 Dominik Spicher <dominikspicher@gmail.com>
3// SPDX-License-Identifier: MIT
4
5pub mod decoder;
6pub mod encoder;
7
8#[cfg(feature = "alloc")]
9pub use self::decoder::Decoder;
10pub use self::decoder::{BaseDecoder, HeaplessDecoder};
11
12#[cfg(feature = "alloc")]
13pub use self::encoder::Encoder;
14pub use self::encoder::{BaseEncoder, HeaplessEncoder};
15
16use crate::{
17    bytewords::{Bytewords, Style},
18    fountain::part::Part,
19};
20use core::{fmt, num::ParseIntError};
21
22/// An uniform resource.
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
25pub enum UR<'a> {
26    /// A single-part resource.
27    SinglePart {
28        /// The type.
29        ur_type: &'a str,
30        /// The message.
31        message: &'a str,
32    },
33    /// A deserialized single-part resource.
34    SinglePartDeserialized {
35        /// The type.
36        ur_type: &'a str,
37        /// The message.
38        message: &'a [u8],
39    },
40    /// A multiple-part resource.
41    MultiPart {
42        /// The type.
43        ur_type: &'a str,
44        /// The fragment.
45        fragment: &'a str,
46        /// The sequence number.
47        sequence: u32,
48        /// The total sequence count.
49        sequence_count: u32,
50    },
51    /// A deserialized multiple-part resource.
52    MultiPartDeserialized {
53        /// The type.
54        ur_type: &'a str,
55        /// The fragment.
56        fragment: Part<'a>,
57    },
58}
59
60impl<'a> UR<'a> {
61    /// Construct a new single-part [`UR`].
62    pub fn new(ur_type: &'a str, message: &'a [u8]) -> Self {
63        UR::SinglePartDeserialized { ur_type, message }
64    }
65
66    /// Parses an uniform resource string.
67    ///
68    /// Keep in mind, this does not deserialize the `bytewords` payload,
69    /// deserialization is performed separately, for example, by the
70    /// [decoder](BaseDecoder).
71    pub fn parse(s: &'a str) -> Result<Self, ParseURError> {
72        let (ur_type, rest) = s
73            .strip_prefix("ur:")
74            .ok_or(ParseURError::InvalidScheme)?
75            .split_once('/')
76            .ok_or(ParseURError::TypeUnspecified)?;
77
78        if !ur_type
79            .trim_start_matches(|c: char| c.is_ascii_alphanumeric() || c == '-')
80            .is_empty()
81        {
82            return Err(ParseURError::InvalidCharacters);
83        }
84
85        match rest.rsplit_once('/') {
86            None => Ok(UR::SinglePart {
87                ur_type,
88                message: rest,
89            }),
90            Some((indices, fragment)) => {
91                let (sequence, sequence_count) = indices
92                    .split_once('-')
93                    .ok_or(ParseURError::InvalidIndices)?;
94
95                Ok(UR::MultiPart {
96                    ur_type,
97                    fragment,
98                    sequence: sequence.parse()?,
99                    sequence_count: sequence_count.parse()?,
100                })
101            }
102        }
103    }
104
105    /// Returns true if the Uniform Resource is single-part.
106    #[inline]
107    pub fn is_single_part(&self) -> bool {
108        matches!(
109            self,
110            UR::SinglePart { .. } | UR::SinglePartDeserialized { .. }
111        )
112    }
113
114    /// Returns `true` if the Uniform Resource is multi-part.
115    #[inline]
116    pub fn is_multi_part(&self) -> bool {
117        matches!(
118            self,
119            UR::MultiPart { .. } | UR::MultiPartDeserialized { .. }
120        )
121    }
122
123    /// Returns `true` if this Uniform Resource is multi-part and deserialized.
124    #[inline]
125    pub fn is_deserialized(&self) -> bool {
126        matches!(
127            self,
128            UR::SinglePartDeserialized { .. } | UR::MultiPartDeserialized { .. }
129        )
130    }
131
132    /// Returns the UR type.
133    pub fn as_type(&self) -> &str {
134        match self {
135            UR::SinglePart { ur_type, .. } => ur_type,
136            UR::SinglePartDeserialized { ur_type, .. } => ur_type,
137            UR::MultiPart { ur_type, .. } => ur_type,
138            UR::MultiPartDeserialized { ur_type, .. } => ur_type,
139        }
140    }
141
142    /// Returns `Some(bytewords)` if the Uniform Resource is serialized.
143    pub fn as_bytewords(&self) -> Option<&str> {
144        match self {
145            UR::SinglePart { message, .. } => Some(message),
146            UR::MultiPart { fragment, .. } => Some(fragment),
147            _ => None,
148        }
149    }
150
151    /// Returns `Some(part)` if the Uniform Resource is multi-part and is
152    /// deserialized.
153    pub fn as_part(&self) -> Option<&Part> {
154        match self {
155            UR::MultiPartDeserialized { fragment, .. } => Some(fragment),
156            _ => None,
157        }
158    }
159
160    /// Returns `Some(n)` where `n` is the sequence number if the Uniform
161    /// Resource is multi part.
162    pub fn sequence(&self) -> Option<u32> {
163        match self {
164            UR::MultiPart { sequence, .. } => Some(*sequence),
165            UR::MultiPartDeserialized { fragment, .. } => Some(fragment.sequence),
166            _ => None,
167        }
168    }
169
170    /// Returns `Some(n)` where `n` is the sequence count if the Uniform
171    /// Resource is multi part.
172    pub fn sequence_count(&self) -> Option<u32> {
173        match self {
174            UR::MultiPart { sequence_count, .. } => Some(*sequence_count),
175            UR::MultiPartDeserialized { fragment, .. } => Some(fragment.sequence_count),
176            _ => None,
177        }
178    }
179}
180
181impl<'a> fmt::Display for UR<'a> {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            UR::SinglePart { ur_type, message } => {
185                write!(f, "ur:{ur_type}/{message}")
186            }
187            UR::SinglePartDeserialized { ur_type, message } => {
188                let message = Bytewords(message, Style::Minimal);
189                write!(f, "ur:{ur_type}/{message}")
190            }
191            UR::MultiPart {
192                ur_type,
193                fragment,
194                sequence,
195                sequence_count,
196            } => {
197                write!(f, "ur:{ur_type}/{sequence}-{sequence_count}/{fragment}")
198            }
199            UR::MultiPartDeserialized { ur_type, fragment } => {
200                let (sequence, sequence_count) = (fragment.sequence, fragment.sequence_count);
201                write!(f, "ur:{ur_type}/{sequence}-{sequence_count}/{fragment}",)
202            }
203        }
204    }
205}
206
207/// Errors that can happen during parsing of Uniform Resources.
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub enum ParseURError {
210    /// Invalid scheme.
211    InvalidScheme,
212    /// No type specified.
213    TypeUnspecified,
214    /// Invalid characters.
215    InvalidCharacters,
216    /// Invalid indices in multi-part UR.
217    InvalidIndices,
218    /// Could not parse indices integers.
219    ParseInt(ParseIntError),
220}
221
222#[cfg(feature = "std")]
223impl std::error::Error for ParseURError {}
224
225impl fmt::Display for ParseURError {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        match self {
228            ParseURError::InvalidScheme => write!(f, "Invalid Uniform Resource scheme"),
229            ParseURError::TypeUnspecified => {
230                write!(f, "No type was specified for the Uniform Resource")
231            }
232            ParseURError::InvalidCharacters => {
233                write!(f, "Uniform Resource type contains invalid characters")
234            }
235            ParseURError::InvalidIndices => write!(f, "Uniform Resource indices are invalid"),
236            ParseURError::ParseInt(e) => {
237                write!(f, "Could not parse Uniform Resource indices: {e}")
238            }
239        }
240    }
241}
242
243impl From<ParseIntError> for ParseURError {
244    fn from(e: ParseIntError) -> Self {
245        Self::ParseInt(e)
246    }
247}
248
249/// Encode a single part UR to a string.
250#[cfg(feature = "alloc")]
251pub fn to_string(ur_type: &str, message: &[u8]) -> alloc::string::String {
252    let ur = UR::SinglePartDeserialized { ur_type, message };
253
254    ur.to_string()
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use core::num::IntErrorKind;
261
262    #[cfg(feature = "alloc")]
263    pub fn make_message_ur(length: usize, seed: &str) -> Vec<u8> {
264        let message = crate::xoshiro::test_utils::make_message(seed, length);
265        minicbor::to_vec(minicbor::bytes::ByteVec::from(message)).unwrap()
266    }
267
268    #[test]
269    #[cfg(feature = "alloc")]
270    fn test_single_part_ur() {
271        const EXPECTED: &str = "ur:bytes/hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch";
272
273        let encoded = UR::new("bytes", &make_message_ur(50, "Wolf")).to_string();
274        assert_eq!(&encoded, EXPECTED);
275
276        let parsed = UR::parse(&encoded).unwrap();
277        assert!(matches!(parsed, UR::SinglePart {
278            ur_type: "bytes",
279            message: "hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch",
280        }));
281    }
282
283    #[test]
284    #[cfg(feature = "alloc")]
285    fn test_ur_roundtrip() {
286        let ur = make_message_ur(32767, "Wolf");
287        let mut encoder = Encoder::new();
288        encoder.start("bytes", &ur, 1000);
289
290        let mut decoder = Decoder::default();
291        while !decoder.is_complete() {
292            assert_eq!(decoder.message().unwrap(), None);
293            decoder.receive(encoder.next_part()).unwrap();
294        }
295        assert_eq!(decoder.message().unwrap(), Some(ur.as_slice()));
296    }
297
298    #[test]
299    fn test_parser() {
300        UR::parse("ur:bytes/aeadaolazmjendeoti").unwrap();
301        UR::parse("ur:whatever-12/aeadaolazmjendeoti").unwrap();
302    }
303
304    #[test]
305    fn test_parser_errors() {
306        const TEST_VECTORS: &[(&str, ParseURError)] = &[
307            ("uhr:bytes/aeadaolazmjendeoti", ParseURError::InvalidScheme),
308            ("ur:aeadaolazmjendeoti", ParseURError::TypeUnspecified),
309            (
310                "ur:bytes#4/aeadaolazmjendeoti",
311                ParseURError::InvalidCharacters,
312            ),
313            (
314                "ur:bytes/1 1/aeadaolazmjendeoti",
315                ParseURError::InvalidIndices,
316            ),
317        ];
318
319        for (input, error) in TEST_VECTORS {
320            assert_eq!(UR::parse(&input).unwrap_err(), error.clone());
321        }
322
323        match UR::parse("ur:bytes/1-1/toomuch/aeadaolazmjendeoti") {
324            Err(ParseURError::ParseInt(e)) => {
325                assert_eq!(*e.kind(), IntErrorKind::InvalidDigit)
326            }
327            _ => panic!(),
328        }
329
330        match UR::parse("ur:bytes/1-1a/aeadaolazmjendeoti") {
331            Err(ParseURError::ParseInt(e)) => {
332                assert_eq!(*e.kind(), IntErrorKind::InvalidDigit)
333            }
334            _ => panic!(),
335        }
336    }
337}