1extern crate alloc;
29use alloc::{string::String, vec::Vec};
30
31#[derive(Debug)]
33pub enum Error {
34 Bytewords(crate::bytewords::Error),
36 Fountain(crate::fountain::Error),
38 InvalidScheme,
40 TypeUnspecified,
42 InvalidCharacters,
44 InvalidIndices,
46 NotMultiPart,
48}
49
50impl core::fmt::Display for Error {
51 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52 match self {
53 Self::Bytewords(e) => write!(f, "{e}"),
54 Self::Fountain(e) => write!(f, "{e}"),
55 Self::InvalidScheme => write!(f, "Invalid scheme"),
56 Self::TypeUnspecified => write!(f, "No type specified"),
57 Self::InvalidCharacters => write!(f, "Type contains invalid characters"),
58 Self::InvalidIndices => write!(f, "Invalid indices"),
59 Self::NotMultiPart => write!(f, "Can't decode single-part UR as multi-part"),
60 }
61 }
62}
63
64impl From<crate::bytewords::Error> for Error {
65 fn from(e: crate::bytewords::Error) -> Self {
66 Self::Bytewords(e)
67 }
68}
69
70impl From<crate::fountain::Error> for Error {
71 fn from(e: crate::fountain::Error) -> Self {
72 Self::Fountain(e)
73 }
74}
75
76#[must_use]
87pub fn encode(data: &[u8], ur_type: &Type) -> String {
88 let body = crate::bytewords::encode(data, crate::bytewords::Style::Minimal);
89 alloc::format!("ur:{}/{body}", ur_type.encoding())
90}
91
92pub enum Type<'a> {
94 Bytes,
96 Custom(&'a str),
98}
99
100impl<'a> Type<'a> {
101 const fn encoding(&self) -> &'a str {
102 match self {
103 Self::Bytes => "bytes",
104 Self::Custom(s) => s,
105 }
106 }
107}
108
109pub struct Encoder<'a> {
115 fountain: crate::fountain::Encoder,
116 ur_type: Type<'a>,
117}
118
119impl<'a> Encoder<'a> {
120 pub fn bytes(message: &[u8], max_fragment_length: usize) -> Result<Self, Error> {
135 Ok(Self {
136 fountain: crate::fountain::Encoder::new(message, max_fragment_length)?,
137 ur_type: Type::Bytes,
138 })
139 }
140
141 pub fn new(message: &[u8], max_fragment_length: usize, s: &'a str) -> Result<Self, Error> {
152 Ok(Self {
153 fountain: crate::fountain::Encoder::new(message, max_fragment_length)?,
154 ur_type: Type::Custom(s),
155 })
156 }
157
158 pub fn next_part(&mut self) -> Result<String, Error> {
168 let part = self.fountain.next_part();
169 let body = crate::bytewords::encode(&part.cbor()?, crate::bytewords::Style::Minimal);
170 Ok(alloc::format!(
171 "ur:{}/{}/{body}",
172 self.ur_type.encoding(),
173 part.sequence_id()
174 ))
175 }
176
177 #[must_use]
188 pub const fn current_index(&self) -> usize {
189 self.fountain.current_sequence()
190 }
191
192 #[must_use]
201 pub fn fragment_count(&self) -> usize {
202 self.fountain.fragment_count()
203 }
204}
205
206#[derive(Debug, PartialEq, Eq)]
209pub enum Kind {
210 SinglePart,
212 MultiPart,
214}
215
216pub fn decode(value: &str) -> Result<(Kind, Vec<u8>), Error> {
239 let strip_scheme = value.strip_prefix("ur:").ok_or(Error::InvalidScheme)?;
240 let (r#type, strip_type) = strip_scheme.split_once('/').ok_or(Error::TypeUnspecified)?;
241
242 if !r#type
243 .trim_start_matches(|c: char| c.is_ascii_alphanumeric() || c == '-')
244 .is_empty()
245 {
246 return Err(Error::InvalidCharacters);
247 }
248
249 match strip_type.rsplit_once('/') {
250 None => Ok((
251 Kind::SinglePart,
252 crate::bytewords::decode(strip_type, crate::bytewords::Style::Minimal)?,
253 )),
254 Some((indices, payload)) => {
255 let (idx, idx_total) = indices.split_once('-').ok_or(Error::InvalidIndices)?;
256 if idx.parse::<u16>().is_err() || idx_total.parse::<u16>().is_err() {
257 return Err(Error::InvalidIndices);
258 }
259
260 Ok((
261 Kind::MultiPart,
262 crate::bytewords::decode(payload, crate::bytewords::Style::Minimal)?,
263 ))
264 }
265 }
266}
267
268#[derive(Default)]
274pub struct Decoder {
275 fountain: crate::fountain::Decoder,
276}
277
278impl Decoder {
279 pub fn receive(&mut self, value: &str) -> Result<(), Error> {
296 let (kind, decoded) = decode(value)?;
297 if kind != Kind::MultiPart {
298 return Err(Error::NotMultiPart);
299 }
300
301 self.fountain
302 .receive(crate::fountain::Part::from_cbor(decoded.as_slice())?)?;
303 Ok(())
304 }
305
306 #[must_use]
312 pub fn complete(&self) -> bool {
313 self.fountain.complete()
314 }
315
316 pub fn message(&self) -> Result<Option<Vec<u8>>, Error> {
328 self.fountain.message().map_err(Error::from)
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use minicbor::{bytes::ByteVec, data::Tag};
336
337 fn make_message_ur(length: usize, seed: &str) -> Vec<u8> {
338 let message = crate::xoshiro::test_utils::make_message(seed, length);
339 minicbor::to_vec(ByteVec::from(message)).unwrap()
340 }
341
342 #[test]
343 fn test_single_part_ur() {
344 let ur = make_message_ur(50, "Wolf");
345 let encoded = encode(&ur, &Type::Bytes);
346 let expected = "ur:bytes/hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch";
347 assert_eq!(encoded, expected);
348 let decoded = decode(&encoded).unwrap();
349 assert_eq!((Kind::SinglePart, ur), decoded);
350 }
351
352 #[test]
353 fn test_ur_encoder() {
354 let ur = make_message_ur(256, "Wolf");
355 let mut encoder = Encoder::bytes(&ur, 30).unwrap();
356 let expected = vec![
357 "ur:bytes/1-9/lpadascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtdkgslpgh",
358 "ur:bytes/2-9/lpaoascfadaxcywenbpljkhdcagwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsgmghhkhstlrdcxaefz",
359 "ur:bytes/3-9/lpaxascfadaxcywenbpljkhdcahelbknlkuejnbadmssfhfrdpsbiegecpasvssovlgeykssjykklronvsjksopdzmol",
360 "ur:bytes/4-9/lpaaascfadaxcywenbpljkhdcasotkhemthydawydtaxneurlkosgwcekonertkbrlwmplssjtammdplolsbrdzcrtas",
361 "ur:bytes/5-9/lpahascfadaxcywenbpljkhdcatbbdfmssrkzmcwnezelennjpfzbgmuktrhtejscktelgfpdlrkfyfwdajldejokbwf",
362 "ur:bytes/6-9/lpamascfadaxcywenbpljkhdcackjlhkhybssklbwefectpfnbbectrljectpavyrolkzczcpkmwidmwoxkilghdsowp",
363 "ur:bytes/7-9/lpatascfadaxcywenbpljkhdcavszmwnjkwtclrtvaynhpahrtoxmwvwatmedibkaegdosftvandiodagdhthtrlnnhy",
364 "ur:bytes/8-9/lpayascfadaxcywenbpljkhdcadmsponkkbbhgsoltjntegepmttmoonftnbuoiyrehfrtsabzsttorodklubbuyaetk",
365 "ur:bytes/9-9/lpasascfadaxcywenbpljkhdcajskecpmdckihdyhphfotjojtfmlnwmadspaxrkytbztpbauotbgtgtaeaevtgavtny",
366 "ur:bytes/10-9/lpbkascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtwdkiplzs",
367 "ur:bytes/11-9/lpbdascfadaxcywenbpljkhdcahelbknlkuejnbadmssfhfrdpsbiegecpasvssovlgeykssjykklronvsjkvetiiapk",
368 "ur:bytes/12-9/lpbnascfadaxcywenbpljkhdcarllaluzmdmgstospeyiefmwejlwtpedamktksrvlcygmzemovovllarodtmtbnptrs",
369 "ur:bytes/13-9/lpbtascfadaxcywenbpljkhdcamtkgtpknghchchyketwsvwgwfdhpgmgtylctotzopdrpayoschcmhplffziachrfgd",
370 "ur:bytes/14-9/lpbaascfadaxcywenbpljkhdcapazewnvonnvdnsbyleynwtnsjkjndeoldydkbkdslgjkbbkortbelomueekgvstegt",
371 "ur:bytes/15-9/lpbsascfadaxcywenbpljkhdcaynmhpddpzmversbdqdfyrehnqzlugmjzmnmtwmrouohtstgsbsahpawkditkckynwt",
372 "ur:bytes/16-9/lpbeascfadaxcywenbpljkhdcawygekobamwtlihsnpalnsghenskkiynthdzotsimtojetprsttmukirlrsbtamjtpd",
373 "ur:bytes/17-9/lpbyascfadaxcywenbpljkhdcamklgftaxykpewyrtqzhydntpnytyisincxmhtbceaykolduortotiaiaiafhiaoyce",
374 "ur:bytes/18-9/lpbgascfadaxcywenbpljkhdcahkadaemejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtntwkbkwy",
375 "ur:bytes/19-9/lpbwascfadaxcywenbpljkhdcadekicpaajootjzpsdrbalpeywllbdsnbinaerkurspbncxgslgftvtsrjtksplcpeo",
376 "ur:bytes/20-9/lpbbascfadaxcywenbpljkhdcayapmrleeleaxpasfrtrdkncffwjyjzgyetdmlewtkpktgllepfrltataztksmhkbot",
377 ];
378 assert_eq!(encoder.fragment_count(), 9);
379 for (index, e) in expected.into_iter().enumerate() {
380 assert_eq!(encoder.current_index(), index);
381 assert_eq!(encoder.next_part().unwrap(), e);
382 }
383 }
384
385 #[test]
386 fn test_ur_encoder_decoder_bc_crypto_request() {
387 fn crypto_seed() -> Result<Vec<u8>, minicbor::encode::Error<std::convert::Infallible>> {
390 let mut e = minicbor::Encoder::new(Vec::new());
391
392 let uuid = hex::decode("020C223A86F7464693FC650EF3CAC047").unwrap();
393 let seed_digest =
394 hex::decode("E824467CAFFEAF3BBC3E0CA095E660A9BAD80DDB6A919433A37161908B9A3986")
395 .unwrap();
396
397 #[rustfmt::skip]
398 e.map(2)?
399 .u8(1)?.tag(Tag::Unassigned(37))?.bytes(&uuid)?
401 .u8(2)?.tag(Tag::Unassigned(500))?.map(1)?
403 .u8(1)?.tag(Tag::Unassigned(600))?.bytes(&seed_digest)?;
405
406 Ok(e.into_writer())
407 }
408
409 let data = crypto_seed().unwrap();
410
411 let e = encode(&data, &Type::Custom("crypto-request"));
412 let expected = "ur:crypto-request/oeadtpdagdaobncpftlnylfgfgmuztihbawfsgrtflaotaadwkoyadtaaohdhdcxvsdkfgkepezepefrrffmbnnbmdvahnptrdtpbtuyimmemweootjshsmhlunyeslnameyhsdi";
413 assert_eq!(expected, e);
414
415 let decoded = decode(e.as_str()).unwrap();
417 assert_eq!((Kind::SinglePart, data), decoded);
418 }
419
420 #[test]
421 fn test_multipart_ur() {
422 let ur = make_message_ur(32767, "Wolf");
423 let mut encoder = Encoder::bytes(&ur, 1000).unwrap();
424 let mut decoder = Decoder::default();
425 while !decoder.complete() {
426 assert_eq!(decoder.message().unwrap(), None);
427 decoder.receive(&encoder.next_part().unwrap()).unwrap();
428 }
429 assert_eq!(decoder.message().unwrap(), Some(ur));
430 }
431
432 #[test]
433 fn test_decoder() {
434 assert!(matches!(
435 decode("uhr:bytes/aeadaolazmjendeoti"),
436 Err(Error::InvalidScheme)
437 ));
438 assert!(matches!(
439 decode("ur:aeadaolazmjendeoti"),
440 Err(Error::TypeUnspecified)
441 ));
442 assert!(matches!(
443 decode("ur:bytes#4/aeadaolazmjendeoti"),
444 Err(Error::InvalidCharacters)
445 ));
446 assert!(matches!(
447 decode("ur:bytes/1-1a/aeadaolazmjendeoti"),
448 Err(Error::InvalidIndices)
449 ));
450 assert!(matches!(
451 decode("ur:bytes/1-1/toomuch/aeadaolazmjendeoti"),
452 Err(Error::InvalidIndices)
453 ));
454 decode("ur:bytes/aeadaolazmjendeoti").unwrap();
455 decode("ur:whatever-12/aeadaolazmjendeoti").unwrap();
456 }
457
458 #[test]
459 fn test_custom_encoder() {
460 let data = String::from("Ten chars!");
461 let max_length = 5;
462 let mut encoder = Encoder::new(data.as_bytes(), max_length, "my-scheme").unwrap();
463 assert_eq!(
464 encoder.next_part().unwrap(),
465 "ur:my-scheme/1-2/lpadaobkcywkwmhfwnfeghihjtcxiansvomopr"
466 );
467 }
468}