use crate::error::FakeTagError;
use crate::id3::v2::error::{Id3v2HeaderError, Id3v2ParseError};
use crate::id3::v2::restrictions::TagRestrictions;
use crate::id3::v2::util::synchsafe::{SynchsafeInteger, UnsynchronizedStream};
use std::fmt::Display;
use std::io::Read;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Id3v2Version {
V2,
V3,
V4,
}
impl Display for Id3v2Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Id3v2Version::V2 => write!(f, "ID3v2.2"),
Id3v2Version::V3 => write!(f, "ID3v2.3"),
Id3v2Version::V4 => write!(f, "ID3v2.4"),
}
}
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct Id3v2TagFlags {
pub unsynchronisation: bool,
pub experimental: bool,
pub footer: bool,
pub crc: bool,
pub restrictions: Option<TagRestrictions>,
}
impl Id3v2TagFlags {
pub fn as_id3v24_byte(&self) -> u8 {
let mut byte = 0;
if self.unsynchronisation {
byte |= 0x80;
}
if self.experimental {
byte |= 0x20;
}
if self.footer {
byte |= 0x10;
}
byte
}
pub fn as_id3v23_byte(&self) -> u8 {
let mut byte = 0;
if self.experimental {
byte |= 0x40;
}
if self.footer {
byte |= 0x10;
}
byte
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct Id3v2Header {
pub version: Id3v2Version,
pub flags: Id3v2TagFlags,
pub size: u32,
pub extended_size: u32,
}
impl Id3v2Header {
pub(crate) fn parse<R>(bytes: &mut R) -> Result<Self, Id3v2ParseError>
where
R: Read,
{
log::debug!("Parsing ID3v2 header");
let mut header = [0; 10];
bytes.read_exact(&mut header)?;
if &header[..3] != b"ID3" {
return Err(FakeTagError.into());
}
let version = match header[3] {
2 => Id3v2Version::V2,
3 => Id3v2Version::V3,
4 => Id3v2Version::V4,
major => {
return Err(Id3v2HeaderError::BadId3v2Version(major, header[4]).into());
},
};
let flags = header[5];
if version == Id3v2Version::V2 && flags & 0x40 == 0x40 {
return Err(Id3v2HeaderError::V2Compression.into());
}
let mut flags_parsed = Id3v2TagFlags {
unsynchronisation: flags & 0x80 == 0x80,
experimental: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x20 == 0x20,
footer: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x10 == 0x10,
crc: false, restrictions: None, };
let size = BigEndian::read_u32(&header[6..]).unsynch();
let mut extended_size = 0;
let extended_header =
(version == Id3v2Version::V4 || version == Id3v2Version::V3) && flags & 0x40 == 0x40;
if extended_header {
match version {
Id3v2Version::V3 => {
if flags_parsed.unsynchronisation {
let mut reader = UnsynchronizedStream::new(bytes);
extended_size = reader.read_u32::<BigEndian>()?;
read_extended_header(&mut reader, &mut flags_parsed, extended_size)?;
} else {
extended_size = bytes.read_u32::<BigEndian>()?;
read_extended_header(bytes, &mut flags_parsed, extended_size)?;
}
},
Id3v2Version::V4 => {
extended_size = bytes.read_u32::<BigEndian>()?.unsynch();
read_extended_header(bytes, &mut flags_parsed, extended_size)?;
},
_ => unreachable!(),
}
}
if extended_size > 0 && extended_size >= size {
return Err(Id3v2HeaderError::BadExtendedHeaderSize.into());
}
Ok(Id3v2Header {
version,
flags: flags_parsed,
size,
extended_size,
})
}
pub(crate) fn full_tag_size(&self) -> u32 {
self.size + 10 + self.extended_size + if self.flags.footer { 10 } else { 0 }
}
}
fn read_extended_header<R: Read>(
reader: &mut R,
flags: &mut Id3v2TagFlags,
header_size: u32,
) -> Result<(), Id3v2ParseError> {
if header_size < 6 {
return Err(Id3v2HeaderError::BadExtendedHeaderSize.into());
}
let _num_flag_bytes = reader.read_u8()?;
let extended_flags = reader.read_u8()?;
if extended_flags & 0x20 == 0x20 {
flags.crc = true;
let mut crc = [0; 6];
reader.read_exact(&mut crc)?;
}
if extended_flags & 0x10 == 0x10 {
let _data_length = reader.read_u8()?;
flags.restrictions = Some(TagRestrictions::from_byte(reader.read_u8()?));
}
Ok(())
}