1pub 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#[derive(Debug, Clone)]
24#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
25pub enum UR<'a> {
26 SinglePart {
28 ur_type: &'a str,
30 message: &'a str,
32 },
33 SinglePartDeserialized {
35 ur_type: &'a str,
37 message: &'a [u8],
39 },
40 MultiPart {
42 ur_type: &'a str,
44 fragment: &'a str,
46 sequence: u32,
48 sequence_count: u32,
50 },
51 MultiPartDeserialized {
53 ur_type: &'a str,
55 fragment: Part<'a>,
57 },
58}
59
60impl<'a> UR<'a> {
61 pub fn new(ur_type: &'a str, message: &'a [u8]) -> Self {
63 UR::SinglePartDeserialized { ur_type, message }
64 }
65
66 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 #[inline]
107 pub fn is_single_part(&self) -> bool {
108 matches!(
109 self,
110 UR::SinglePart { .. } | UR::SinglePartDeserialized { .. }
111 )
112 }
113
114 #[inline]
116 pub fn is_multi_part(&self) -> bool {
117 matches!(
118 self,
119 UR::MultiPart { .. } | UR::MultiPartDeserialized { .. }
120 )
121 }
122
123 #[inline]
125 pub fn is_deserialized(&self) -> bool {
126 matches!(
127 self,
128 UR::SinglePartDeserialized { .. } | UR::MultiPartDeserialized { .. }
129 )
130 }
131
132 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 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 pub fn as_part(&self) -> Option<&Part> {
154 match self {
155 UR::MultiPartDeserialized { fragment, .. } => Some(fragment),
156 _ => None,
157 }
158 }
159
160 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 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#[derive(Debug, Clone, PartialEq, Eq)]
209pub enum ParseURError {
210 InvalidScheme,
212 TypeUnspecified,
214 InvalidCharacters,
216 InvalidIndices,
218 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#[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}