1use std::fmt;
40use std::str::FromStr;
41use std::string::FromUtf8Error;
42
43use thiserror::Error;
44
45use crate::nuts::nut00::{Token, TokenV4};
46
47pub const TOKEN_UR_TYPE: &str = "bytes";
52
53pub const DEFAULT_MAX_FRAGMENT_LENGTH: usize = 200;
59
60pub const MAX_UR_PART_LENGTH: usize = 4_296;
66
67pub const MAX_UR_FRAGMENT_COUNT: usize = 4_096;
72
73pub const MAX_UR_MESSAGE_LENGTH: usize = 1024 * 1024;
78
79#[derive(Debug, Error)]
81pub enum Error {
82 #[error("UR error: {0}")]
84 Ur(ur::ur::Error),
85 #[error(transparent)]
87 CiboriumSer(#[from] ciborium::ser::Error<std::io::Error>),
88 #[error(transparent)]
90 CiboriumDe(#[from] ciborium::de::Error<std::io::Error>),
91 #[error(transparent)]
93 Utf8(#[from] FromUtf8Error),
94 #[error(transparent)]
96 Token(#[from] crate::nuts::nut00::Error),
97 #[error("unexpected UR type: expected `{TOKEN_UR_TYPE}`, got `{0}`")]
99 UnexpectedUrType(String),
100 #[error("UR part too large: {actual} bytes, maximum is {max}")]
102 PartTooLarge {
103 actual: usize,
105 max: usize,
107 },
108 #[error("too many UR fragments: {actual}, maximum is {max}")]
110 TooManyFragments {
111 actual: usize,
113 max: usize,
115 },
116 #[error("UR message too large: {actual} bytes, maximum is {max}")]
118 MessageTooLarge {
119 actual: usize,
121 max: usize,
123 },
124}
125
126impl From<ur::ur::Error> for Error {
127 fn from(err: ur::ur::Error) -> Self {
128 Self::Ur(err)
129 }
130}
131
132pub struct TokenUrEncoder {
149 encoder: ur::Encoder<'static>,
150 cbor: Vec<u8>,
152}
153
154impl TokenUrEncoder {
155 pub fn new(token: &Token, max_fragment_length: usize) -> Result<Self, Error> {
165 let cbor = token_to_cbor(token)?;
166 let encoder = ur::Encoder::bytes(&cbor, max_fragment_length)?;
167 Ok(Self { encoder, cbor })
168 }
169
170 pub fn next_part(&mut self) -> Result<String, Error> {
176 if self.fragment_count() == 1 {
177 let _part = self.encoder.next_part()?;
181 return Ok(ur::encode(&self.cbor, &ur::Type::Bytes));
182 }
183
184 Ok(self.encoder.next_part()?)
185 }
186
187 pub fn current_index(&self) -> usize {
189 self.encoder.current_index()
190 }
191
192 pub fn fragment_count(&self) -> usize {
194 self.encoder.fragment_count()
195 }
196
197 pub fn is_single_fragment(&self) -> bool {
199 self.fragment_count() == 1
200 }
201}
202
203impl fmt::Debug for TokenUrEncoder {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 f.debug_struct("TokenUrEncoder")
206 .field("current_index", &self.current_index())
207 .field("fragment_count", &self.fragment_count())
208 .finish()
209 }
210}
211
212impl Token {
213 pub fn ur_encoder(&self, max_fragment_length: usize) -> Result<TokenUrEncoder, Error> {
225 TokenUrEncoder::new(self, max_fragment_length)
226 }
227}
228
229#[derive(Default)]
235pub struct TokenUrDecoder {
236 decoder: ur::Decoder,
237 single_part: Option<Vec<u8>>,
239}
240
241impl TokenUrDecoder {
242 pub fn receive(&mut self, part: &str) -> Result<(), Error> {
253 if part.len() > MAX_UR_PART_LENGTH {
254 return Err(Error::PartTooLarge {
255 actual: part.len(),
256 max: MAX_UR_PART_LENGTH,
257 });
258 }
259
260 let ur_type = parse_ur_type(part)?;
261 if !ur_type.eq_ignore_ascii_case(TOKEN_UR_TYPE) {
262 return Err(Error::UnexpectedUrType(ur_type.to_string()));
263 }
264
265 let (kind, payload) = ur::decode(part)?;
266 match kind {
267 ur::ur::Kind::MultiPart => {
268 validate_multi_part_metadata(&payload)?;
269 self.decoder.receive(part)?;
270 }
271 ur::ur::Kind::SinglePart => {
272 if payload.len() > MAX_UR_MESSAGE_LENGTH {
273 return Err(Error::MessageTooLarge {
274 actual: payload.len(),
275 max: MAX_UR_MESSAGE_LENGTH,
276 });
277 }
278 self.single_part = Some(payload);
279 }
280 }
281
282 Ok(())
283 }
284
285 pub fn complete(&self) -> bool {
287 self.single_part.is_some() || self.decoder.complete()
288 }
289
290 pub fn token(&self) -> Result<Option<Token>, Error> {
298 let message = match &self.single_part {
299 Some(payload) => Some(payload.clone()),
300 None => self.decoder.message()?,
301 };
302
303 message.map(|m| token_from_cbor(&m)).transpose()
304 }
305
306 pub fn fragment_count(&self) -> usize {
310 self.decoder.fragment_count()
311 }
312
313 pub fn resolved_fragment_count(&self) -> Option<usize> {
319 match &self.single_part {
320 Some(_) => Some(1),
321 None => self.decoder.resolved_fragment_count(),
322 }
323 }
324}
325
326impl fmt::Debug for TokenUrDecoder {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("TokenUrDecoder")
329 .field("complete", &self.complete())
330 .field("fragment_count", &self.fragment_count())
331 .field("resolved_fragment_count", &self.resolved_fragment_count())
332 .finish()
333 }
334}
335
336fn token_to_cbor(token: &Token) -> Result<Vec<u8>, Error> {
339 let serialized = match token {
340 Token::TokenV3(token) => TokenV4::try_from(token.clone())?.to_string(),
341 Token::TokenV4(token) => token.to_string(),
342 };
343 let mut cbor = Vec::new();
344 ciborium::ser::into_writer(&serde_bytes::Bytes::new(serialized.as_bytes()), &mut cbor)?;
345 Ok(cbor)
346}
347
348fn token_from_cbor(cbor: &[u8]) -> Result<Token, Error> {
350 let bytes: serde_bytes::ByteBuf = ciborium::de::from_reader(cbor)?;
351 let token_str = String::from_utf8(bytes.into_vec())?;
352 Ok(Token::from_str(&token_str)?)
353}
354
355fn parse_ur_type(part: &str) -> Result<&str, Error> {
357 let (scheme, without_scheme) = part.split_once(':').ok_or(ur::ur::Error::InvalidScheme)?;
358 if !scheme.eq_ignore_ascii_case("ur") {
359 return Err(ur::ur::Error::InvalidScheme.into());
360 }
361
362 let (ur_type, _) = without_scheme
363 .split_once('/')
364 .ok_or(ur::ur::Error::TypeUnspecified)?;
365 Ok(ur_type)
366}
367
368fn validate_multi_part_metadata(payload: &[u8]) -> Result<(), Error> {
370 type FountainPart = (u32, u32, u32, u32, serde_bytes::ByteBuf);
371
372 let (_, fragment_count, message_length, _, _): FountainPart =
373 ciborium::de::from_reader(payload)?;
374 let fragment_count = fragment_count as usize;
375 let message_length = message_length as usize;
376
377 if fragment_count > MAX_UR_FRAGMENT_COUNT {
378 return Err(Error::TooManyFragments {
379 actual: fragment_count,
380 max: MAX_UR_FRAGMENT_COUNT,
381 });
382 }
383 if message_length > MAX_UR_MESSAGE_LENGTH {
384 return Err(Error::MessageTooLarge {
385 actual: message_length,
386 max: MAX_UR_MESSAGE_LENGTH,
387 });
388 }
389
390 Ok(())
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 const TOKEN_STR: &str = "cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ=";
399 const TOKEN_V3_STR: &str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
400 const TOKEN_V4_FROM_V3_STR: &str = "cashuBpGFtd2h0dHBzOi8vODMzMy5zcGFjZTozMzM4YXVjc2F0YWRqVGhhbmsgeW91LmF0gaJhaUgAmh8pMlPkHmFwgqRhYQJhc3hANDA3OTE1YmMyMTJiZTYxYTc3ZTNlNmQyYWViNGM3Mjc5ODBiZGE1MWNkMDZhNmFmYzI5ZTI4NjE3NjhhNzgzN2FjWCECvJCXmX2Br7LMc0a15DRak0a9KlBut5WFmKcvDPhRY-phZPakYWEIYXN4QGZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmVhY1ghAp6OUFC4kKfWwJaNsWvB1dX6BA6h3ihPbsadYSmfZxBZYWT2";
401
402 fn test_token() -> Token {
403 Token::from_str(TOKEN_STR).expect("valid test token")
404 }
405
406 fn multi_part(sequence: u32, fragment_count: u32, message_length: u32, data: &[u8]) -> String {
407 let metadata = (
408 sequence,
409 fragment_count,
410 message_length,
411 0_u32,
412 serde_bytes::Bytes::new(data),
413 );
414 let mut cbor = Vec::new();
415 ciborium::ser::into_writer(&metadata, &mut cbor).expect("valid fountain part CBOR");
416 let encoded = ur::encode(&cbor, &ur::Type::Bytes);
417 let payload = encoded
418 .strip_prefix("ur:bytes/")
419 .expect("encoded bytes UR has expected prefix");
420 format!("ur:bytes/{sequence}-{fragment_count}/{payload}")
421 }
422
423 #[test]
424 fn test_single_fragment_roundtrip() {
425 let token = test_token();
426 let mut encoder = TokenUrEncoder::new(&token, 1000).expect("valid encoder");
427 assert!(encoder.is_single_fragment());
428
429 let part = encoder.next_part().expect("valid part");
430 assert!(part.starts_with("ur:bytes/"));
431 assert_eq!(part.matches('/').count(), 1);
433
434 let mut decoder = TokenUrDecoder::default();
435 decoder.receive(&part).expect("valid receive");
436 assert!(decoder.complete());
437 assert_eq!(decoder.token().expect("valid token"), Some(token));
438 }
439
440 #[test]
441 fn test_v3_token_is_encoded_as_v4() {
442 let token = Token::from_str(TOKEN_V3_STR).expect("valid V3 token");
443 let expected = Token::from_str(TOKEN_V4_FROM_V3_STR).expect("valid V4 token");
444 let mut encoder = token.ur_encoder(1000).expect("valid encoder");
445 let part = encoder.next_part().expect("valid part");
446
447 let mut decoder = TokenUrDecoder::default();
448 decoder.receive(&part).expect("valid receive");
449
450 assert_eq!(decoder.token().expect("valid token"), Some(expected));
451 }
452
453 #[test]
454 fn test_token_ur_encoder_method() {
455 let token = Token::from_str(TOKEN_STR).expect("valid token");
456 let mut encoder = token.ur_encoder(100).expect("encoder");
457
458 let mut decoder = TokenUrDecoder::default();
459 while !decoder.complete() {
460 let part = encoder.next_part().expect("part");
461 decoder.receive(&part).expect("receive");
462 }
463
464 assert_eq!(decoder.token().expect("token"), Some(token));
465 }
466
467 #[test]
468 fn test_multi_fragment_roundtrip() {
469 let token = test_token();
470 let mut encoder = TokenUrEncoder::new(&token, 20).expect("valid encoder");
471 assert!(encoder.fragment_count() > 1);
472
473 let mut decoder = TokenUrDecoder::default();
474 let mut parts = 0;
475 while !decoder.complete() {
476 let part = encoder.next_part().expect("valid part");
477 assert!(part.starts_with("ur:bytes/"));
478 assert_eq!(part.matches('/').count(), 2);
481 decoder.receive(&part).expect("valid receive");
482 parts += 1;
483 assert!(parts <= 100, "decoder should complete");
484 }
485
486 assert!(parts >= encoder.fragment_count());
487 assert_eq!(decoder.token().expect("valid token"), Some(token));
488 }
489
490 #[test]
491 fn test_multi_fragment_with_dropped_frames() {
492 let token = test_token();
493 let mut encoder = TokenUrEncoder::new(&token, 20).expect("valid encoder");
494 let mut decoder = TokenUrDecoder::default();
495
496 for _ in 0..200 {
498 if decoder.complete() {
499 break;
500 }
501 let part = encoder.next_part().expect("valid part");
502 if encoder.current_index() % 2 == 0 {
503 decoder.receive(&part).expect("valid receive");
504 }
505 }
506
507 assert!(decoder.complete(), "decoder must tolerate dropped frames");
508 assert_eq!(decoder.token().expect("valid token"), Some(token));
509 }
510
511 #[test]
512 fn test_rejects_wrong_ur_type() {
513 let part = ur::encode(b"not a token", &ur::Type::Custom("crypto-psbt"));
514 let mut decoder = TokenUrDecoder::default();
515 let err = decoder.receive(&part).expect_err("must reject wrong type");
516 assert!(matches!(err, Error::UnexpectedUrType(t) if t == "crypto-psbt"));
517 }
518
519 #[test]
520 fn test_rejects_non_ur_frame() {
521 let mut decoder = TokenUrDecoder::default();
522 assert!(decoder.receive(TOKEN_STR).is_err());
523 }
524
525 #[test]
526 fn test_accepts_uppercase_ur() {
527 let token = test_token();
528 let mut encoder = TokenUrEncoder::new(&token, 1000).expect("valid encoder");
529 let part = encoder
530 .next_part()
531 .expect("valid part")
532 .to_ascii_uppercase();
533
534 let mut decoder = TokenUrDecoder::default();
535 decoder.receive(&part).expect("valid uppercase receive");
536 assert_eq!(decoder.token().expect("valid token"), Some(token));
537 }
538
539 #[test]
540 fn test_rejects_oversized_part_before_decoding() {
541 let part = "x".repeat(MAX_UR_PART_LENGTH + 1);
542 let mut decoder = TokenUrDecoder::default();
543 let err = decoder.receive(&part).expect_err("oversized part");
544
545 assert!(matches!(
546 err,
547 Error::PartTooLarge {
548 actual,
549 max: MAX_UR_PART_LENGTH,
550 } if actual == MAX_UR_PART_LENGTH + 1
551 ));
552 }
553
554 #[test]
555 fn test_rejects_excessive_fragment_count() {
556 let fragment_count =
557 u32::try_from(MAX_UR_FRAGMENT_COUNT + 1).expect("fragment limit fits in u32");
558 let part = multi_part(fragment_count + 1, fragment_count, 1, &[0]);
559 let mut decoder = TokenUrDecoder::default();
560 let err = decoder.receive(&part).expect_err("too many fragments");
561
562 assert!(matches!(
563 err,
564 Error::TooManyFragments {
565 actual,
566 max: MAX_UR_FRAGMENT_COUNT,
567 } if actual == MAX_UR_FRAGMENT_COUNT + 1
568 ));
569 }
570
571 #[test]
572 fn test_rejects_excessive_message_length() {
573 let message_length =
574 u32::try_from(MAX_UR_MESSAGE_LENGTH + 1).expect("message limit fits in u32");
575 let part = multi_part(1, 1, message_length, &[0]);
576 let mut decoder = TokenUrDecoder::default();
577 let err = decoder.receive(&part).expect_err("message too large");
578
579 assert!(matches!(
580 err,
581 Error::MessageTooLarge {
582 actual,
583 max: MAX_UR_MESSAGE_LENGTH,
584 } if actual == MAX_UR_MESSAGE_LENGTH + 1
585 ));
586 }
587
588 #[test]
589 fn test_payload_is_cbor_byte_string_of_token() {
590 let token = test_token();
593 let cbor = token_to_cbor(&token).expect("valid cbor");
594 let bytes: serde_bytes::ByteBuf =
595 ciborium::de::from_reader(&cbor[..]).expect("valid cbor byte string");
596 assert_eq!(bytes.into_vec(), token.to_string().as_bytes());
597 }
598}