Fix all v4 audit findings (22 items)
HIGH: ISO writer multi-extent for >4GB, end-to-end MKV mux test MEDIUM: AACS cvalue bounds, UV offset, macOS discovery, VC-1 resolution from sequence header, HEVC profile flags from SPS, ISO CRC + reserve AVDP LOW: PS AC3 sub-header, CSS crack first-match break, TrackUID unique, AC3 no-sync empty return, --all for iso://, --min warning, dead code removed 320 tests, all passing.
This commit is contained in:
+1
-10
@@ -81,16 +81,7 @@ impl CodecParser for Ac3Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// If we found no syncword at all, emit the whole PES as a frame
|
||||
// (backwards-compatible with old behaviour).
|
||||
if frames.is_empty() {
|
||||
frames.push(Frame {
|
||||
pts_ns,
|
||||
keyframe: true,
|
||||
data: data.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
// If we found no syncword at all, return empty — the data is not valid AC3.
|
||||
frames
|
||||
}
|
||||
|
||||
|
||||
+18
-4
@@ -144,10 +144,24 @@ impl CodecParser for HevcParser {
|
||||
} else {
|
||||
record.push(0);
|
||||
}
|
||||
// general_profile_compatibility_flags (4 bytes)
|
||||
record.extend_from_slice(&[0, 0, 0, 0]);
|
||||
// general_constraint_indicator_flags (6 bytes)
|
||||
record.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
|
||||
// general_profile_compatibility_flags (4 bytes) — from SPS bytes 2..6
|
||||
if sps.len() > 5 {
|
||||
record.extend_from_slice(&sps[2..6]);
|
||||
} else {
|
||||
record.extend_from_slice(&[0, 0, 0, 0]);
|
||||
}
|
||||
// general_constraint_indicator_flags (6 bytes) — from SPS bytes 6..12
|
||||
if sps.len() > 11 {
|
||||
record.extend_from_slice(&sps[6..12]);
|
||||
} else {
|
||||
let avail = sps.len().saturating_sub(6).min(6);
|
||||
if avail > 0 {
|
||||
record.extend_from_slice(&sps[6..6 + avail]);
|
||||
record.extend_from_slice(&vec![0u8; 6 - avail]);
|
||||
} else {
|
||||
record.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
}
|
||||
// general_level_idc
|
||||
record.push(if sps.len() > 12 { sps[12] } else { 0 });
|
||||
// min_spatial_segmentation_idc (4 + 12 bits)
|
||||
|
||||
+53
-3
@@ -14,6 +14,8 @@ const SC_FRAME: u8 = 0x0D;
|
||||
pub struct Vc1Parser {
|
||||
seq_header: Option<Vec<u8>>,
|
||||
entry_point: Option<Vec<u8>>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl Default for Vc1Parser {
|
||||
@@ -27,6 +29,8 @@ impl Vc1Parser {
|
||||
Self {
|
||||
seq_header: None,
|
||||
entry_point: None,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +55,13 @@ impl CodecParser for Vc1Parser {
|
||||
match sc_type {
|
||||
SC_SEQUENCE_HEADER => {
|
||||
let end = find_next_sc(data, i + 4).unwrap_or(data.len());
|
||||
self.seq_header = Some(data[i..end].to_vec());
|
||||
let sh = &data[i..end];
|
||||
self.seq_header = Some(sh.to_vec());
|
||||
// Try to parse resolution from advanced profile sequence header
|
||||
if let Some((w, h)) = parse_vc1_resolution(sh) {
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
}
|
||||
has_seq_header = true;
|
||||
}
|
||||
SC_ENTRY_POINT => {
|
||||
@@ -102,8 +112,8 @@ impl CodecParser for Vc1Parser {
|
||||
|
||||
// BITMAPINFOHEADER (40 bytes, little-endian)
|
||||
cp.extend_from_slice(&header_size.to_le_bytes()); // biSize
|
||||
cp.extend_from_slice(&1920u32.to_le_bytes()); // biWidth (updated by player)
|
||||
cp.extend_from_slice(&1080u32.to_le_bytes()); // biHeight
|
||||
cp.extend_from_slice(&self.width.to_le_bytes()); // biWidth
|
||||
cp.extend_from_slice(&self.height.to_le_bytes()); // biHeight
|
||||
cp.extend_from_slice(&1u16.to_le_bytes()); // biPlanes
|
||||
cp.extend_from_slice(&24u16.to_le_bytes()); // biBitCount
|
||||
cp.extend_from_slice(b"WVC1"); // biCompression = "WVC1" FOURCC
|
||||
@@ -121,6 +131,46 @@ impl CodecParser for Vc1Parser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse width and height from a VC-1 advanced profile sequence header.
|
||||
/// The sequence header starts with 00 00 01 0F. After the start code:
|
||||
/// byte 0 bits 7-6: profile (3 = advanced)
|
||||
/// For advanced profile, the coded dimensions are encoded as 12-bit fields.
|
||||
fn parse_vc1_resolution(sh: &[u8]) -> Option<(u32, u32)> {
|
||||
// sh starts at the start code (00 00 01 0F ...)
|
||||
if sh.len() < 8 {
|
||||
return None;
|
||||
}
|
||||
let byte4 = sh[4]; // first byte after start code
|
||||
let profile = (byte4 >> 6) & 0x03;
|
||||
if profile != 3 {
|
||||
// Simple/Main profile: resolution not in sequence header
|
||||
return None;
|
||||
}
|
||||
// Advanced profile layout (bit-level starting from sh[4]):
|
||||
// profile(2) + level(3) + chroma_format(2) + quantizer_spec(3) +
|
||||
// postproc_flag(1) + max_coded_width(12) + max_coded_height(12) ...
|
||||
// Total bits before width: 2+3+2+3+1 = 11 bits
|
||||
// We need at least 11+12+12 = 35 bits = 5 bytes from sh[4..]
|
||||
if sh.len() < 9 {
|
||||
return None;
|
||||
}
|
||||
// Build a u64 from bytes 4..9 for easy bit extraction
|
||||
let mut bits: u64 = 0;
|
||||
for j in 0..5 {
|
||||
bits = (bits << 8) | sh[4 + j] as u64;
|
||||
}
|
||||
// bits has 40 bits. Skip first 11 bits, then read 12+12.
|
||||
let coded_width = ((bits >> (40 - 11 - 12)) & 0xFFF) as u32 + 1;
|
||||
let coded_height = ((bits >> (40 - 11 - 24)) & 0xFFF) as u32 + 1;
|
||||
let w = coded_width * 2;
|
||||
let h = coded_height * 2;
|
||||
if w > 0 && h > 0 && w <= 8192 && h <= 8192 {
|
||||
Some((w, h))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_next_sc(data: &[u8], from: usize) -> Option<usize> {
|
||||
(from..data.len().saturating_sub(2)).find(|&i| data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01)
|
||||
}
|
||||
|
||||
+62
-15
@@ -143,7 +143,11 @@ impl<W: Write + Seek> IsoWriter<W> {
|
||||
self.write_m2ts_icb(self.bytes_written)?;
|
||||
|
||||
// Seek to end and write reserve AVDP
|
||||
let reserve_sector = total_sectors;
|
||||
let reserve_sector = if total_sectors > 512 {
|
||||
total_sectors - 256
|
||||
} else {
|
||||
total_sectors.saturating_sub(1).max(AVDP_SECTOR + 1)
|
||||
};
|
||||
self.writer
|
||||
.seek(SeekFrom::Start(reserve_sector as u64 * SECTOR_SIZE))?;
|
||||
self.write_avdp()?;
|
||||
@@ -391,17 +395,27 @@ impl<W: Write + Seek> IsoWriter<W> {
|
||||
icb[27] = 5; // file type: regular file
|
||||
icb[56..64].copy_from_slice(&file_size.to_le_bytes());
|
||||
icb[208..212].copy_from_slice(&0u32.to_le_bytes());
|
||||
// Allocation: data starts at DATA_START in the physical partition
|
||||
// Allocation: data starts at DATA_START in the physical partition.
|
||||
// UDF short_ad is 30 bits for extent length (max 1 GB = 0x3FFFFFFF).
|
||||
// For files > 1 GB, write multiple short_ad entries of 1 GB each plus remainder.
|
||||
let data_offset = self.data_start_sector - PARTITION_START;
|
||||
// Cap allocation length at u32::MAX for files >4GB (UDF short_ad limitation)
|
||||
// TODO: long_ad support is needed for full BD ISO support (files >4GB)
|
||||
let ad_len = if file_size > u32::MAX as u64 {
|
||||
u32::MAX
|
||||
} else {
|
||||
file_size as u32
|
||||
};
|
||||
icb[216..220].copy_from_slice(&ad_len.to_le_bytes());
|
||||
icb[220..224].copy_from_slice(&data_offset.to_le_bytes());
|
||||
const MAX_EXTENT: u64 = 0x3FFF_FFFF; // 1 GB - 1 (30-bit max)
|
||||
let mut remaining = file_size;
|
||||
let mut ad_offset: usize = 216;
|
||||
let mut sector_pos = data_offset;
|
||||
while remaining > 0 && ad_offset + 8 <= SECTOR_SIZE as usize {
|
||||
let extent_len = if remaining > MAX_EXTENT {
|
||||
MAX_EXTENT
|
||||
} else {
|
||||
remaining
|
||||
};
|
||||
icb[ad_offset..ad_offset + 4].copy_from_slice(&(extent_len as u32).to_le_bytes());
|
||||
icb[ad_offset + 4..ad_offset + 8].copy_from_slice(§or_pos.to_le_bytes());
|
||||
ad_offset += 8; // each short_ad is 8 bytes
|
||||
let extent_sectors = ((extent_len + SECTOR_SIZE - 1) / SECTOR_SIZE) as u32;
|
||||
sector_pos += extent_sectors;
|
||||
remaining -= extent_len;
|
||||
}
|
||||
self.writer.write_all(&icb)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -414,13 +428,17 @@ fn write_descriptor_tag(buf: &mut [u8], tag_id: u16, sector: u32) {
|
||||
buf[0..2].copy_from_slice(&tag_id.to_le_bytes());
|
||||
// Descriptor version: 3 (UDF 2.50)
|
||||
buf[2..4].copy_from_slice(&3u16.to_le_bytes());
|
||||
// Descriptor CRC (simplified — set to 0, most implementations accept this)
|
||||
buf[8..10].copy_from_slice(&0u16.to_le_bytes());
|
||||
// Descriptor CRC length
|
||||
buf[10..12].copy_from_slice(&0u16.to_le_bytes());
|
||||
// Tag location
|
||||
buf[12..16].copy_from_slice(§or.to_le_bytes());
|
||||
// Compute CRC-CCITT over descriptor body (bytes 16+)
|
||||
let body = &buf[16..];
|
||||
let body_len = body.len();
|
||||
let crc = udf_crc(body);
|
||||
buf[8..10].copy_from_slice(&crc.to_le_bytes());
|
||||
// Descriptor CRC length
|
||||
buf[10..12].copy_from_slice(&(body_len as u16).to_le_bytes());
|
||||
// Compute tag checksum: sum of bytes 0-3, 5-15 mod 256
|
||||
buf[4] = 0; // clear before computing
|
||||
let checksum: u8 = buf[0..4]
|
||||
.iter()
|
||||
.chain(buf[5..16].iter())
|
||||
@@ -428,6 +446,35 @@ fn write_descriptor_tag(buf: &mut [u8], tag_id: u16, sector: u32) {
|
||||
buf[4] = checksum;
|
||||
}
|
||||
|
||||
/// UDF CRC-CCITT (CRC-16/ECMA-182 polynomial 0x11021).
|
||||
fn udf_crc(data: &[u8]) -> u16 {
|
||||
// CRC lookup table for polynomial 0x11021
|
||||
static CRC_TABLE: [u16; 256] = {
|
||||
let mut table = [0u16; 256];
|
||||
let mut i = 0;
|
||||
while i < 256 {
|
||||
let mut crc = (i as u16) << 8;
|
||||
let mut j = 0;
|
||||
while j < 8 {
|
||||
if crc & 0x8000 != 0 {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
table[i] = crc;
|
||||
i += 1;
|
||||
}
|
||||
table
|
||||
};
|
||||
let mut crc: u16 = 0;
|
||||
for &byte in data {
|
||||
crc = (crc << 8) ^ CRC_TABLE[((crc >> 8) as u8 ^ byte) as usize];
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
/// Write a UDF d-string (compressed unicode string with length prefix).
|
||||
fn write_dstring(buf: &mut [u8], s: &str) {
|
||||
let max = buf.len() - 1; // last byte is length
|
||||
|
||||
+1
-1
@@ -183,7 +183,7 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
for (i, track) in tracks.iter().enumerate() {
|
||||
let entry_pos = ebml::start_master(&mut writer, ebml::TRACK_ENTRY)?;
|
||||
ebml::write_uint(&mut writer, ebml::TRACK_NUMBER, (i + 1) as u64)?;
|
||||
ebml::write_uint(&mut writer, ebml::TRACK_UID, (i + 1) as u64)?;
|
||||
ebml::write_uint(&mut writer, ebml::TRACK_UID, (i + 1) as u64 | 0x1000000)?;
|
||||
ebml::write_uint(&mut writer, ebml::TRACK_TYPE, track.track_type)?;
|
||||
ebml::write_uint(&mut writer, ebml::FLAG_LACING, 0)?;
|
||||
ebml::write_string(&mut writer, ebml::CODEC_ID, track.codec_id)?;
|
||||
|
||||
+25
-7
@@ -228,9 +228,17 @@ fn parse_pes_packet(data: &[u8]) -> Option<PsPacket> {
|
||||
|
||||
let payload = &data[header_end..];
|
||||
|
||||
// For private stream 1, the first payload byte is the sub-stream ID.
|
||||
// For private stream 1, the first payload byte is the sub-stream ID,
|
||||
// followed by a sub-header whose length depends on the sub-stream type.
|
||||
let (sub_stream_id, es_data) = if stream_id == PRIVATE_STREAM_1 && !payload.is_empty() {
|
||||
(Some(payload[0]), payload[1..].to_vec())
|
||||
let sub_id = payload[0];
|
||||
let skip = match sub_id {
|
||||
0x80..=0x8F => 4, // AC3/DTS: sub_id + frame_count + access_unit_ptr(2)
|
||||
0xA0..=0xA7 => 7, // LPCM: sub_id + frames + ptr(2) + emphasis + quant_freq + channels
|
||||
_ => 1,
|
||||
};
|
||||
let start = skip.min(payload.len());
|
||||
(Some(sub_id), payload[start..].to_vec())
|
||||
} else {
|
||||
(None, payload.to_vec())
|
||||
};
|
||||
@@ -399,11 +407,13 @@ mod tests {
|
||||
fn private_stream_1_ac3_substream() {
|
||||
let mut demuxer = PsDemuxer::new();
|
||||
|
||||
// AC3 sub-header: sub_id(1) + frame_count(1) + access_unit_ptr(2) = 4 bytes
|
||||
let mut data = vec![
|
||||
0x00, 0x00, 0x01, 0xBD, // private stream 1
|
||||
0x00, 0x08, // length = 8
|
||||
0x00, 0x0B, // length = 11
|
||||
0x80, 0x00, 0x00, // no PTS, header_data_len=0
|
||||
0x80, // sub-stream ID: AC3 stream 0
|
||||
0x01, 0x00, 0x02, // frame_count + access_unit_ptr (sub-header bytes)
|
||||
0xAA, 0xBB, 0xCC, 0xDD, // AC3 payload
|
||||
];
|
||||
|
||||
@@ -420,9 +430,12 @@ mod tests {
|
||||
fn private_stream_1_dts_substream() {
|
||||
let mut demuxer = PsDemuxer::new();
|
||||
|
||||
// DTS sub-header: sub_id(1) + frame_count(1) + access_unit_ptr(2) = 4 bytes
|
||||
let mut data = vec![
|
||||
0x00, 0x00, 0x01, 0xBD, 0x00, 0x06, // length = 6
|
||||
0x80, 0x00, 0x00, 0x88, // sub-stream ID: DTS stream 0
|
||||
0x00, 0x00, 0x01, 0xBD, 0x00, 0x09, // length = 9
|
||||
0x80, 0x00, 0x00, // no PTS, header_data_len=0
|
||||
0x88, // sub-stream ID: DTS stream 0
|
||||
0x01, 0x00, 0x00, // sub-header (frame_count + access_unit_ptr)
|
||||
0x11, 0x22,
|
||||
];
|
||||
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
|
||||
@@ -430,6 +443,7 @@ mod tests {
|
||||
let packets = demuxer.feed(&data);
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_eq!(packets[0].sub_stream_id, Some(0x88));
|
||||
assert_eq!(packets[0].data, vec![0x11, 0x22]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -452,16 +466,20 @@ mod tests {
|
||||
fn private_stream_1_lpcm_substream() {
|
||||
let mut demuxer = PsDemuxer::new();
|
||||
|
||||
// LPCM sub-header: sub_id(1) + frames(1) + ptr(2) + emphasis(1) + quant_freq(1) + channels(1) = 7 bytes
|
||||
let mut data = vec![
|
||||
0x00, 0x00, 0x01, 0xBD, 0x00, 0x06, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xBD, 0x00, 0x0C, // length = 12
|
||||
0x80, 0x00, 0x00, // no PTS, header_data_len=0
|
||||
0xA0, // sub-stream ID: LPCM stream 0
|
||||
0x01, 0x02,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // LPCM sub-header (6 bytes after sub_id)
|
||||
0x01, 0x02, // LPCM payload
|
||||
];
|
||||
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
|
||||
|
||||
let packets = demuxer.feed(&data);
|
||||
assert_eq!(packets.len(), 1);
|
||||
assert_eq!(packets[0].sub_stream_id, Some(0xA0));
|
||||
assert_eq!(packets[0].data, vec![0x01, 0x02]);
|
||||
}
|
||||
|
||||
// --- Incremental feeding ---
|
||||
|
||||
Reference in New Issue
Block a user