lofty/iff/
chunk.rs

1use crate::config::ParseOptions;
2use crate::error::Result;
3use crate::id3::v2::tag::Id3v2Tag;
4use crate::macros::{err, try_vec};
5use crate::util::text::utf8_decode;
6
7use std::io::{Read, Seek, SeekFrom};
8use std::marker::PhantomData;
9
10use byteorder::{ByteOrder, ReadBytesExt};
11
12const RIFF_CHUNK_HEADER_SIZE: u64 = 8;
13
14pub(crate) struct Chunks<B>
15where
16	B: ByteOrder,
17{
18	pub fourcc: [u8; 4],
19	pub size: u32,
20	remaining_size: u64,
21	_phantom: PhantomData<B>,
22}
23
24impl<B: ByteOrder> Chunks<B> {
25	#[must_use]
26	pub const fn new(file_size: u64) -> Self {
27		Self {
28			fourcc: [0; 4],
29			size: 0,
30			remaining_size: file_size,
31			_phantom: PhantomData,
32		}
33	}
34
35	pub fn next<R>(&mut self, data: &mut R) -> Result<bool>
36	where
37		R: Read,
38	{
39		if self.remaining_size < RIFF_CHUNK_HEADER_SIZE {
40			return Ok(false);
41		}
42
43		data.read_exact(&mut self.fourcc)?;
44		self.size = data.read_u32::<B>()?;
45
46		self.remaining_size = self.remaining_size.saturating_sub(8);
47
48		Ok(true)
49	}
50
51	pub fn read_cstring<R>(&mut self, data: &mut R) -> Result<String>
52	where
53		R: Read + Seek,
54	{
55		let cont = self.content(data)?;
56		self.correct_position(data)?;
57
58		utf8_decode(cont)
59	}
60
61	pub fn read_pstring<R>(&mut self, data: &mut R, size: Option<u32>) -> Result<String>
62	where
63		R: Read + Seek,
64	{
65		let cont = if let Some(size) = size {
66			self.read(data, u64::from(size))?
67		} else {
68			self.content(data)?
69		};
70
71		if cont.len() % 2 != 0 {
72			data.seek(SeekFrom::Current(1))?;
73		}
74
75		utf8_decode(cont)
76	}
77
78	pub fn content<R>(&mut self, data: &mut R) -> Result<Vec<u8>>
79	where
80		R: Read,
81	{
82		self.read(data, u64::from(self.size))
83	}
84
85	fn read<R>(&mut self, data: &mut R, size: u64) -> Result<Vec<u8>>
86	where
87		R: Read,
88	{
89		if size > self.remaining_size {
90			err!(SizeMismatch);
91		}
92
93		let mut content = try_vec![0; size as usize];
94		data.read_exact(&mut content)?;
95
96		self.remaining_size = self.remaining_size.saturating_sub(size);
97		Ok(content)
98	}
99
100	pub fn id3_chunk<R>(&mut self, data: &mut R, parse_options: ParseOptions) -> Result<Id3v2Tag>
101	where
102		R: Read + Seek,
103	{
104		use crate::id3::v2::header::Id3v2Header;
105		use crate::id3::v2::read::parse_id3v2;
106
107		let content = self.content(data)?;
108
109		let reader = &mut &*content;
110
111		let header = Id3v2Header::parse(reader)?;
112		let id3v2 = parse_id3v2(reader, header, parse_options)?;
113
114		// Skip over the footer
115		if id3v2.flags().footer {
116			data.seek(SeekFrom::Current(10))?;
117		}
118
119		self.correct_position(data)?;
120
121		Ok(id3v2)
122	}
123
124	pub fn skip<R>(&mut self, data: &mut R) -> Result<()>
125	where
126		R: Read + Seek,
127	{
128		data.seek(SeekFrom::Current(i64::from(self.size)))?;
129		self.correct_position(data)?;
130
131		self.remaining_size = self.remaining_size.saturating_sub(u64::from(self.size));
132
133		Ok(())
134	}
135
136	pub fn correct_position<R>(&mut self, data: &mut R) -> Result<()>
137	where
138		R: Read + Seek,
139	{
140		// Chunks are expected to start on even boundaries, and are padded
141		// with a 0 if necessary. This is NOT the null terminator of the value,
142		// and it is NOT included in the chunk's size
143		if self.size % 2 != 0 {
144			data.seek(SeekFrom::Current(1))?;
145			self.remaining_size = self.remaining_size.saturating_sub(1);
146		}
147
148		Ok(())
149	}
150}