use bitfield_struct::bitfield;
use num_enum::TryFromPrimitive;
use crate::id::tag::IntoTagType;
#[bitfield(u8, debug = false)]
pub struct TagTypeSize {
#[bits(2)]
pub encoded_size: SizeBits,
#[bits(2)]
pub ty: TypeBits,
#[bits(4)]
pub tag: u8,
}
impl TagTypeSize {
pub fn from_tag<T: IntoTagType>(tag: T, data_len: usize) -> Self {
let size = SizeBits::from_size(data_len);
tag.encode_tag().with_encoded_size(size)
}
pub fn size(self) -> Size {
if self.ty() == TypeBits::Reserved
&& self.tag() == 0xF
&& self.encoded_size() == SizeBits::Two
{
Size::Long
} else {
Size::Short(self.encoded_size().size_bytes())
}
}
}
pub fn encode_unsigned(data: &[u8]) -> &[u8] {
if data.is_empty() {
return data;
}
assert!(data.len() <= 4 && data.len() != 3);
let count_zero = data.iter().rev().take_while(|&&b| b == 0).count();
let mut truncated_len = data.len() - count_zero;
if truncated_len == 0 {
truncated_len = 1;
}
if truncated_len == 3 {
truncated_len = 4;
}
&data[..truncated_len]
}
pub fn encode_signed(data: &[u8]) -> &[u8] {
fn can_drop(&&[lsb, msb]: &&[u8; 2]) -> bool {
(msb == 0 && ((lsb & 0x80) == 0)) ||
(msb == 0xFF) && ((lsb & 0x80) != 0)
}
if data.is_empty() {
return data;
}
let count_zero = data.array_windows::<2>().rev().take_while(can_drop).count();
let mut truncated_len = data.len() - count_zero;
if truncated_len == 3 {
truncated_len = 4;
}
&data[..truncated_len]
}
pub enum Size {
Short(usize),
Long,
}
#[expect(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum SizeBits {
Zero = 0,
One = 1,
Two = 2,
Four = 3,
}
impl SizeBits {
pub const fn into_bits(self) -> u8 {
self as _
}
pub const fn from_bits(value: u8) -> Self {
match value {
0 => Self::Zero,
1 => Self::One,
2 => Self::Two,
3 => Self::Four,
_ => panic!("SizeBits value out of range"),
}
}
pub fn size_bytes(self) -> usize {
match self {
SizeBits::Zero => 0,
SizeBits::One => 1,
SizeBits::Two => 2,
SizeBits::Four => 4,
}
}
pub fn from_size(size: usize) -> Self {
match size {
0 => SizeBits::Zero,
1 => SizeBits::One,
2 => SizeBits::Two,
4 => SizeBits::Four,
n => panic!("improper short item size ({n})"),
}
}
}
#[expect(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
pub enum TypeBits {
Main = 0,
Global = 1,
Local = 2,
Reserved = 3,
}
impl TypeBits {
pub const fn into_bits(self) -> u8 {
self as _
}
pub const fn from_bits(value: u8) -> Self {
match value {
0 => Self::Main,
1 => Self::Global,
2 => Self::Local,
3 => Self::Reserved,
_ => panic!("TypeBits value out of range"),
}
}
}
#[cfg(feature = "std")]
mod std_impls {
use super::*;
use std::fmt::{self, Debug};
impl Debug for TagTypeSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TagTypeSize")
.field("raw", &format_args!("{:#04x}", self.into_bits()))
.field("size", &self.encoded_size().size_bytes())
.field("type", &self.ty())
.field("tag", &format_args!("{:#x}", self.tag()))
.finish()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unsigned_encoding() {
let unsigned_values = [
(0u32, &[0x00][..]),
(0x7F, &[0x7F]),
(0x80, &[0x80]),
(0xFF, &[0xFF]),
(0x100, &[0x00, 0x01]),
(0xFFFF, &[0xFF, 0xFF]),
(0x123456, &[0x56, 0x34, 0x12, 0x00]),
];
for (value, expected) in unsigned_values {
assert_eq!(expected, encode_unsigned(&value.to_le_bytes()))
}
}
#[test]
fn test_signed_encoding() {
let signed_values = [
(0i32, &[0x00][..]),
(1i32, &[0x01]),
(0x7F, &[0x7F]),
(0x80, &[0x80, 0x00]),
(0xFF, &[0xFF, 0x00]),
(0x100, &[0x00, 0x01]),
(0xFFFF, &[0xFF, 0xFF, 0x00, 0x00]),
(0x123456, &[0x56, 0x34, 0x12, 0x00]),
(-1, &[0xFF]),
(-128, &[0x80]),
(-129, &[0x7F, 0xFF]),
(-32768, &[0x00, 0x80]),
(-32769, &[0xFF, 0x7F, 0xFF, 0xFF]),
(-2147483648, &[0x00, 0x00, 0x00, 0x80]),
];
for (value, expected) in signed_values {
let bytes = value.to_le_bytes();
let encoded = encode_signed(&bytes);
assert_eq!(
expected, encoded,
"expected {:X?}, got {:X?}",
expected, encoded
)
}
}
}