1use std::{
10 io::{self, Read, Seek, SeekFrom, Write},
11 path::Path,
12};
13
14use crate::formats::{Bits, ByteInput as Input, prelude::*};
15
16use super::{cstr, huffman::Tree};
17
18pub const SUFFIX: &str = "cbv";
20
21pub type Range = core::range::Range<usize>;
22
23crate::finite_set!(
24 Kind,
26 Members {
28 Games as games,
29 Index as index,
30 Players as players,
31 Tournaments as tournaments,
32 Annotations as annotations,
33 Annotators as annotators,
34 Sources as sources,
35 Teams as teams,
36 }
37);
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub struct Info {
46 pub members: usize,
47 pub metadata_len: usize,
48}
49
50#[derive(Debug)]
52pub struct Header {
53 pub len: usize,
54 pub members: Vec<Member>,
55}
56
57impl Info {
58 pub const LEN: usize = 8;
59
60 pub fn header_len(self) -> usize {
61 self.members * self.metadata_len + Self::LEN
62 }
63}
64
65#[derive(Clone, Debug)]
67pub struct Member {
68 pub name: String,
73 pub len: usize,
75 pub packed: Range,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub struct Block {
82 pub packed: Range,
83 pub unpacked: Range,
84}
85
86pub struct Access<I> {
88 input: I,
89 members: Members<Option<State>>,
90 packed: Vec<u8>,
91}
92
93pub struct Reader<'a, I> {
95 input: &'a mut I,
96 state: &'a mut State,
97 packed: &'a mut Vec<u8>,
98 position: usize,
99}
100
101struct State {
102 member: Member,
103 blocks: Vec<Block>,
104 cached: Option<(usize, Vec<u8>)>,
105}
106
107#[derive(Debug, thiserror::Error)]
108pub enum Error {
109 #[error("invalid ChessBase archive")]
110 Data,
111 #[error(transparent)]
112 Io(#[from] io::Error),
113}
114
115impl From<FrameError<ContextMode>> for Error {
116 fn from(error: FrameError<ContextMode>) -> Self {
117 match error {
118 FrameError::Error(_) => Self::Data,
119 FrameError::Io(error) => Self::Io(error),
120 }
121 }
122}
123
124pub type Result<T, E = Error> = core::result::Result<T, E>;
125
126impl Framed for Info {
127 const LEN: usize = Info::LEN;
128
129 fn total(&self) -> usize {
130 self.header_len()
131 }
132}
133
134pub fn read_header(input: &mut impl Read) -> Result<Header> {
135 Ok(framed(input, &mut Vec::new(), info, members)?)
136}
137
138pub fn header(input: &mut Input<'_>) -> ModalResult<Header> {
152 let info = info.parse_next(input)?;
153 members(info).parse_next(input)
154}
155
156fn members(info: Info) -> impl FnMut(&mut Input<'_>) -> ModalResult<Header> {
157 move |input| {
158 let len = info.header_len();
159 let mut offset = len;
160 let mut members = Vec::with_capacity(info.members);
161 for _ in 0..info.members {
163 let member = member(offset, info.metadata_len).parse_next(input)?;
164 offset = member.packed.end;
165 members.push(member);
166 }
167 Ok(Header { len, members })
168 }
169}
170
171pub fn info(input: &mut Input<'_>) -> ModalResult<Info> {
172 let (members, metadata_len) =
173 delimited(magic_tag, (le_u16, u8), take(3u8)).parse_next(input)?;
174 Ok(Info { members: members as usize, metadata_len: metadata_len as usize })
175}
176
177fn member(offset: usize, metadata_len: usize) -> impl FnMut(&mut Input<'_>) -> ModalResult<Member> {
178 move |input: &mut Input<'_>| {
179 let metadata = take(metadata_len).and_then(metadata).parse_next(input)?;
180 let end = offset.checked_add(metadata.packed).ok_or_else(backtrack)?;
181 Ok(Member { name: metadata.name, len: metadata.len, packed: Range::from(offset..end) })
182 }
183}
184
185struct Metadata {
186 name: String,
187 len: usize,
188 packed: usize,
189}
190
191fn metadata(input: &mut Input<'_>) -> ModalResult<Metadata> {
192 terminated(
193 seq! {Metadata {
194 name: take(132usize).map(cstr),
195 packed: le_i32.map(as_usize),
196 len: le_i32.map(as_usize),
197 }},
198 take(1usize),
199 )
200 .parse_next(input)
201}
202
203pub fn unpack_to(member: &Member, input: &mut impl Read, output: &mut impl Write) -> Result<()> {
208 let mut input = input.take((member.packed.end - member.packed.start) as u64);
209 let mut packed = Vec::new();
210 let mut len = 0usize;
211 while input.limit() != 0 {
212 let block = framed(&mut input, &mut packed, block_info, unpack_block)?;
213 len = len.checked_add(block.len()).ok_or(Error::Data)?;
214 output.write_all(&block)?;
215 }
216 if len != member.len {
217 return Err(Error::Data);
218 }
219 Ok(())
220}
221
222#[derive(Clone, Copy, Debug)]
223struct BlockInfo {
224 len: usize,
225}
226
227impl Framed for BlockInfo {
228 const LEN: usize = 4;
229
230 fn total(&self) -> usize {
231 Self::LEN + self.len
232 }
233}
234
235#[derive(Clone, Copy, Debug)]
237pub struct Compression {
238 pub huffman: bool,
240 pub rle: bool,
242}
243
244fn block_info(input: &mut Input<'_>) -> ModalResult<BlockInfo> {
245 terminated(le_u16, take(2u8))
246 .verify(|len| *len > 0)
247 .map(|len| BlockInfo { len: usize::from(len) })
248 .parse_next(input)
249}
250
251fn unpack_block(info: BlockInfo) -> impl FnMut(&mut Input<'_>) -> ModalResult<Vec<u8>> {
252 move |input| {
253 let compression = compression.parse_next(input)?;
254 let mut block = take(info.len - 1).parse_next(input)?;
255
256 let block = if compression.huffman {
257 unpack_huffman(&mut block).expect("correct Huffman")
258 } else {
259 block.to_vec()
260 };
261
262 if compression.rle {
263 unpack_rle_backref(&mut block.as_slice()).map_err(ErrMode::Backtrack)
264 } else {
265 Ok(block)
266 }
267 }
268}
269
270fn compression(input: &mut Input<'_>) -> ModalResult<Compression> {
271 u8.verify_map(|compression| {
272 (compression < 4)
273 .then_some(Compression { rle: compression & 1 != 0, huffman: compression & 2 != 0 })
274 })
275 .parse_next(input)
276}
277
278pub fn unpack_huffman(input: &mut Input<'_>) -> winnow::Result<Vec<u8>> {
280 use core::array::from_fn;
281
282 let len = be_u16(input)? as usize;
284
285 let mut bits = Bits::from(*input);
287 let values: [(usize, u16); 256] = from_fn(|_| huffman_node(&mut bits).unwrap());
288
289 let tree = Tree::load(values);
291 let unpacked = tree.unpack(bits, len).unwrap();
292
293 assert_eq!(len, unpacked.len());
294 Ok(unpacked)
295}
296
297fn huffman_node(bits: &mut Bits) -> Option<(usize, u16)> {
298 let len = bits.unsigned(4)?;
299 let bits = bits.unsigned(len)? as u16;
300 Some((len, bits))
301}
302
303pub fn unpack_rle_backref(input: &mut Input<'_>) -> winnow::Result<Vec<u8>> {
305 use winnow::binary::{le_u16, u8};
306
307 let mut result = vec![];
308 while !input.is_empty() {
309 let indicator = le_u16(input)?.to_be_bytes();
310 let indicator = Bits::from(indicator.as_slice());
311
312 for encoded in indicator {
313 if input.is_empty() {
314 return Ok(result);
315 }
316
317 if !encoded {
318 result.push(u8(input)?);
319 continue;
320 }
321
322 let (high, low) = u8(input).map(nibbles)?;
323
324 match high {
325 0 => {
327 result.append(&mut vec![u8(input)?; 3 + low]);
328 }
329 1 => {
331 let size = 19 + low + ((u8(input)? as usize) << 4);
332 result.append(&mut vec![u8(input)?; size]);
333 }
334 _ => {
336 let start = result.len() - (3 + low + ((u8(input)? as usize) << 4));
338 let end = start
339 + if high > 2 {
340 high as usize
342 } else {
343 16 + u8(input)? as usize
345 };
346 result.extend_from_within(start..end);
347 }
348 }
349 }
350 }
351
352 Ok(result)
353}
354
355fn nibbles(x: u8) -> (u8, usize) {
356 (x >> 4, x as usize & 0xF)
357}
358
359fn magic_tag(input: &mut Input<'_>) -> ModalResult<[u8; 2]> {
364 const MAGIC_NUMBER: [u8; 2] = [0x08, 0x00];
365 MAGIC_NUMBER.as_slice().map(|_| MAGIC_NUMBER).parse_next(input)
366}
367
368fn as_usize(x: i32) -> usize {
369 x as usize
370}
371
372impl<I> Access<I> {
373 pub fn new(input: I, members: &[Member]) -> Result<Self> {
374 let mut states: Members<Option<State>> = Members::empty();
375 for member in members {
376 let Some(kind) = Kind::from_name(&member.name) else {
377 continue;
378 };
379 if states[kind].is_some() {
380 return Err(Error::Data);
381 }
382 states[kind] = Some(State { member: member.clone(), blocks: Vec::new(), cached: None });
383 }
384 Ok(Self { input, members: states, packed: Vec::new() })
385 }
386
387 pub fn reader(&mut self, kind: Kind) -> Option<Reader<'_, I>> {
388 let state = self.members[kind].as_mut()?;
389 Some(Reader { input: &mut self.input, state, packed: &mut self.packed, position: 0 })
390 }
391}
392
393impl Kind {
394 fn from_name(name: &str) -> Option<Self> {
395 Self::from_suffix(Path::new(name).extension()?.to_str()?)
396 }
397
398 fn from_suffix(suffix: &str) -> Option<Self> {
399 use Kind::*;
400
401 Some(match suffix.to_ascii_lowercase().as_str() {
402 "cbg" => Games,
403 "cbh" => Index,
404 "cbp" => Players,
405 "cbt" => Tournaments,
406 "cba" => Annotations,
407 "cbc" => Annotators,
408 "cbs" => Sources,
409 "cbe" => Teams,
410 _ => return None,
411 })
412 }
413}
414
415impl<I: Read + Seek> Reader<'_, I> {
416 fn locate(&mut self, position: usize) -> Result<(usize, Block)> {
417 if let Some((i, block)) = self
418 .state
419 .blocks
420 .iter()
421 .enumerate()
422 .find(|(_, block)| block.unpacked.contains(&position))
423 {
424 return Ok((i, *block));
425 }
426
427 loop {
428 let (packed_offset, unpacked_offset, index) =
429 if let Some(block) = self.state.blocks.last() {
430 (block.packed.end, block.unpacked.end, self.state.blocks.len())
431 } else {
432 (0, 0, 0)
433 };
434
435 if packed_offset >= self.state.member.packed.end - self.state.member.packed.start {
436 return Err(Error::Data);
437 }
438
439 let (block, unpacked) = self.discover(packed_offset, unpacked_offset)?;
440 self.state.blocks.push(block);
441 self.state.cached = Some((index, unpacked));
442 if block.unpacked.contains(&position) {
443 return Ok((index, block));
444 }
445 }
446 }
447
448 fn discover(
449 &mut self,
450 packed_offset: usize,
451 unpacked_offset: usize,
452 ) -> Result<(Block, Vec<u8>)> {
453 let absolute =
454 self.state.member.packed.start.checked_add(packed_offset).ok_or(Error::Data)?;
455 self.input.seek(SeekFrom::Start(absolute as u64))?;
456 let remaining = (self.state.member.packed.end - self.state.member.packed.start)
457 .checked_sub(packed_offset)
458 .ok_or(Error::Data)?;
459 let mut input = self.input.by_ref().take(remaining as u64);
460 let data = framed(&mut input, self.packed, block_info, unpack_block)?;
461 let packed = Range {
462 start: packed_offset,
463 end: packed_offset.checked_add(self.packed.len()).ok_or(Error::Data)?,
464 };
465 let unpacked = Range {
466 start: unpacked_offset,
467 end: unpacked_offset.checked_add(data.len()).ok_or(Error::Data)?,
468 };
469
470 Ok((Block { packed, unpacked }, data))
471 }
472
473 fn load(&mut self, index: usize, block: Block) -> Result<()> {
474 if self.state.cached.as_ref().map(|(cached, _)| *cached) == Some(index) {
475 return Ok(());
476 }
477
478 let (loaded, unpacked) = self.discover(block.packed.start, block.unpacked.start)?;
479 if loaded != block {
480 return Err(Error::Data);
481 }
482 self.state.cached = Some((index, unpacked));
483 Ok(())
484 }
485}
486
487impl<I: Read + Seek> Read for Reader<'_, I> {
488 fn read(&mut self, mut output: &mut [u8]) -> io::Result<usize> {
489 let start_len = output.len();
490 while !output.is_empty() && self.position < self.state.member.len {
491 let (index, block) = self.locate(self.position)?;
492 self.load(index, block)?;
493 let offset = self.position - block.unpacked.start;
494 let available = block.unpacked.end - self.position;
495 let len = available.min(output.len());
496 let unpacked = &self.state.cached.as_ref().ok_or_else(invalid_data)?.1;
497 output[..len].copy_from_slice(&unpacked[offset..offset + len]);
498 self.position += len;
499 output = &mut output[len..];
500 }
501 Ok(start_len - output.len())
502 }
503}
504
505impl<I> Seek for Reader<'_, I> {
506 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
507 use SeekFrom::*;
508
509 let base = match position {
510 Start(position) => {
511 self.position = usize::try_from(position).map_err(|_| invalid_input())?;
512 return Ok(position);
513 }
514 Current(_) => self.position,
515 End(_) => self.state.member.len,
516 };
517 let offset = match position {
518 Current(offset) | End(offset) => offset,
519 Start(_) => unreachable!(),
520 };
521 let position = (base as i128).checked_add(i128::from(offset)).ok_or_else(invalid_input)?;
522 self.position = usize::try_from(position).map_err(|_| invalid_input())?;
523 Ok(self.position as u64)
524 }
525}
526
527impl From<Error> for io::Error {
528 fn from(error: Error) -> io::Error {
529 match error {
530 Error::Data => io::Error::new(io::ErrorKind::InvalidData, error),
531 Error::Io(error) => error,
532 }
533 }
534}
535
536fn invalid_input() -> io::Error {
537 io::Error::new(io::ErrorKind::InvalidInput, "invalid ChessBase archive seek")
538}
539
540fn invalid_data() -> io::Error {
541 io::Error::new(io::ErrorKind::InvalidData, Error::Data)
542}
543
544#[cfg(test)]
545mod tests {
546 use hex_literal::hex;
547
548 use crate::formats::twic;
549
550 use super::*;
551
552 #[test]
553 fn unpack_to_rle() {
554 const INPUT: [u8; 38] = hex!(
555 "22004d39013740010400d20296494806000001ff410104040005ff1e62000008
556 6b1800004665"
557 );
558 const PREFIX: [u8; 40] = hex!(
559 "0100000000000000d202964948060000ffffffff010000000400000000000000
560 ffffffffffffffff"
561 );
562 const SUFFIX: [u8; 8] = hex!("6b18000001000000");
563
564 let member = Member {
565 name: "twic1616.cbl".to_string(),
566 len: 1649,
567 packed: Range::from(0..INPUT.len()),
568 };
569 let mut input = INPUT.as_slice();
570 let mut output = Vec::new();
571 super::unpack_to(&member, &mut input, &mut output).unwrap();
572
573 let mut expected = vec![0; member.len];
574 expected[..PREFIX.len()].copy_from_slice(&PREFIX);
575 expected[member.len - SUFFIX.len()..].copy_from_slice(&SUFFIX);
576 assert_eq!(output, expected);
577 }
578
579 #[test]
580 fn unpack_to_huffman_rle() {
581 const INPUT: [u8; 295] = hex!(
582 "23014bce03013d2190085a756ebe120f0000000428429001b0084684984a3a10
583 8a10a210aa007000000002138000000002130000000002134211c00000000000
584 21600000000000000000000002170000000021740000000000000000216c0000
585 0000000000000002164000000000000000000000000000000000212c00000000
586 000000000000000000000211000000213c0000214c0000000000215000000000
587 000000fab475e0b28027202e2e8a6ad685b554d4558504f4c41243612cc64149
588 18444a4825ea2bcfe5ef12f7897bc4bde25ef12f7897bc4bde3f97bc4bde25ef
589 12f7897bc4bde25ef12f78fe5ef12f7897bc4bde25ef12f7897bc4bde3f97bc4
590 bde25ef12f7897bc4bde25ef12f78fe5ef12f7897bc4bde25ef12f7897bc4bde
591 05497bc4beaf00"
592 );
593
594 let member = Member {
595 name: "twic1616.cbtt".to_string(),
596 len: 17_852,
597 packed: Range::from(0..INPUT.len()),
598 };
599 let mut input = INPUT.as_slice();
600 let mut output = Vec::new();
601 super::unpack_to(&member, &mut input, &mut output).unwrap();
602
603 assert_eq!(output.len(), member.len);
604 assert_eq!(crc32(&output), 0xb007_79cb);
605 }
606
607 fn crc32(input: &[u8]) -> u32 {
608 let mut crc = u32::MAX;
609 for byte in input {
610 crc ^= u32::from(*byte);
611 for _ in 0..8 {
612 crc = (crc >> 1) ^ (0xedb8_8320 & 0u32.wrapping_sub(crc & 1));
613 }
614 }
615 !crc
616 }
617
618 #[test]
619 #[ignore = "exhaustive test against the external TWIC fixture"]
620 fn twic_unpack_to() {
621 let data = twic::data(1616, "cbv");
622 let mut input = data.as_slice();
623 let header = read_header(&mut input).unwrap();
624 for member in &header.members {
625 let mut output = Vec::new();
626 super::unpack_to(member, &mut input, &mut output).unwrap();
627 }
628 println!("{header:?}");
629 }
630}