use crate::common::error::{MutagenError, Result};
pub struct BitPaddedInt;
impl BitPaddedInt {
pub fn decode(data: &[u8], bits: u8) -> u32 {
let mut result: u32 = 0;
let mask = (1u32 << bits) - 1;
for &b in data {
result = (result << bits) | (b as u32 & mask);
}
result
}
pub fn syncsafe(data: &[u8]) -> u32 {
Self::decode(data, 7)
}
pub fn normal(data: &[u8]) -> u32 {
Self::decode(data, 8)
}
pub fn encode(value: u32, width: usize, bits: u8) -> Vec<u8> {
let mut result = vec![0u8; width];
let mask = (1u32 << bits) - 1;
let mut val = value;
for i in (0..width).rev() {
result[i] = (val & mask) as u8;
val >>= bits;
}
result
}
pub fn has_valid_padding(data: &[u8]) -> bool {
data.iter().all(|&b| b & 0x80 == 0)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ID3Flags {
pub unsynchronisation: bool,
pub extended: bool,
pub experimental: bool,
pub footer: bool,
}
#[derive(Debug, Clone)]
pub struct ID3Header {
pub version: (u8, u8), pub flags: ID3Flags,
pub size: u32, pub offset: u64, }
impl ID3Header {
pub fn parse(data: &[u8], offset: u64) -> Result<Self> {
if data.len() < 10 {
return Err(MutagenError::ID3NoHeader);
}
if &data[0..3] != b"ID3" {
return Err(MutagenError::ID3NoHeader);
}
let major = data[3];
let revision = data[4];
if !(2..=4).contains(&major) {
return Err(MutagenError::ID3UnsupportedVersion(
format!("ID3v2.{}.{}", major, revision),
));
}
let flag_byte = data[5];
let flags = ID3Flags {
unsynchronisation: flag_byte & 0x80 != 0,
extended: flag_byte & 0x40 != 0,
experimental: flag_byte & 0x20 != 0,
footer: major == 4 && (flag_byte & 0x10 != 0),
};
let size = BitPaddedInt::syncsafe(&data[6..10]);
Ok(ID3Header {
version: (major, revision),
flags,
size,
offset,
})
}
pub fn full_size(&self) -> u32 {
let mut s = self.size + 10;
if self.flags.footer {
s += 10;
}
s
}
}
pub fn determine_bpi(data: &[u8], frames_end: usize) -> u8 {
let end = frames_end.min(data.len());
let empty10 = [0u8; 10];
let mut o = 0usize;
let mut asbpi = 0u32;
let bpioff;
loop {
if o + 10 > end { bpioff = o as i64 - end as i64; break; }
if data[o..o + 10] == empty10 {
bpioff = -(((end - o) % 10) as i64);
break;
}
let size = BitPaddedInt::syncsafe(&data[o + 4..o + 8]) as usize;
o += 10 + size;
let id = &data[o - 10 - size..o - 10 - size + 4];
if id.iter().all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit()) {
asbpi += 1;
}
}
let mut o = 0usize;
let mut asint = 0u32;
let intoff;
loop {
if o + 10 > end { intoff = o as i64 - end as i64; break; }
if data[o..o + 10] == empty10 {
intoff = -(((end - o) % 10) as i64);
break;
}
let size = BitPaddedInt::normal(&data[o + 4..o + 8]) as usize;
o += 10 + size;
let id = &data[o - 10 - size..o - 10 - size + 4];
if id.iter().all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit()) {
asint += 1;
}
}
if asint > asbpi || (asint == asbpi && bpioff >= 1 && intoff <= 1) {
8
} else {
7
}
}
pub fn find_id3v2_header(data: &[u8]) -> Option<u64> {
if data.len() >= 10 && &data[0..3] == b"ID3" {
Some(0)
} else {
None
}
}