1pub const CHUNK_HEADER_BYTES: usize = 32;
24pub const CHUNK_PAYLOAD_BYTES: usize = 2016;
26pub const MAX_CHUNKS: u16 = 32;
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct Chunk {
33 pub index: u16,
35 pub count: u16,
37 pub frame: u32,
39 pub size: u32,
41 pub width: u16,
43 pub height: u16,
45}
46
47impl Chunk {
48 pub fn parse(sector: &[u8]) -> Option<Chunk> {
50 if sector.len() < CHUNK_HEADER_BYTES {
51 return None;
52 }
53 let h = |i: usize| u16::from_le_bytes([sector[i], sector[i + 1]]);
54 let w =
55 |i: usize| u32::from_le_bytes([sector[i], sector[i + 1], sector[i + 2], sector[i + 3]]);
56 if h(0) != 0x0160 || h(2) != 0x8001 {
57 return None;
58 }
59 let chunk = Chunk {
60 index: h(4),
61 count: h(6),
62 frame: w(8),
63 size: w(12),
64 width: h(16),
65 height: h(18),
66 };
67 if chunk.count == 0 || chunk.count > MAX_CHUNKS || chunk.index >= chunk.count {
68 return None;
69 }
70 Some(chunk)
71 }
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct Frame {
77 pub number: u32,
79 pub size: u32,
81 pub width: u16,
83 pub height: u16,
85}
86
87#[derive(Default)]
93pub struct FrameAssembler {
94 frame: u32,
95 mask: u32,
96 count: u16,
97 pub dropped: u32,
99}
100
101impl FrameAssembler {
102 pub const fn new() -> Self {
104 FrameAssembler {
105 frame: 0,
106 mask: 0,
107 count: 0,
108 dropped: 0,
109 }
110 }
111
112 pub fn add(&mut self, sector: &[u8], buf: &mut [u8]) -> Option<Frame> {
117 let chunk = Chunk::parse(sector)?;
118 if chunk.frame != self.frame || chunk.count != self.count {
119 if self.mask != 0 {
120 self.dropped += 1;
121 }
122 self.frame = chunk.frame;
123 self.count = chunk.count;
124 self.mask = 0;
125 }
126 let at = chunk.index as usize * CHUNK_PAYLOAD_BYTES;
127 let end = at + CHUNK_PAYLOAD_BYTES;
128 if end > buf.len() || sector.len() < CHUNK_HEADER_BYTES + CHUNK_PAYLOAD_BYTES {
129 return None;
130 }
131 buf[at..end]
132 .copy_from_slice(§or[CHUNK_HEADER_BYTES..CHUNK_HEADER_BYTES + CHUNK_PAYLOAD_BYTES]);
133 self.mask |= 1 << chunk.index;
134 let full = if chunk.count == 32 {
135 u32::MAX
136 } else {
137 (1u32 << chunk.count) - 1
138 };
139 if self.mask != full {
140 return None;
141 }
142 self.mask = 0;
143 self.frame = 0;
144 Some(Frame {
145 number: chunk.frame,
146 size: chunk.size,
147 width: chunk.width,
148 height: chunk.height,
149 })
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn sector(index: u16, count: u16, frame: u32, fill: u8) -> [u8; 2048] {
158 let mut s = [fill; 2048];
159 s[0..2].copy_from_slice(&0x0160u16.to_le_bytes());
160 s[2..4].copy_from_slice(&0x8001u16.to_le_bytes());
161 s[4..6].copy_from_slice(&index.to_le_bytes());
162 s[6..8].copy_from_slice(&count.to_le_bytes());
163 s[8..12].copy_from_slice(&frame.to_le_bytes());
164 s[12..16].copy_from_slice(&5000u32.to_le_bytes());
165 s[16..18].copy_from_slice(&320u16.to_le_bytes());
166 s[18..20].copy_from_slice(&240u16.to_le_bytes());
167 s
168 }
169
170 #[test]
171 fn assembles_out_of_order_and_counts_drops() {
172 let mut a = FrameAssembler::new();
173 let mut buf = [0u8; 3 * CHUNK_PAYLOAD_BYTES];
174 assert_eq!(a.add(§or(2, 3, 1, 0xC2), &mut buf), None);
175 assert_eq!(a.add(§or(0, 3, 1, 0xC0), &mut buf), None);
176 let f = a.add(§or(1, 3, 1, 0xC1), &mut buf).unwrap();
177 assert_eq!((f.number, f.size, f.width, f.height), (1, 5000, 320, 240));
178 assert_eq!(buf[0], 0xC0);
179 assert_eq!(buf[CHUNK_PAYLOAD_BYTES], 0xC1);
180 assert_eq!(buf[2 * CHUNK_PAYLOAD_BYTES + 5], 0xC2);
181 assert_eq!(a.add(§or(0, 3, 2, 0), &mut buf), None);
183 assert_eq!(a.add(§or(0, 3, 3, 0), &mut buf), None);
184 assert_eq!(a.add(§or(1, 3, 3, 0), &mut buf), None);
185 assert!(a.add(§or(2, 3, 3, 0), &mut buf).is_some());
186 assert_eq!(a.dropped, 1);
187 }
188
189 #[test]
190 fn ignores_non_video_sectors() {
191 let mut a = FrameAssembler::new();
192 let mut buf = [0u8; CHUNK_PAYLOAD_BYTES];
193 assert_eq!(a.add(&[0u8; 2048], &mut buf), None);
194 assert_eq!(Chunk::parse(§or(3, 3, 1, 0)), None);
195 }
196}