1use crate::{
2 error::{Error, Result},
3 util::{probe_ape, probe_id3v1, probe_lyrics3v2, ID3V1_OFFSET},
4};
5use byteorder::{LittleEndian, ReadBytesExt};
6use std::io::{Read, Seek, SeekFrom};
7
8pub(super) const APE_VERSION: u32 = 2000;
9
10#[derive(Debug)]
11pub(super) struct Meta {
12 pub(super) size: u32,
14 pub(super) position: MetaPosition,
16 pub(super) has_header: bool,
18 pub(super) item_count: u32,
20 pub(super) start_pos: u64,
22 pub(super) end_pos: u64,
24}
25
26impl Meta {
27 pub(super) fn read<R: Read + Seek>(reader: &mut R) -> Result<Meta> {
28 const APE_HEADER_SIZE: i64 = 32;
29
30 let mut found = probe_ape(reader, SeekFrom::End(-APE_HEADER_SIZE))? || probe_ape(reader, SeekFrom::Start(0))?;
31 if !found && probe_id3v1(reader)? {
34 found = probe_ape(reader, SeekFrom::End(ID3V1_OFFSET - APE_HEADER_SIZE))?;
35 if !found {
36 let size = probe_lyrics3v2(reader)?;
38 if size != -1 {
39 found = probe_ape(reader, SeekFrom::End(ID3V1_OFFSET - size - APE_HEADER_SIZE))?;
40 }
41 }
42 }
43 if !found {
44 return Err(Error::TagNotFound);
45 }
46 if reader.read_u32::<LittleEndian>()? != APE_VERSION {
47 return Err(Error::InvalidApeVersion);
48 }
49 let size = reader.read_u32::<LittleEndian>()?;
50 let item_count = reader.read_u32::<LittleEndian>()?;
51 let flags = MetaFlags::from_raw(reader.read_u32::<LittleEndian>()?);
52 const RESERVED_BYTES_NUM: i64 = 8;
54 let end_pos = reader.seek(SeekFrom::Current(RESERVED_BYTES_NUM))?;
55 Ok(Meta {
56 size,
57 position: flags.position,
58 has_header: flags.has_header,
59 item_count,
60 start_pos: match flags.position {
61 MetaPosition::Header => end_pos,
62 MetaPosition::Footer => end_pos - size as u64,
63 },
64 end_pos: match flags.position {
65 MetaPosition::Header => {
66 let mut pos = end_pos + size as u64;
67 if flags.has_footer {
68 pos -= APE_HEADER_SIZE as u64;
69 }
70 pos
71 }
72 MetaPosition::Footer => end_pos - APE_HEADER_SIZE as u64,
73 },
74 })
75 }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq)]
79pub(super) enum MetaPosition {
80 Header,
82 Footer,
84}
85
86const HAS_HEADER: u32 = 1 << 31;
87const HAS_NO_FOOTER: u32 = 1 << 30;
88const IS_HEADER: u32 = 1 << 29;
89
90struct MetaFlags {
91 position: MetaPosition,
92 has_header: bool,
93 has_footer: bool,
94}
95
96impl MetaFlags {
97 fn from_raw(raw: u32) -> Self {
98 Self {
99 position: if raw & IS_HEADER != 0 {
100 MetaPosition::Header
101 } else {
102 MetaPosition::Footer
103 },
104 has_header: raw & HAS_HEADER != 0,
105 has_footer: raw & HAS_NO_FOOTER == 0,
106 }
107 }
108}
109
110#[cfg(test)]
111mod test {
112 use super::*;
113 use byteorder::{LittleEndian, WriteBytesExt};
114 use std::io::{Cursor, Write};
115
116 #[test]
117 fn found_at_end() {
118 let mut data = Cursor::new(Vec::<u8>::new());
119 let size = 40;
120 let item_count = 4;
121 let flags = 0;
122 data.write_all(&[0; 100]).unwrap();
123 data.write_all(b"APETAGEX").unwrap();
124 data.write_u32::<LittleEndian>(2000).unwrap();
125 data.write_u32::<LittleEndian>(size).unwrap();
126 data.write_u32::<LittleEndian>(item_count).unwrap();
127 data.write_u32::<LittleEndian>(flags).unwrap();
128 data.write_all(&[0; 8]).unwrap();
129 let meta = Meta::read(&mut data).unwrap();
130 assert_eq!(size, meta.size);
131 assert_eq!(item_count, meta.item_count);
132 assert_eq!(meta.position, MetaPosition::Footer);
133 assert!(!meta.has_header);
134 assert_eq!(92, meta.start_pos);
135 assert_eq!(100, meta.end_pos);
136 }
137
138 #[test]
139 fn found_at_start() {
140 let mut data = Cursor::new(Vec::<u8>::new());
141 let size = 50;
142 let item_count = 5;
143 let flags = HAS_HEADER | IS_HEADER | HAS_NO_FOOTER;
144 data.write_all(b"APETAGEX").unwrap();
145 data.write_u32::<LittleEndian>(2000).unwrap();
146 data.write_u32::<LittleEndian>(size).unwrap();
147 data.write_u32::<LittleEndian>(item_count).unwrap();
148 data.write_u32::<LittleEndian>(flags).unwrap();
149 data.write_all(&[0; 8]).unwrap();
150 data.write_all(&[0; 200]).unwrap();
151 let meta = Meta::read(&mut data).unwrap();
152 assert_eq!(size, meta.size);
153 assert_eq!(item_count, meta.item_count);
154 assert_eq!(meta.position, MetaPosition::Header);
155 assert!(meta.has_header);
156 assert_eq!(32, meta.start_pos);
157 assert_eq!(82, meta.end_pos);
158 }
159
160 #[test]
161 fn found_before_id3v1() {
162 let mut data = Cursor::new(Vec::<u8>::new());
163 let size = 62;
164 let item_count = 3;
165 let flags = 0;
166 data.write_all(&[0; 300]).unwrap();
167 data.write_all(b"APETAGEX").unwrap();
168 data.write_u32::<LittleEndian>(2000).unwrap();
169 data.write_u32::<LittleEndian>(size).unwrap();
170 data.write_u32::<LittleEndian>(item_count).unwrap();
171 data.write_u32::<LittleEndian>(flags).unwrap();
172 data.write_all(&[0; 8]).unwrap();
173 data.write_all(b"TAG").unwrap();
174 data.write_all(&[0; 125]).unwrap();
175 let meta = Meta::read(&mut data).unwrap();
176 assert_eq!(size, meta.size);
177 assert_eq!(item_count, meta.item_count);
178 assert_eq!(meta.position, MetaPosition::Footer);
179 assert!(!meta.has_header);
180 assert_eq!(270, meta.start_pos);
181 assert_eq!(300, meta.end_pos);
182 }
183
184 #[test]
185 fn found_before_lyrics3v2() {
186 let mut data = Cursor::new(Vec::<u8>::new());
187 let size = 70;
188 let item_count = 2;
189 let flags = 0;
190 data.write_all(&[0; 600]).unwrap();
191 data.write_all(b"APETAGEX").unwrap();
192 data.write_u32::<LittleEndian>(2000).unwrap();
193 data.write_u32::<LittleEndian>(size).unwrap();
194 data.write_u32::<LittleEndian>(item_count).unwrap();
195 data.write_u32::<LittleEndian>(flags).unwrap();
196 data.write_all(&[0; 8]).unwrap();
197 data.write_all(&[0; 120]).unwrap();
198 data.write_all(b"000120LYRICS200").unwrap();
199 data.write_all(b"TAG").unwrap();
200 data.write_all(&[0; 125]).unwrap();
201 let meta = Meta::read(&mut data).unwrap();
202 assert_eq!(size, meta.size);
203 assert_eq!(item_count, meta.item_count);
204 assert_eq!(meta.position, MetaPosition::Footer);
205 assert!(!meta.has_header);
206 assert_eq!(562, meta.start_pos);
207 assert_eq!(600, meta.end_pos);
208 }
209
210 #[test]
211 fn not_found() {
212 let mut data = Cursor::new((1..200).collect::<Vec<u8>>());
213 let err = Meta::read(&mut data).unwrap_err().to_string();
214 assert_eq!(err, "APE tag does not exist");
215 }
216
217 #[test]
218 fn invalid_ape_version() {
219 let mut data = Cursor::new(Vec::<u8>::new());
220 data.write_all(b"APETAGEX").unwrap();
221 data.write_u32::<LittleEndian>(1000).unwrap();
222 data.write_all(&[0; 20]).unwrap();
223 let err = Meta::read(&mut data).unwrap_err().to_string();
224 assert_eq!(err, "invalid APE version");
225 }
226}