1#![doc = include_str!("../README.md")]
2#![forbid(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5use core::{
6 borrow::Borrow,
7 fmt::{Debug, Display},
8 iter::{once, Once},
9 ops::Deref,
10 str::FromStr,
11};
12
13mod proquint;
14pub use proquint::Proquint;
15
16pub mod fractional;
17use fractional::*;
18
19pub const PROQUINT_CONSONANTS: [u8; 16] = [
21 b'b', b'd', b'f', b'g', b'h', b'j', b'k', b'l', b'm', b'n', b'p', b'r', b's', b't', b'v', b'z',
22];
23
24pub const PROQUINT_VOWELS: [u8; 4] = [b'a', b'i', b'o', b'u'];
26
27pub const PROQUINT_SYLLABLES: [[u8; 2]; 64] = [
31 *b"ba", *b"bi", *b"bo", *b"bu", *b"da", *b"di", *b"do", *b"du", *b"fa", *b"fi", *b"fo", *b"fu",
32 *b"ga", *b"gi", *b"go", *b"gu", *b"ha", *b"hi", *b"ho", *b"hu", *b"ja", *b"ji", *b"jo", *b"ju",
33 *b"ka", *b"ki", *b"ko", *b"ku", *b"la", *b"li", *b"lo", *b"lu", *b"ma", *b"mi", *b"mo", *b"mu",
34 *b"na", *b"ni", *b"no", *b"nu", *b"pa", *b"pi", *b"po", *b"pu", *b"ra", *b"ri", *b"ro", *b"ru",
35 *b"sa", *b"si", *b"so", *b"su", *b"ta", *b"ti", *b"to", *b"tu", *b"va", *b"vi", *b"vo", *b"vu",
36 *b"za", *b"zi", *b"zo", *b"zu",
37];
38
39pub const VOWEL_MASK: u16 = 0b11;
41pub const CONSONANT_MASK: u16 = 0b1111;
43pub const SYLLABLE_MASK: u16 = 0b111111;
45
46pub const FIRST_CONSONANT_SHIFT: u32 = 12;
48pub const FIRST_CONSONANT_MASK: u16 = CONSONANT_MASK << FIRST_CONSONANT_SHIFT;
50pub const FIRST_VOWEL_SHIFT: u32 = 10;
52pub const FIRST_VOWEL_MASK: u16 = VOWEL_MASK << FIRST_VOWEL_SHIFT;
54pub const SECOND_CONSONANT_SHIFT: u32 = 6;
56pub const SECOND_CONSONANT_MASK: u16 = CONSONANT_MASK << SECOND_CONSONANT_SHIFT;
58pub const SECOND_VOWEL_SHIFT: u32 = 4;
60pub const SECOND_VOWEL_MASK: u16 = VOWEL_MASK << SECOND_VOWEL_SHIFT;
62pub const FINAL_CONSONANT_SHIFT: u32 = 0;
64pub const FINAL_CONSONANT_MASK: u16 = CONSONANT_MASK << FINAL_CONSONANT_SHIFT;
66pub const FIRST_SYLLABLE_SHIFT: u32 = FIRST_VOWEL_SHIFT;
68pub const FIRST_SYLLABLE_MASK: u16 = SYLLABLE_MASK << FIRST_VOWEL_SHIFT;
70pub const SECOND_SYLLABLE_SHIFT: u32 = SECOND_VOWEL_SHIFT;
72pub const SECOND_SYLLABLE_MASK: u16 = SYLLABLE_MASK << SECOND_SYLLABLE_SHIFT;
74
75impl FromStr for Proquint {
76 type Err = ProquintParseError;
77
78 #[inline]
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 let bytes: [u8; 5] = s
81 .as_bytes()
82 .try_into()
83 .map_err(|_| ProquintParseError(None))?;
84 Self::from_bytes(bytes)
85 }
86}
87
88impl From<u16> for Proquint {
89 #[cfg_attr(not(tarpaulin), inline(always))]
90 fn from(value: u16) -> Self {
91 Self::from_u16(value)
92 }
93}
94
95impl<'a> TryFrom<&'a [u8]> for Proquint {
96 type Error = ProquintParseError;
97
98 #[cfg_attr(not(tarpaulin), inline(always))]
99 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
100 let bytes: [u8; 5] = value.try_into().map_err(|_| ProquintParseError(None))?;
101 Self::from_bytes(bytes)
102 }
103}
104
105impl TryFrom<[u8; 5]> for Proquint {
106 type Error = ProquintParseError;
107
108 #[cfg_attr(not(tarpaulin), inline(always))]
109 fn try_from(value: [u8; 5]) -> Result<Self, Self::Error> {
110 Self::from_bytes(value)
111 }
112}
113
114#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
116pub struct ProquintParseError(Option<[u8; 5]>);
117
118impl Display for ProquintParseError {
119 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120 if let Some(data) = self.0 {
121 write!(
122 f,
123 "{data:?} (utf-8 = {:?}) is not a valid proquint",
124 core::str::from_utf8(&data[..])
125 )
126 } else {
127 write!(f, "not enough characters to parse a proquint")
128 }
129 }
130}
131
132impl Proquint {
133 #[cfg_attr(not(tarpaulin), inline(always))]
135 pub const fn value(self) -> u16 {
136 Self::value_from_bytes_unchecked(*self.as_bytes())
137 }
138
139 #[cfg_attr(not(tarpaulin), inline(always))]
143 pub const fn value_from_bytes(bytes: [u8; 5]) -> Result<u16, ProquintParseError> {
144 let first_consonant = Self::consonant(bytes[0]);
145 let first_vowel = Self::vowel(bytes[1]);
146 let second_consonant = Self::consonant(bytes[2]);
147 let second_vowel = Self::vowel(bytes[3]);
148 let final_consonant = Self::consonant(bytes[4]);
149 if first_consonant != u16::MAX
150 && first_vowel != u16::MAX
151 && second_consonant != u16::MAX
152 && second_vowel != u16::MAX
153 && final_consonant != u16::MAX
154 {
155 Ok(first_consonant << FIRST_CONSONANT_SHIFT
156 | first_vowel << FIRST_VOWEL_SHIFT
157 | second_consonant << SECOND_CONSONANT_SHIFT
158 | second_vowel << SECOND_VOWEL_SHIFT
159 | final_consonant << FINAL_CONSONANT_SHIFT)
160 } else {
161 Err(ProquintParseError(Some(bytes)))
162 }
163 }
164
165 #[cfg_attr(not(tarpaulin), inline(always))]
169 pub const fn value_from_bytes_unchecked(bytes: [u8; 5]) -> u16 {
170 Self::consonant(bytes[0]) << FIRST_CONSONANT_SHIFT
171 | Self::vowel(bytes[1]) << FIRST_VOWEL_SHIFT
172 | Self::consonant(bytes[2]) << SECOND_CONSONANT_SHIFT
173 | Self::vowel(bytes[3]) << SECOND_VOWEL_SHIFT
174 | Self::consonant(bytes[4]) << FINAL_CONSONANT_SHIFT
175 }
176
177 #[cfg_attr(not(tarpaulin), inline(always))]
181 pub fn parse_partial(input: &str) -> Result<(&str, Proquint), ProquintParseError> {
182 if input.len() >= 5 {
183 Ok((
184 &input[5..],
185 Self::from_bytes(input.as_bytes()[..5].try_into().unwrap())?,
186 ))
187 } else {
188 Err(ProquintParseError(None))
189 }
190 }
191
192 #[cfg_attr(not(tarpaulin), inline(always))]
194 pub const fn parse_str(input: &str) -> Result<Proquint, ProquintParseError> {
195 Self::parse_bytes(input.as_bytes())
196 }
197
198 #[cfg_attr(not(tarpaulin), inline(always))]
202 pub fn parse_partial_bytes(input: &[u8]) -> Result<(&[u8], Proquint), ProquintParseError> {
203 if input.len() >= 5 {
204 Ok((
205 &input[5..],
206 Self::from_bytes([input[0], input[1], input[2], input[3], input[4]])?,
207 ))
208 } else {
209 Err(ProquintParseError(None))
210 }
211 }
212
213 #[cfg_attr(not(tarpaulin), inline(always))]
215 pub const fn parse_bytes(input: &[u8]) -> Result<Proquint, ProquintParseError> {
216 if input.len() == 5 {
217 Self::from_bytes([input[0], input[1], input[2], input[3], input[4]])
218 } else {
219 Err(ProquintParseError(None))
220 }
221 }
222
223 #[cfg_attr(not(tarpaulin), inline(always))]
227 pub fn parse_partial_value(input: &str) -> Result<(&str, u16), ProquintParseError> {
228 let (rest, result) = Self::parse_partial_value_bytes(input.as_bytes())?;
229 Ok((&input[input.len() - rest.len()..], result))
230 }
231
232 #[cfg_attr(not(tarpaulin), inline(always))]
234 pub const fn parse_value(input: &str) -> Result<u16, ProquintParseError> {
235 Self::parse_value_bytes(input.as_bytes())
236 }
237
238 #[cfg_attr(not(tarpaulin), inline(always))]
242 pub fn parse_partial_value_bytes(input: &[u8]) -> Result<(&[u8], u16), ProquintParseError> {
243 if input.len() >= 5 {
244 Ok((
245 &input[5..],
246 Self::value_from_bytes([input[0], input[1], input[2], input[3], input[4]])?,
247 ))
248 } else {
249 Err(ProquintParseError(None))
250 }
251 }
252
253 #[cfg_attr(not(tarpaulin), inline(always))]
255 pub const fn parse_value_bytes(input: &[u8]) -> Result<u16, ProquintParseError> {
256 if input.len() == 5 {
257 Self::value_from_bytes([input[0], input[1], input[2], input[3], input[4]])
258 } else {
259 Err(ProquintParseError(None))
260 }
261 }
262
263 #[cfg_attr(not(tarpaulin), inline(always))]
267 pub const fn consonant(byte: u8) -> u16 {
268 match byte {
269 b'b' => 0,
270 b'd' => 1,
271 b'f' => 2,
272 b'g' => 3,
273 b'h' => 4,
274 b'j' => 5,
275 b'k' => 6,
276 b'l' => 7,
277 b'm' => 8,
278 b'n' => 9,
279 b'p' => 10,
280 b'r' => 11,
281 b's' => 12,
282 b't' => 13,
283 b'v' => 14,
284 b'z' => 15,
285 _ => u16::MAX, }
287 }
288
289 #[cfg_attr(not(tarpaulin), inline(always))]
293 pub const fn vowel(byte: u8) -> u16 {
294 match byte {
295 b'a' => 0,
296 b'i' => 1,
297 b'o' => 2,
298 b'u' => 3,
299 _ => u16::MAX,
300 }
301 }
302}
303
304impl<'a> TryFrom<&'a str> for Proquint {
305 type Error = ProquintParseError;
306
307 #[inline]
308 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
309 Self::from_str(value)
310 }
311}
312
313impl From<Proquint> for u16 {
314 #[cfg_attr(not(tarpaulin), inline(always))]
315 fn from(value: Proquint) -> Self {
316 value.value()
317 }
318}
319
320impl Debug for Proquint {
321 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322 write!(f, "{}", self.as_str())
323 }
324}
325
326impl Display for Proquint {
327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328 write!(f, "{}", self.as_str())
329 }
330}
331
332#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
334pub struct ProquintEncode<T>(pub T);
335
336impl<T> IntoIterator for ProquintEncode<T>
337where
338 T: IntoIterator,
339 T::Item: IntoFraction<u16>,
340{
341 type Item = Proquint;
342
343 type IntoIter = ProquintEncodeIter<<FractionalDigits<T, u16> as IntoIterator>::IntoIter>;
344
345 #[cfg_attr(not(tarpaulin), inline(always))]
346 fn into_iter(self) -> Self::IntoIter {
347 ProquintEncodeIter(FractionalDigits::new(self.0).into_iter())
348 }
349}
350
351#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
353pub struct ProquintEncodeIter<T>(pub T);
354
355impl<T> Iterator for ProquintEncodeIter<T>
356where
357 T: Iterator,
358 T::Item: Into<Proquint>,
359{
360 type Item = Proquint;
361
362 #[inline]
363 fn next(&mut self) -> Option<Self::Item> {
364 self.0.next().map(Into::into)
365 }
366}
367
368impl<T> Display for ProquintEncode<T>
369where
370 T: Clone + IntoIterator,
371 T::Item: IntoFraction<u16>,
372{
373 #[inline]
374 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 let mut copied = self.clone().into_iter();
376 if let Some(first) = copied.next() {
377 write!(f, "{first}")?;
378 }
379 for digit in copied {
380 write!(f, "-{digit}")?;
381 }
382 Ok(())
383 }
384}
385
386#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
388
389pub struct ParseProquintDigits<T> {
390 pub buffer: T,
392 pub expect_dash: bool,
394}
395
396impl<T> ParseProquintDigits<T> {
397 #[cfg_attr(not(tarpaulin), inline(always))]
399 pub const fn new(buffer: T) -> ParseProquintDigits<T> {
400 ParseProquintDigits {
401 buffer,
402 expect_dash: false,
403 }
404 }
405
406 #[cfg_attr(not(tarpaulin), inline(always))]
408 pub const fn new_trailing(buffer: T) -> ParseProquintDigits<T> {
409 ParseProquintDigits {
410 buffer,
411 expect_dash: true,
412 }
413 }
414}
415
416impl<'a> Iterator for ParseProquintDigits<&'a str> {
417 type Item = u16;
418
419 fn next(&mut self) -> Option<Self::Item> {
420 let mut ptr = self.buffer;
421 if self.expect_dash && *ptr.as_bytes().get(0)? == b'-' {
422 ptr = &ptr[1..];
423 }
424 let (ptr, proquint) = Proquint::parse_partial_value(ptr).ok()?;
425 self.buffer = ptr;
426 self.expect_dash = true;
427 Some(proquint)
428 }
429}
430
431impl<'a> Iterator for ParseProquintDigits<&'a [u8]> {
432 type Item = u16;
433
434 fn next(&mut self) -> Option<Self::Item> {
435 let mut ptr = self.buffer;
436 if self.expect_dash && *ptr.get(0)? == b'-' {
437 ptr = &ptr[1..];
438 }
439 let (ptr, proquint) = Proquint::parse_partial_value_bytes(ptr).ok()?;
440 self.buffer = ptr;
441 self.expect_dash = true;
442 Some(proquint)
443 }
444}
445
446impl<T> ParseProquintDigits<T>
447where
448 ParseProquintDigits<T>: IntoIterator<Item = u16>,
449{
450 #[cfg_attr(not(tarpaulin), inline(always))]
452 pub fn parse<U: FromProquints>(self) -> Result<U, U::FromProquintsError> {
453 U::from_proquints(self)
454 }
455}
456
457#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
459pub struct ParseProquints<T> {
460 pub buffer: T,
462 pub expect_dash: bool,
464}
465
466impl<T> ParseProquints<T> {
467 #[cfg_attr(not(tarpaulin), inline(always))]
469 pub fn new(buffer: T) -> ParseProquints<T> {
470 ParseProquints {
471 buffer,
472 expect_dash: false,
473 }
474 }
475
476 #[cfg_attr(not(tarpaulin), inline(always))]
478 pub fn new_trailing(buffer: T) -> ParseProquints<T> {
479 ParseProquints {
480 buffer,
481 expect_dash: true,
482 }
483 }
484}
485
486impl<'a> Iterator for ParseProquints<&'a str> {
487 type Item = Proquint;
488
489 #[inline]
490 fn next(&mut self) -> Option<Self::Item> {
491 let mut ptr = self.buffer;
492 if self.expect_dash && *ptr.as_bytes().get(0)? == b'-' {
493 ptr = &ptr[1..]
494 }
495 let proquint = Proquint::try_from(ptr.get(0..5)?).ok()?;
496 self.buffer = &ptr[5..];
497 self.expect_dash = true;
498 Some(proquint)
499 }
500}
501
502impl<'a> Iterator for ParseProquints<&'a [u8]> {
503 type Item = Proquint;
504
505 #[inline]
506 fn next(&mut self) -> Option<Self::Item> {
507 let mut ptr = self.buffer;
508 if self.expect_dash && *ptr.get(0)? == b'-' {
509 ptr = &ptr[1..]
510 }
511 let proquint = Proquint::try_from(ptr.get(0..5)?).ok()?;
512 self.buffer = &ptr[5..];
513 self.expect_dash = true;
514 Some(proquint)
515 }
516}
517
518pub trait IntoProquints<'a>: Sized {
520 type DigitsIter: IntoIterator<Item = u16> + 'a;
522
523 fn proquint_digits(self) -> Self::DigitsIter;
525
526 #[cfg_attr(not(tarpaulin), inline(always))]
528 fn proquint_encode(self) -> ProquintEncode<Self::DigitsIter> {
529 ProquintEncode(self.proquint_digits())
530 }
531}
532
533impl<'a, T> IntoProquints<'a> for T
534where
535 T: IntoFraction<u16> + 'a,
536{
537 type DigitsIter = <FractionalDigits<Once<T>, u16> as IntoIterator>::IntoIter;
538
539 #[cfg_attr(not(tarpaulin), inline(always))]
540 fn proquint_digits(self) -> Self::DigitsIter {
541 FractionalDigits::new(once(self)).into_iter()
542 }
543}
544
545impl<'a, const N: usize, T> IntoProquints<'a> for [T; N]
546where
547 T: IntoFraction<u16> + 'a,
548{
549 type DigitsIter =
550 <FractionalDigits<core::array::IntoIter<T, N>, u16> as IntoIterator>::IntoIter;
551
552 #[cfg_attr(not(tarpaulin), inline(always))]
553 fn proquint_digits(self) -> Self::DigitsIter {
554 FractionalDigits::new(self).into_iter()
555 }
556}
557
558impl<'a, T> IntoProquints<'a> for &'a [T]
559where
560 T: Clone + IntoFraction<u16> + 'a,
561{
562 type DigitsIter = <FractionalDigits<core::iter::Cloned<core::slice::Iter<'a, T>>, u16> as IntoIterator>::IntoIter;
563
564 #[cfg_attr(not(tarpaulin), inline(always))]
565 fn proquint_digits(self) -> Self::DigitsIter {
566 FractionalDigits::new(self.iter().cloned()).into_iter()
567 }
568}
569
570pub trait FromProquints
572where
573 Self: Sized,
574{
575 type FromProquintsError;
577
578 fn from_proquints_partial(
582 proquints: impl Iterator<Item = u16>,
583 ) -> Result<Self, Self::FromProquintsError>;
584
585 fn trailing_error(next: Option<u16>) -> Self::FromProquintsError;
587
588 #[cfg_attr(not(tarpaulin), inline(always))]
592 fn from_proquints(
593 proquints: impl IntoIterator<Item = u16>,
594 ) -> Result<Self, Self::FromProquintsError> {
595 let mut proquints = proquints.into_iter();
596 let parse = Self::from_proquints_partial(&mut proquints)?;
597 if let Some(next) = proquints.next() {
598 Err(Self::trailing_error(Some(next)))
599 } else {
600 Ok(parse)
601 }
602 }
603
604 #[cfg_attr(not(tarpaulin), inline(always))]
606 fn parse_partial_proquints(proquints: &str) -> Result<(&str, Self), Self::FromProquintsError> {
607 let mut proquints = ParseProquintDigits::new(proquints);
608 let parse = Self::from_proquints_partial(&mut proquints)?;
609 Ok((proquints.buffer, parse))
610 }
611
612 #[cfg_attr(not(tarpaulin), inline(always))]
614 fn parse_partial_proquints_bytes(
615 proquints: &[u8],
616 ) -> Result<(&[u8], Self), Self::FromProquintsError> {
617 let mut proquints = ParseProquintDigits::new(proquints);
618 let parse = Self::from_proquints_partial(&mut proquints)?;
619 Ok((proquints.buffer, parse))
620 }
621
622 #[cfg_attr(not(tarpaulin), inline(always))]
626 fn parse_proquints(proquints: impl AsRef<[u8]>) -> Result<Self, Self::FromProquintsError> {
627 let mut proquints = ParseProquintDigits::new(proquints.as_ref());
628 let parse = Self::from_proquints_partial(&mut proquints)?;
629 if proquints.buffer.len() > 0 {
630 Err(Self::trailing_error(None))
631 } else {
632 Ok(parse)
633 }
634 }
635}
636
637#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
639pub enum ProquintsParseError {
640 NotEnoughProquints(usize),
642 TrailingData(Option<u16>),
644 InvalidValue,
646}
647
648impl Display for ProquintsParseError {
649 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
650 match self {
651 ProquintsParseError::NotEnoughProquints(p) => {
652 write!(f, "expected at least {p:?} more proquints of data")
653 }
654 ProquintsParseError::TrailingData(d) => write!(f, "trailing data: {d:?}"),
655 ProquintsParseError::InvalidValue => write!(f, "invalid value"),
656 }
657 }
658}
659
660impl<T> FromProquints for T
661where
662 T: FromFraction<u16>,
663{
664 type FromProquintsError = ProquintsParseError;
665
666 #[cfg_attr(not(tarpaulin), inline(always))]
667 fn from_proquints_partial(
668 proquints: impl Iterator<Item = u16>,
669 ) -> Result<Self, Self::FromProquintsError> {
670 FromFraction::from_pieces(proquints).map_err(|err| {
671 if err.0 == 0 {
672 ProquintsParseError::InvalidValue
673 } else {
674 ProquintsParseError::NotEnoughProquints(err.0)
675 }
676 })
677 }
678
679 #[cfg_attr(not(tarpaulin), inline(always))]
680 fn trailing_error(next: Option<u16>) -> Self::FromProquintsError {
681 ProquintsParseError::TrailingData(next)
682 }
683}
684
685#[cfg_attr(not(tarpaulin), inline(always))]
686pub(crate) fn be_encode_array_u16<const M: usize>(array: &[u8]) -> [u16; M] {
687 let mut result = [0; M];
688 for (i, w) in array.chunks(2).enumerate() {
689 result[i] = u16::from_be_bytes(w.try_into().unwrap())
690 }
691 result
692}
693
694#[cfg_attr(not(tarpaulin), inline(always))]
695pub(crate) fn be_encode_array_u32<const M: usize>(array: &[u8]) -> [u32; M] {
696 let mut result = [0; M];
697 for (i, w) in array.chunks(4).enumerate() {
698 result[i] = u32::from_be_bytes(w.try_into().unwrap())
699 }
700 result
701}
702
703#[cfg_attr(not(tarpaulin), inline(always))]
704pub(crate) fn be_encode_array_u64<const M: usize>(array: &[u8]) -> [u64; M] {
705 let mut result = [0; M];
706 for (i, w) in array.chunks(8).enumerate() {
707 result[i] = u64::from_be_bytes(w.try_into().unwrap())
708 }
709 result
710}
711
712#[cfg_attr(not(tarpaulin), inline(always))]
713pub(crate) fn be_encode_array_i16<const M: usize>(array: &[u8]) -> [i16; M] {
714 let mut result = [0; M];
715 for (i, w) in array.chunks(2).enumerate() {
716 result[i] = i16::from_be_bytes(w.try_into().unwrap())
717 }
718 result
719}
720
721#[cfg_attr(not(tarpaulin), inline(always))]
722pub(crate) fn be_encode_array_i32<const M: usize>(array: &[u8]) -> [i32; M] {
723 let mut result = [0; M];
724 for (i, w) in array.chunks(4).enumerate() {
725 result[i] = i32::from_be_bytes(w.try_into().unwrap())
726 }
727 result
728}
729
730#[cfg_attr(not(tarpaulin), inline(always))]
731pub(crate) fn be_encode_array_i64<const M: usize>(array: &[u8]) -> [i64; M] {
732 let mut result = [0; M];
733 for (i, w) in array.chunks(8).enumerate() {
734 result[i] = i64::from_be_bytes(w.try_into().unwrap())
735 }
736 result
737}
738
739#[cfg(feature = "std")]
740mod std_ {
741 use super::*;
742 use std::error::Error;
743 use std::net::{Ipv4Addr, Ipv6Addr};
744
745 impl IntoProquints<'static> for Ipv4Addr {
746 type DigitsIter = core::array::IntoIter<u16, 2>;
747
748 #[cfg_attr(not(tarpaulin), inline(always))]
749 fn proquint_digits(self) -> Self::DigitsIter {
750 be_encode_array_u16::<2>(&self.octets()).into_iter()
751 }
752 }
753
754 impl IntoProquints<'static> for Ipv6Addr {
755 type DigitsIter = core::array::IntoIter<u16, 8>;
756
757 #[cfg_attr(not(tarpaulin), inline(always))]
758 fn proquint_digits(self) -> Self::DigitsIter {
759 be_encode_array_u16::<8>(&self.octets()).into_iter()
760 }
761 }
762
763 impl Error for ProquintParseError {}
764 impl Error for ProquintsParseError {}
765}
766
767#[cfg(test)]
768mod test {
769 use super::*;
770 use proptest::prelude::*;
771
772 #[cfg(feature = "std")]
773 macro_rules! roundtrip {
774 ($t:ty, $i:expr) => {{
775 let mut encoded = format!("{}", $i.proquint_encode());
776 assert_eq!(<$t>::parse_proquints(&encoded).unwrap(), $i);
777 assert_eq!(<$t>::parse_proquints(encoded.as_bytes()).unwrap(), $i);
778 assert_eq!(<$t>::parse_partial_proquints(&encoded).unwrap(), ("", $i));
779 assert_eq!(
780 <$t>::parse_partial_proquints_bytes(encoded.as_bytes()).unwrap(),
781 (&b""[..], $i)
782 );
783 encoded.push('a');
784 assert_eq!(
785 <$t>::parse_proquints(&encoded).unwrap_err(),
786 ProquintsParseError::TrailingData(None)
787 );
788 assert_eq!(<$t>::parse_partial_proquints(&encoded).unwrap(), ("a", $i));
789 }};
790 }
791
792 proptest! {
793 #[test]
794 fn roundtrip_proquint(i: u16) {
795 let pq = Proquint::from(i);
796 assert_eq!(u16::from(pq), i);
797 let as_str = pq.as_str();
798 assert_eq!(as_str.as_bytes(), &*pq);
799 assert_eq!(as_str, <Proquint as AsRef<str>>::as_ref(&pq));
800 assert_eq!(as_str.as_bytes(), <Proquint as AsRef<[u8]>>::as_ref(&pq));
801 assert_eq!(as_str.as_bytes(), <Proquint as AsRef<[u8; 5]>>::as_ref(&pq));
802 assert_eq!(as_str, <Proquint as Borrow<str>>::borrow(&pq));
803 assert_eq!(as_str.as_bytes(), <Proquint as Borrow<[u8]>>::borrow(&pq));
804 assert_eq!(as_str.as_bytes(), <Proquint as Borrow<[u8; 5]>>::borrow(&pq));
805 #[cfg(feature = "std")]
806 {
807 assert_eq!(format!("{pq}").parse::<Proquint>().unwrap(), pq);
808 assert_eq!(format!("{pq:?}").parse::<Proquint>().unwrap(), pq);
809 }
810 assert_eq!(Proquint::try_from(*pq).unwrap(), pq);
811 assert_eq!(Proquint::try_from(&pq[..]).unwrap(), pq);
812 }
813 }
814
815 #[cfg(feature = "std")]
816 proptest! {
817 #[test]
818 fn roundtrip_u128(i: u128) {
819 roundtrip!(u128, i);
820 }
821
822 #[test]
823 fn roundtrip_u64(i: u64) {
824 roundtrip!(u64, i);
825 roundtrip!(u128, i as u128);
826 assert_eq!(
827 u128::from_proquints(i.proquint_digits()).unwrap(),
828 i as u128
829 );
830 }
831
832 #[test]
833 fn roundtrip_u32(i: u32) {
834 roundtrip!(u32, i);
835 }
836
837 #[test]
838 fn roundtrip_u16(i: u16) {
839 roundtrip!(u16, i);
840 }
841
842 #[test]
843 fn roundtrip_u8(i: u8) {
844 roundtrip!(u8, i);
845 }
846
847 #[test]
848 fn roundtrip_i128(i: i128) {
849 roundtrip!(i128, i);
850 }
851
852 #[test]
853 fn roundtrip_i64(i: i64) {
854 roundtrip!(i64, i);
855 }
856
857 #[test]
858 fn roundtrip_i32(i: i32) {
859 roundtrip!(i32, i);
860 }
861
862 #[test]
863 fn roundtrip_i16(i: i16) {
864 roundtrip!(i16, i);
865 }
866
867 #[test]
868 fn roundtrip_u8_4(i: [u8; 4]) {
869 roundtrip!([u8; 4], i);
870 }
871
872 #[test]
873 fn roundtrip_u8_3(i: [u8; 3]) {
874 roundtrip!([u8; 3], i);
875 }
876
877 #[test]
878 fn parse_proquints(v: Vec<u16>) {
879 let proquints = format!("{}", ProquintEncode(v));
880 Iterator::eq(ParseProquintDigits::new(&proquints[..]), ParseProquints::new(&proquints[..]).map(u16::from));
881 Iterator::eq(ParseProquintDigits::new(&proquints[..]).map(Proquint::from), ParseProquints::new(&proquints[..]));
882 Iterator::eq(ParseProquintDigits::new(proquints.as_bytes()), ParseProquints::new(proquints.as_bytes()).map(u16::from));
883 Iterator::eq(ParseProquintDigits::new(proquints.as_bytes()).map(Proquint::from), ParseProquints::new(proquints.as_bytes()));
884 }
885
886 #[test]
887 fn roundtrip_ipv4(i: std::net::Ipv4Addr) {
888 roundtrip!(std::net::Ipv4Addr, i);
889 assert_eq!(i.proquint_digits().count(), 2);
890 assert_eq!(i.proquint_encode().into_iter().count(), 2);
891 assert!(i.proquint_digits().eq(i.octets().proquint_digits()));
892 assert!(i.proquint_encode().into_iter().eq(i.octets().proquint_encode().into_iter()));
893 assert_eq!(format!("{}", i.proquint_encode()), format!("{}", i.octets().proquint_encode()));
894 }
895
896 #[test]
897 fn roundtrip_ipv6(i: std::net::Ipv6Addr) {
898 roundtrip!(std::net::Ipv6Addr, i);
899 }
900 }
901
902 #[cfg(feature = "std")]
903 #[test]
904 fn proquint_encode_nil() {
905 assert_eq!(
906 format!("{}", ProquintEncode(core::iter::Empty::<u16>::default())),
907 ""
908 );
909 }
910
911 #[test]
912 fn invalid_proquint() {
913 assert_eq!(
914 Proquint::try_from(*b"xxxxx"),
915 Err(ProquintParseError(Some(*b"xxxxx")))
916 );
917 assert_eq!(
918 Proquint::try_from("bxxxx"),
919 Err(ProquintParseError(Some(*b"bxxxx")))
920 );
921 assert_eq!(
922 Proquint::parse_str("bxxxx"),
923 Err(ProquintParseError(Some(*b"bxxxx")))
924 );
925 assert_eq!(
926 Proquint::parse_partial("bxxxx"),
927 Err(ProquintParseError(Some(*b"bxxxx")))
928 );
929 assert_eq!(
930 Proquint::parse_partial("bxxx"),
931 Err(ProquintParseError(None))
932 );
933 assert_eq!(
934 Proquint::parse_value_bytes(b"baxxx"),
935 Err(ProquintParseError(Some(*b"baxxx")))
936 );
937 assert_eq!(
938 Proquint::parse_value_bytes(b"baxx"),
939 Err(ProquintParseError(None))
940 );
941 assert_eq!(
942 Proquint::parse_bytes(b"baxxx"),
943 Err(ProquintParseError(Some(*b"baxxx")))
944 );
945 assert_eq!(
946 Proquint::parse_bytes(b"baxx"),
947 Err(ProquintParseError(None))
948 );
949 assert_eq!(
950 Proquint::parse_partial_bytes(b"baxxx"),
951 Err(ProquintParseError(Some(*b"baxxx")))
952 );
953 assert_eq!(
954 Proquint::parse_partial_bytes(b"baxx"),
955 Err(ProquintParseError(None))
956 );
957 assert_eq!(
958 Proquint::parse_value("babxx"),
959 Err(ProquintParseError(Some(*b"babxx")))
960 );
961 assert_eq!(
962 "12345".parse::<Proquint>(),
963 Err(ProquintParseError(Some(*b"12345")))
964 );
965 #[cfg(feature = "std")]
966 {
967 assert_eq!(
968 format!("{}", ProquintParseError(None)),
969 "not enough characters to parse a proquint"
970 );
971 assert_eq!(
972 format!("{}", ProquintParseError(Some(*b"xxxxx"))),
973 "[120, 120, 120, 120, 120] (utf-8 = Ok(\"xxxxx\")) is not a valid proquint"
974 );
975 assert_eq!(
976 format!("{}", ProquintsParseError::InvalidValue),
977 "invalid value"
978 );
979 assert_eq!(
980 format!("{}", ProquintsParseError::TrailingData(None)),
981 "trailing data: None"
982 );
983 assert_eq!(
984 format!("{}", ProquintsParseError::NotEnoughProquints(5)),
985 "expected at least 5 more proquints of data"
986 );
987 }
988 }
989
990 #[test]
991 fn u8_overflow_proquint() {
992 assert_eq!(
993 u8::from_proquints(once(0x0100)),
994 Err(ProquintsParseError::InvalidValue)
995 );
996 assert_eq!(
997 u8::from_proquints([0x0010, 0x0000]),
998 Err(ProquintsParseError::TrailingData(Some(0x0000)))
999 )
1000 }
1001
1002 #[cfg(feature = "std")]
1003 #[test]
1004 fn proquint_example_ips() {
1005 use std::net::Ipv4Addr;
1006
1007 let ips = [
1008 (Ipv4Addr::new(127, 0, 0, 1), "lusab-babad"),
1009 (Ipv4Addr::new(63, 84, 220, 193), "gutih-tugad"),
1010 (Ipv4Addr::new(63, 118, 7, 35), "gutuk-bisog"),
1011 (Ipv4Addr::new(140, 98, 193, 141), "mudof-sakat"),
1012 (Ipv4Addr::new(64, 255, 6, 200), "haguz-biram"),
1013 (Ipv4Addr::new(128, 30, 52, 45), "mabiv-gibot"),
1014 (Ipv4Addr::new(147, 67, 119, 2), "natag-lisaf"),
1015 (Ipv4Addr::new(212, 58, 253, 68), "tibup-zujah"),
1016 (Ipv4Addr::new(216, 35, 68, 215), "tobog-higil"),
1017 (Ipv4Addr::new(216, 68, 232, 21), "todah-vobij"),
1018 (Ipv4Addr::new(198, 81, 129, 136), "sinid-makam"),
1019 (Ipv4Addr::new(12, 110, 110, 204), "budov-kuras"),
1020 (Ipv4Addr::new(255, 255, 255, 255), "zuzuz-zuzuz"),
1021 ];
1022
1023 for (ip, name) in ips {
1024 assert_eq!(format!("{}", ip.proquint_encode()), name);
1025 }
1026 }
1027
1028 #[cfg(feature = "std")]
1029 #[test]
1030 fn proquint_ips_octet_encode() {
1031 use std::net::Ipv4Addr;
1032 assert_eq!(Ipv4Addr::new(127, 0, 0, 1).octets(), [127, 0, 0, 1]);
1033 assert_eq!(
1034 format!("{}", Ipv4Addr::new(127, 0, 0, 1).proquint_encode()),
1035 format!("{}", [127u8, 0, 0, 1].proquint_encode())
1036 );
1037 }
1038}