use std::io::{self, Read, Write};
use std::str::FromStr;
pub mod prelude;
#[derive(Debug, Eq, PartialEq)]
pub struct Tag(u16);
pub trait Serialize {
fn serialize<W: Write>(&self, stream: &mut W) -> io::Result<()>;
}
pub trait Deserialize: Sized {
fn deserialize<R: Read>(stream: &mut R) -> io::Result<Self>;
}
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
let mut buf = [0u8; 1];
stream.read_exact(&mut buf)?;
Ok(buf[0])
}
fn read_u16_be<R: Read>(stream: &mut R) -> io::Result<u16> {
let mut buf = [0u8; 2];
stream.read_exact(&mut buf)?;
Ok(u16::from_be_bytes(buf))
}
fn read_u32_be<R: Read>(stream: &mut R) -> io::Result<u32> {
let mut buf = [0u8; 4];
stream.read_exact(&mut buf)?;
Ok(u32::from_be_bytes(buf))
}
#[allow(unused)]
fn read_u64_be<R: Read>(stream: &mut R) -> io::Result<u64> {
let mut buf = [0u8; 8];
stream.read_exact(&mut buf)?;
Ok(u64::from_be_bytes(buf))
}
#[must_use]
pub const fn is_valid_tag_char(c: u8) -> bool {
c.is_ascii_lowercase() || c.is_ascii_uppercase() || c.is_ascii_digit() || c == b'_'
}
impl Tag {
pub fn with_str(s: &str) -> io::Result<Self> {
if s.len() != 2 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Tag must be exactly 2 characters long.",
));
}
let bytes = s.as_bytes();
if !is_valid_tag_char(bytes[0]) || !is_valid_tag_char(bytes[1]) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid characters in tag.",
));
}
Ok(Self(u16::from_be_bytes([bytes[0], bytes[1]])))
}
pub fn new(v: u16) -> io::Result<Self> {
let [first, second] = v.to_be_bytes();
if !is_valid_tag_char(first) || !is_valid_tag_char(second) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid characters in tag.",
));
}
Ok(Self(v))
}
#[must_use]
pub const fn inner(&self) -> u16 {
self.0
}
}
impl FromStr for Tag {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::with_str(s)
}
}
impl TryFrom<&str> for Tag {
type Error = io::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::with_str(value)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct TagHeader {
pub name: Tag,
}
impl TagHeader {
#[must_use]
pub const fn new(name: Tag) -> Self {
Self { name }
}
}
impl Serialize for TagHeader {
fn serialize<W: Write>(&self, stream: &mut W) -> io::Result<()> {
stream.write_all(&self.name.0.to_be_bytes())
}
}
impl Deserialize for TagHeader {
fn deserialize<R: Read>(stream: &mut R) -> io::Result<Self> {
Ok(Self {
name: Tag::new(read_u16_be(stream)?)?,
})
}
}
fn decode_size<R: Read>(stream: &mut R) -> io::Result<u32> {
let mut size = 0u32;
let mut shift = 0;
loop {
let octet = read_u8(stream)?;
size |= u32::from(octet & 0x7F) << shift;
shift += 7;
if shift > 28 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Size exceeds u32 maximum value",
));
}
if octet & 0x80 == 0 {
break;
}
}
Ok(size)
}
fn encode_size(size: u32) -> Vec<u8> {
let mut encoded = Vec::new();
let mut current = size;
loop {
let octet = (current & 0x7F) as u8;
current >>= 7;
if current > 0 {
encoded.push(octet | 0x80);
} else {
encoded.push(octet);
break;
}
}
encoded
}
#[derive(Debug, Eq, PartialEq)]
pub struct ChunkHeader {
pub tag: TagHeader,
pub size: u32,
}
impl ChunkHeader {
#[must_use]
pub const fn new(tag: Tag, size: u32) -> Self {
Self {
tag: TagHeader::new(tag),
size,
}
}
}
impl Serialize for ChunkHeader {
fn serialize<W: Write>(&self, stream: &mut W) -> io::Result<()> {
self.tag.serialize(stream)?;
stream.write_all(encode_size(self.size).as_slice())
}
}
impl Deserialize for ChunkHeader {
fn deserialize<R: Read>(stream: &mut R) -> io::Result<Self> {
Ok(Self {
tag: TagHeader::deserialize(stream)?,
size: decode_size(stream)?,
})
}
}
#[derive(Debug, Eq, PartialEq, Default)]
pub struct RaffHeader {
pub major: u8,
pub minor: u8,
}
pub const RAFF_TEXT: u32 = 0x5241_4646;
pub const RAFF_ICON: u32 = 0xF09F_A68A;
pub const RAFF_MAJOR: u8 = 0x00;
pub const RAFF_MINOR: u8 = 0x02;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct RaffApplicationHeader {
pub identifier: [u8; 16],
pub app_major: u8,
pub app_minor: u8,
}
impl RaffApplicationHeader {
#[must_use]
pub const fn new(identifier: [u8; 16], app_major: u8, app_minor: u8) -> Self {
Self {
identifier,
app_major,
app_minor,
}
}
pub fn with_str(id: &str, app_major: u8, app_minor: u8) -> io::Result<Self> {
let id_bytes = id.as_bytes();
if id_bytes.len() > 16 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Identifier must be at most 16 bytes when UTF-8 encoded.",
));
}
let mut identifier = [0u8; 16];
identifier[..id_bytes.len()].copy_from_slice(id_bytes);
Ok(Self::new(identifier, app_major, app_minor))
}
#[must_use]
pub fn as_string(&self) -> String {
let end = self.identifier.iter().position(|&b| b == 0).unwrap_or(16);
String::from_utf8_lossy(&self.identifier[..end]).to_string()
}
}
impl RaffHeader {
#[must_use]
pub const fn new() -> Self {
Self {
major: RAFF_MAJOR,
minor: RAFF_MINOR,
}
}
#[must_use]
pub const fn with_version(major: u8, minor: u8) -> Self {
Self { major, minor }
}
}
#[must_use]
pub fn to_version(data: u8) -> u8 {
if !(48..=57).contains(&data) {
return 0;
}
data - 48
}
#[must_use]
pub const fn from_version(data: u8) -> u8 {
if data > 9 {
return 0;
}
data + 48
}
impl Deserialize for RaffHeader {
fn deserialize<R: Read>(stream: &mut R) -> io::Result<Self> {
let icon = read_u32_be(stream)?;
if icon != RAFF_ICON {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid icon",
));
}
let raff_text = read_u32_be(stream)?;
if raff_text != RAFF_TEXT {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid RAFF text",
));
}
let mut version_buf = [0u8; 4]; stream.read_exact(&mut version_buf)?;
if version_buf[1] != b'.' || version_buf[3] != b'\n' {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid version",
));
}
let sub_char = read_u8(stream)?;
if sub_char != 0x1A {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Missing SUB character",
));
}
Ok(Self {
major: to_version(version_buf[0]),
minor: to_version(version_buf[2]),
})
}
}
impl Serialize for RaffHeader {
fn serialize<W: Write>(&self, stream: &mut W) -> io::Result<()> {
stream.write_all(&RAFF_ICON.to_be_bytes())?;
stream.write_all(&RAFF_TEXT.to_be_bytes())?;
stream.write_all(&[from_version(self.major)])?;
stream.write_all(b".")?;
stream.write_all(&[from_version(self.minor)])?;
stream.write_all(&[0x0A])?;
stream.write_all(&[0x1A])?; Ok(())
}
}
impl Serialize for RaffApplicationHeader {
fn serialize<W: Write>(&self, stream: &mut W) -> io::Result<()> {
stream.write_all(&self.identifier)?;
stream.write_all(&[self.app_major])?;
stream.write_all(&[self.app_minor])?;
Ok(())
}
}
impl Deserialize for RaffApplicationHeader {
fn deserialize<R: Read>(stream: &mut R) -> io::Result<Self> {
let mut identifier = [0u8; 16];
stream.read_exact(&mut identifier)?;
let app_major = read_u8(stream)?;
let app_minor = read_u8(stream)?;
Ok(Self::new(identifier, app_major, app_minor))
}
}
pub fn write_chunk<W: Write>(stream: &mut W, tag: Tag, data: &[u8]) -> io::Result<()> {
let size = u32::try_from(data.len())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Data length exceeds u32::MAX"))?;
let header = ChunkHeader::new(tag, size);
header.serialize(stream)?;
stream.write_all(data)
}
pub fn read_raff_header<R: Read>(
stream: &mut R,
) -> io::Result<(RaffHeader, RaffApplicationHeader)> {
let header = RaffHeader::deserialize(stream)?;
if header.major != RAFF_MAJOR || header.minor != RAFF_MINOR {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid RAFF header",
));
}
let app_header = RaffApplicationHeader::deserialize(stream)?;
Ok((header, app_header))
}
pub fn write_raff_header<W: Write>(
stream: &mut W,
app_header: &RaffApplicationHeader,
) -> io::Result<()> {
let header = RaffHeader::new();
header.serialize(stream)?;
app_header.serialize(stream)
}
pub fn write_raff_header_with_app<W: Write>(
stream: &mut W,
identifier: &str,
major: u8,
minor: u8,
) -> io::Result<()> {
let app_header = RaffApplicationHeader::with_str(identifier, major, minor)?;
write_raff_header(stream, &app_header)
}
pub fn read_chunk_header<R: Read>(stream: &mut R) -> io::Result<ChunkHeader> {
ChunkHeader::deserialize(stream)
}
pub fn write_app_header<W: Write>(
stream: &mut W,
app_header: &RaffApplicationHeader,
) -> io::Result<()> {
app_header.serialize(stream)
}
pub fn read_app_header<R: Read>(stream: &mut R) -> io::Result<RaffApplicationHeader> {
RaffApplicationHeader::deserialize(stream)
}