use crate::error::{Error, Result};
use crate::util::{u16le, u32le};
const MAX_PIECES: usize = 1 << 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Piece {
pub cch: usize,
pub fc: usize,
pub compressed: bool,
}
pub(crate) fn parse(clx: &[u8]) -> Result<Vec<Piece>> {
let mut pos = 0usize;
loop {
let marker = *clx
.get(pos)
.ok_or_else(|| Error::PieceTable("CLX truncated before Pcdt".into()))?;
match marker {
0x01 => {
let cb = u16le(clx, pos + 1)
.ok_or_else(|| Error::PieceTable("truncated Prc".into()))?
as usize;
pos = pos
.checked_add(3 + cb)
.ok_or_else(|| Error::PieceTable("Prc length overflow".into()))?;
}
0x02 => {
let lcb = u32le(clx, pos + 1)
.ok_or_else(|| Error::PieceTable("truncated Pcdt".into()))?
as usize;
let start = pos + 5;
let plc = clx
.get(start..start.saturating_add(lcb))
.ok_or_else(|| Error::PieceTable("PlcPcd out of CLX bounds".into()))?;
return parse_plcpcd(plc);
}
other => {
return Err(Error::PieceTable(format!(
"unexpected CLX marker 0x{other:02x}"
)))
}
}
}
}
fn parse_plcpcd(plc: &[u8]) -> Result<Vec<Piece>> {
if plc.len() < 4 || (plc.len() - 4) % 12 != 0 {
return Err(Error::PieceTable(format!(
"bad PlcPcd length {}",
plc.len()
)));
}
let n = ((plc.len() - 4) / 12).min(MAX_PIECES);
let mut pieces = Vec::with_capacity(n);
for i in 0..n {
let cp0 = u32le(plc, i * 4).unwrap_or(0) as i64;
let cp1 = u32le(plc, (i + 1) * 4).unwrap_or(0) as i64;
let cch = (cp1 - cp0).max(0) as usize;
let pcd_off = (n + 1) * 4 + i * 8;
let fc_compressed =
u32le(plc, pcd_off + 2).ok_or_else(|| Error::PieceTable("truncated PCD".into()))?;
let compressed = (fc_compressed & 0x4000_0000) != 0;
let fc30 = (fc_compressed & 0x3FFF_FFFF) as usize;
let fc = if compressed { fc30 / 2 } else { fc30 };
pieces.push(Piece {
cch,
fc,
compressed,
});
}
Ok(pieces)
}
#[cfg(test)]
mod tests {
use super::*;
fn one_piece_pcdt() -> Vec<u8> {
let mut plc = Vec::new();
plc.extend_from_slice(&0u32.to_le_bytes());
plc.extend_from_slice(&5u32.to_le_bytes());
plc.extend_from_slice(&0u16.to_le_bytes()); plc.extend_from_slice(&0x100u32.to_le_bytes()); plc.extend_from_slice(&0u16.to_le_bytes()); let mut clx = vec![0x02u8];
clx.extend_from_slice(&(plc.len() as u32).to_le_bytes());
clx.extend_from_slice(&plc);
clx
}
#[test]
fn parses_single_uncompressed_piece() {
let pieces = parse(&one_piece_pcdt()).unwrap();
assert_eq!(
pieces,
vec![Piece {
cch: 5,
fc: 0x100,
compressed: false
}]
);
}
#[test]
fn skips_leading_prc_block() {
let mut clx = vec![0x01u8, 0x02, 0x00, 0xAA, 0xBB];
clx.extend_from_slice(&one_piece_pcdt());
let pieces = parse(&clx).unwrap();
assert_eq!(pieces.len(), 1);
assert_eq!(pieces[0].fc, 0x100);
}
#[test]
fn rejects_unknown_marker() {
assert!(parse(&[0x99]).is_err());
}
}