use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::restrictions::TagRestrictions;
use crate::id3::v2::util::synchsafe::SynchsafeInteger;
use crate::macros::err;
use std::io::Read;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Id3v2Version {
V2,
V3,
V4,
}
#[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>
where
R: Read,
{
log::debug!("Parsing ID3v2 header");
let mut header = [0; 10];
bytes.read_exact(&mut header)?;
if &header[..3] != b"ID3" {
err!(FakeTag);
}
let version = match header[3] {
2 => Id3v2Version::V2,
3 => Id3v2Version::V3,
4 => Id3v2Version::V4,
major => {
return Err(
Id3v2Error::new(Id3v2ErrorKind::BadId3v2Version(major, header[4])).into(),
);
},
};
let flags = header[5];
if version == Id3v2Version::V2 && flags & 0x40 == 0x40 {
return Err(Id3v2Error::new(Id3v2ErrorKind::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 {
extended_size = bytes.read_u32::<BigEndian>()?.unsynch();
if extended_size < 6 {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into());
}
let _num_flag_bytes = bytes.read_u8()?;
let extended_flags = bytes.read_u8()?;
if extended_flags & 0x20 == 0x20 {
flags_parsed.crc = true;
let mut crc = [0; 6];
bytes.read_exact(&mut crc)?;
}
if extended_flags & 0x10 == 0x10 {
let _data_length = bytes.read_u8()?;
flags_parsed.restrictions = Some(TagRestrictions::from_byte(bytes.read_u8()?));
}
}
if extended_size > 0 && extended_size >= size {
return Err(Id3v2Error::new(Id3v2ErrorKind::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 }
}
}