pub mod decoder;
pub mod encoder;
#[cfg(feature = "alloc")]
pub use self::decoder::Decoder;
pub use self::decoder::{BaseDecoder, HeaplessDecoder};
#[cfg(feature = "alloc")]
pub use self::encoder::Encoder;
pub use self::encoder::{BaseEncoder, HeaplessEncoder};
use crate::{
bytewords::{Bytewords, Style},
fountain::part::Part,
};
use core::{fmt, num::ParseIntError};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum UR<'a> {
SinglePart {
ur_type: &'a str,
message: &'a str,
},
SinglePartDeserialized {
ur_type: &'a str,
message: &'a [u8],
},
MultiPart {
ur_type: &'a str,
fragment: &'a str,
sequence: u32,
sequence_count: u32,
},
MultiPartDeserialized {
ur_type: &'a str,
fragment: Part<'a>,
},
}
impl<'a> UR<'a> {
pub fn new(ur_type: &'a str, message: &'a [u8]) -> Self {
UR::SinglePartDeserialized { ur_type, message }
}
pub fn parse(s: &'a str) -> Result<Self, ParseURError> {
let (ur_type, rest) = s
.strip_prefix("ur:")
.ok_or(ParseURError::InvalidScheme)?
.split_once('/')
.ok_or(ParseURError::TypeUnspecified)?;
if !ur_type
.trim_start_matches(|c: char| c.is_ascii_alphanumeric() || c == '-')
.is_empty()
{
return Err(ParseURError::InvalidCharacters);
}
match rest.rsplit_once('/') {
None => Ok(UR::SinglePart {
ur_type,
message: rest,
}),
Some((indices, fragment)) => {
let (sequence, sequence_count) = indices
.split_once('-')
.ok_or(ParseURError::InvalidIndices)?;
Ok(UR::MultiPart {
ur_type,
fragment,
sequence: sequence.parse()?,
sequence_count: sequence_count.parse()?,
})
}
}
}
#[inline]
pub fn is_single_part(&self) -> bool {
matches!(
self,
UR::SinglePart { .. } | UR::SinglePartDeserialized { .. }
)
}
#[inline]
pub fn is_multi_part(&self) -> bool {
matches!(
self,
UR::MultiPart { .. } | UR::MultiPartDeserialized { .. }
)
}
#[inline]
pub fn is_deserialized(&self) -> bool {
matches!(
self,
UR::SinglePartDeserialized { .. } | UR::MultiPartDeserialized { .. }
)
}
pub fn as_type(&self) -> &str {
match self {
UR::SinglePart { ur_type, .. } => ur_type,
UR::SinglePartDeserialized { ur_type, .. } => ur_type,
UR::MultiPart { ur_type, .. } => ur_type,
UR::MultiPartDeserialized { ur_type, .. } => ur_type,
}
}
pub fn as_bytewords(&self) -> Option<&str> {
match self {
UR::SinglePart { message, .. } => Some(message),
UR::MultiPart { fragment, .. } => Some(fragment),
_ => None,
}
}
pub fn as_part(&self) -> Option<&Part> {
match self {
UR::MultiPartDeserialized { fragment, .. } => Some(fragment),
_ => None,
}
}
pub fn sequence(&self) -> Option<u32> {
match self {
UR::MultiPart { sequence, .. } => Some(*sequence),
UR::MultiPartDeserialized { fragment, .. } => Some(fragment.sequence),
_ => None,
}
}
pub fn sequence_count(&self) -> Option<u32> {
match self {
UR::MultiPart { sequence_count, .. } => Some(*sequence_count),
UR::MultiPartDeserialized { fragment, .. } => Some(fragment.sequence_count),
_ => None,
}
}
}
impl<'a> fmt::Display for UR<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UR::SinglePart { ur_type, message } => {
write!(f, "ur:{ur_type}/{message}")
}
UR::SinglePartDeserialized { ur_type, message } => {
let message = Bytewords(message, Style::Minimal);
write!(f, "ur:{ur_type}/{message}")
}
UR::MultiPart {
ur_type,
fragment,
sequence,
sequence_count,
} => {
write!(f, "ur:{ur_type}/{sequence}-{sequence_count}/{fragment}")
}
UR::MultiPartDeserialized { ur_type, fragment } => {
let (sequence, sequence_count) = (fragment.sequence, fragment.sequence_count);
write!(f, "ur:{ur_type}/{sequence}-{sequence_count}/{fragment}",)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseURError {
InvalidScheme,
TypeUnspecified,
InvalidCharacters,
InvalidIndices,
ParseInt(ParseIntError),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseURError {}
impl fmt::Display for ParseURError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseURError::InvalidScheme => write!(f, "Invalid Uniform Resource scheme"),
ParseURError::TypeUnspecified => {
write!(f, "No type was specified for the Uniform Resource")
}
ParseURError::InvalidCharacters => {
write!(f, "Uniform Resource type contains invalid characters")
}
ParseURError::InvalidIndices => write!(f, "Uniform Resource indices are invalid"),
ParseURError::ParseInt(e) => {
write!(f, "Could not parse Uniform Resource indices: {e}")
}
}
}
}
impl From<ParseIntError> for ParseURError {
fn from(e: ParseIntError) -> Self {
Self::ParseInt(e)
}
}
#[cfg(feature = "alloc")]
pub fn to_string(ur_type: &str, message: &[u8]) -> alloc::string::String {
let ur = UR::SinglePartDeserialized { ur_type, message };
ur.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use core::num::IntErrorKind;
#[cfg(feature = "alloc")]
pub fn make_message_ur(length: usize, seed: &str) -> Vec<u8> {
let message = crate::xoshiro::test_utils::make_message(seed, length);
minicbor::to_vec(minicbor::bytes::ByteVec::from(message)).unwrap()
}
#[test]
#[cfg(feature = "alloc")]
fn test_single_part_ur() {
const EXPECTED: &str = "ur:bytes/hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch";
let encoded = UR::new("bytes", &make_message_ur(50, "Wolf")).to_string();
assert_eq!(&encoded, EXPECTED);
let parsed = UR::parse(&encoded).unwrap();
assert!(matches!(parsed, UR::SinglePart {
ur_type: "bytes",
message: "hdeymejtswhhylkepmykhhtsytsnoyoyaxaedsuttydmmhhpktpmsrjtgwdpfnsboxgwlbaawzuefywkdplrsrjynbvygabwjldapfcsdwkbrkch",
}));
}
#[test]
#[cfg(feature = "alloc")]
fn test_ur_roundtrip() {
let ur = make_message_ur(32767, "Wolf");
let mut encoder = Encoder::new();
encoder.start("bytes", &ur, 1000);
let mut decoder = Decoder::default();
while !decoder.is_complete() {
assert_eq!(decoder.message().unwrap(), None);
decoder.receive(encoder.next_part()).unwrap();
}
assert_eq!(decoder.message().unwrap(), Some(ur.as_slice()));
}
#[test]
fn test_parser() {
UR::parse("ur:bytes/aeadaolazmjendeoti").unwrap();
UR::parse("ur:whatever-12/aeadaolazmjendeoti").unwrap();
}
#[test]
fn test_parser_errors() {
const TEST_VECTORS: &[(&str, ParseURError)] = &[
("uhr:bytes/aeadaolazmjendeoti", ParseURError::InvalidScheme),
("ur:aeadaolazmjendeoti", ParseURError::TypeUnspecified),
(
"ur:bytes#4/aeadaolazmjendeoti",
ParseURError::InvalidCharacters,
),
(
"ur:bytes/1 1/aeadaolazmjendeoti",
ParseURError::InvalidIndices,
),
];
for (input, error) in TEST_VECTORS {
assert_eq!(UR::parse(&input).unwrap_err(), error.clone());
}
match UR::parse("ur:bytes/1-1/toomuch/aeadaolazmjendeoti") {
Err(ParseURError::ParseInt(e)) => {
assert_eq!(*e.kind(), IntErrorKind::InvalidDigit)
}
_ => panic!(),
}
match UR::parse("ur:bytes/1-1a/aeadaolazmjendeoti") {
Err(ParseURError::ParseInt(e)) => {
assert_eq!(*e.kind(), IntErrorKind::InvalidDigit)
}
_ => panic!(),
}
}
}