use thiserror::Error;
use crate::*;
#[derive(Error, Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum InvalidData {
#[error("Unrecognized syllable")]
Syllable,
#[error("Encoded data was too short")]
TooShort,
#[error("Data integrity check failed")]
Checksum,
}
pub type Result<T> = std::result::Result<T, InvalidData>;
pub fn decode(string: impl AsRef<str>) -> Result<Vec<u8>> {
decode_with_settings(string, Checksum::default())
}
pub fn decode_with_settings(string: impl AsRef<str>, checksum: Checksum) -> Result<Vec<u8>> {
decode_mono(string.as_ref(), checksum)
}
#[inline(never)]
fn decode_mono(mut string: &str, checksum: Checksum) -> Result<Vec<u8>> {
let mut buffer = Vec::with_capacity(string.len() / 2);
while !string.is_empty() {
let (index, length) = syllables::longest_prefix_of(string)
.ok_or(InvalidData::Syllable)?;
buffer.push(index);
string = &string[length..];
string = string
.find(char::is_alphabetic)
.map(|index| string.split_at(index))
.map(|(_, next)| next)
.unwrap_or("");
}
let payload_len = buffer
.len()
.checked_sub(checksum.len())
.ok_or(InvalidData::TooShort)?;
let mut hash = Fnv1a::new();
for (i, byte) in buffer.iter_mut().enumerate().take(payload_len) {
*byte = running_code(*byte, i);
hash.update(*byte);
}
let checksum_match = buffer
.drain(payload_len..)
.zip(hash.bytes())
.all(|(a, b)| a == b);
checksum_match
.then_some(buffer)
.ok_or(InvalidData::Checksum)
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn outliers() {
let test = |input| {
decode_with_settings(input, Checksum::Disabled).unwrap();
};
test("uuuuuuuuuuu");
test("u u u u u u u u u u u ");
test("sive123sive@tive ππππ sonπ");
}
#[test]
fn syllable_err() {
let test = |input| {
let result = decode_with_settings(input, Checksum::Disabled);
assert_eq!(result, Err(InvalidData::Syllable));
};
test("π");
test("b");
test("siv");
test("faevlesa");
}
#[test]
fn too_short_err() {
let test = |input, checksum| {
let result = decode_with_settings(input, checksum);
assert_eq!(result, Err(InvalidData::TooShort));
};
test("", Checksum::Length1);
test("sive", Checksum::Length2);
test("uu", Checksum::Length3);
}
}