diff --git a/src/lib_ccx/cc_bitstream.c b/src/lib_ccx/cc_bitstream.c index f43085341..da2cf10de 100644 --- a/src/lib_ccx/cc_bitstream.c +++ b/src/lib_ccx/cc_bitstream.c @@ -3,6 +3,21 @@ // Hold functions to read streams on a bit or byte oriented basis // plus some data related helper functions. +#ifndef DISABLE_RUST +extern uint64_t ccxr_next_bits(struct bitstream *bs, uint32_t bnum); +extern uint64_t ccxr_read_bits(struct bitstream *bs, uint32_t bnum); +extern int ccxr_skip_bits(struct bitstream *bs, uint32_t bnum); +extern int ccxr_is_byte_aligned(struct bitstream *bs); +extern void ccxr_make_byte_aligned(struct bitstream *bs); +extern const uint8_t *ccxr_next_bytes(struct bitstream *bs, size_t bynum); +extern const uint8_t *ccxr_read_bytes(struct bitstream *bs, size_t bynum); +extern uint64_t ccxr_read_exp_golomb_unsigned(struct bitstream *bs); +extern int64_t ccxr_read_exp_golomb(struct bitstream *bs); +extern uint8_t ccxr_reverse8(uint8_t data); +extern uint64_t ccxr_bitstream_get_num(struct bitstream *bs, unsigned bytes, int advance); +extern int64_t ccxr_read_int(struct bitstream *bs, unsigned bnum); +#endif + // Guidelines for all bitsream functions: // * No function shall advance the pointer past the end marker // * If bitstream.bitsleft < 0 do not attempt any read access, @@ -35,6 +50,9 @@ int init_bitstream(struct bitstream *bstr, unsigned char *start, unsigned char * // there are not enough bits left in the bitstream. uint64_t next_bits(struct bitstream *bstr, unsigned bnum) { +#ifndef DISABLE_RUST + return ccxr_next_bits(bstr, bnum); +#else uint64_t res = 0; if (bnum > 64) @@ -99,12 +117,16 @@ uint64_t next_bits(struct bitstream *bstr, unsigned bnum) bstr->_i_pos = vpos; return res; +#endif } // Read bnum bits from bitstream bstr with the most significant // bit read first. A 64 bit unsigned integer is returned. uint64_t read_bits(struct bitstream *bstr, unsigned bnum) { +#ifndef DISABLE_RUST + return ccxr_read_bits(bstr, bnum); +#else uint64_t res = next_bits(bstr, bnum); // Special case for reading zero bits. Also abort when not enough @@ -117,6 +139,7 @@ uint64_t read_bits(struct bitstream *bstr, unsigned bnum) bstr->pos = bstr->_i_pos; return res; +#endif } // This function will advance the bitstream by bnum bits, if possible. @@ -124,6 +147,9 @@ uint64_t read_bits(struct bitstream *bstr, unsigned bnum) // Return TRUE when successful, otherwise FALSE int skip_bits(struct bitstream *bstr, unsigned bnum) { +#ifndef DISABLE_RUST + return ccxr_skip_bits(bstr, bnum); +#else // Sanity check if (bstr->end - bstr->pos < 0) fatal(CCX_COMMON_EXIT_BUG_BUG, "In skip_bits: bitstream length cannot be negative!"); @@ -153,6 +179,7 @@ int skip_bits(struct bitstream *bstr, unsigned bnum) bstr->pos += 1; } return 1; +#endif } // Return TRUE if the current position in the bitstream is on a byte @@ -160,6 +187,9 @@ int skip_bits(struct bitstream *bstr, unsigned bnum) // a byte, otherwise return FALSE int is_byte_aligned(struct bitstream *bstr) { +#ifndef DISABLE_RUST + return ccxr_is_byte_aligned(bstr); +#else // Sanity check if (bstr->end - bstr->pos < 0) fatal(CCX_COMMON_EXIT_BUG_BUG, "In is_byte_aligned: bitstream length can not be negative!"); @@ -175,11 +205,15 @@ int is_byte_aligned(struct bitstream *bstr) return 1; else return 0; +#endif } // Move bitstream to next byte border. Adjust bitsleft. void make_byte_aligned(struct bitstream *bstr) { +#ifndef DISABLE_RUST + ccxr_make_byte_aligned(bstr); +#else // Sanity check if (bstr->end - bstr->pos < 0) fatal(CCX_COMMON_EXIT_BUG_BUG, "In make_byte_aligned: bitstream length can not be negative!"); @@ -208,6 +242,7 @@ void make_byte_aligned(struct bitstream *bstr) bstr->bitsleft = 0LL + 8 * (bstr->end - bstr->pos - 1) + bstr->bpos; return; +#endif } // Return pointer to first of bynum bytes from the bitstream if the @@ -217,6 +252,9 @@ void make_byte_aligned(struct bitstream *bstr) // This function does not advance the bitstream pointer. unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum) { +#ifndef DISABLE_RUST + return (unsigned char *)ccxr_next_bytes(bstr, bynum); +#else // Sanity check if (bstr->end - bstr->pos < 0) fatal(CCX_COMMON_EXIT_BUG_BUG, "In next_bytes: bitstream length can not be negative!"); @@ -238,6 +276,7 @@ unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum) bstr->_i_pos = bstr->pos + bynum; return bstr->pos; +#endif } // Return pointer to first of bynum bytes from the bitstream if the @@ -247,6 +286,9 @@ unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum) // This function does advance the bitstream pointer. unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum) { +#ifndef DISABLE_RUST + return (unsigned char *)ccxr_read_bytes(bstr, bynum); +#else unsigned char *res = next_bytes(bstr, bynum); // Advance the bitstream when a read was possible @@ -256,6 +298,7 @@ unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum) bstr->pos = bstr->_i_pos; } return res; +#endif } // Return an integer number with "bytes" precision from the current @@ -266,6 +309,9 @@ unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum) // little-endian and big-endian CPUs. uint64_t bitstream_get_num(struct bitstream *bstr, unsigned bytes, int advance) { +#ifndef DISABLE_RUST + return ccxr_bitstream_get_num(bstr, bytes, advance); +#else void *bpos; uint64_t rval = 0; @@ -296,11 +342,15 @@ uint64_t bitstream_get_num(struct bitstream *bstr, unsigned bytes, int advance) rval = (rval << 8) + uc; } return rval; +#endif } // Read unsigned Exp-Golomb code from bitstream uint64_t read_exp_golomb_unsigned(struct bitstream *bstr) { +#ifndef DISABLE_RUST + return ccxr_read_exp_golomb_unsigned(bstr); +#else uint64_t res = 0; int zeros = 0; @@ -310,11 +360,15 @@ uint64_t read_exp_golomb_unsigned(struct bitstream *bstr) res = (0x01 << zeros) - 1 + read_bits(bstr, zeros); return res; +#endif } // Read signed Exp-Golomb code from bitstream int64_t read_exp_golomb(struct bitstream *bstr) { +#ifndef DISABLE_RUST + return ccxr_read_exp_golomb(bstr); +#else int64_t res = 0; res = read_exp_golomb_unsigned(bstr); @@ -325,6 +379,7 @@ int64_t read_exp_golomb(struct bitstream *bstr) res = (res / 2 + (res % 2 ? 1 : 0)) * (res % 2 ? 1 : -1); return res; +#endif } // Read unsigned integer with bnum bits length. Basically an @@ -337,6 +392,9 @@ uint64_t read_int_unsigned(struct bitstream *bstr, unsigned bnum) // Read signed integer with bnum bits length. int64_t read_int(struct bitstream *bstr, unsigned bnum) { +#ifndef DISABLE_RUST + return ccxr_read_int(bstr, bnum); +#else uint64_t res = read_bits(bstr, bnum); // Special case for reading zero bits. Return zero @@ -344,11 +402,15 @@ int64_t read_int(struct bitstream *bstr, unsigned bnum) return 0; return (0xFFFFFFFFFFFFFFFFULL << bnum) | res; +#endif } // Return the value with the bit order reversed. uint8_t reverse8(uint8_t data) { +#ifndef DISABLE_RUST + return ccxr_reverse8(data); +#else uint8_t res = 0; for (int k = 0; k < 8; k++) @@ -358,4 +420,5 @@ uint8_t reverse8(uint8_t data) } return res; +#endif } diff --git a/src/lib_ccx/cc_bitstream.h b/src/lib_ccx/cc_bitstream.h index 8b5ea697f..3dd6b2826 100644 --- a/src/lib_ccx/cc_bitstream.h +++ b/src/lib_ccx/cc_bitstream.h @@ -1,7 +1,6 @@ #ifndef _BITSTREAM_ #define _BITSTREAM_ - // The structure holds the current position in the bitstream. // pos points to the current byte position and bpos counts the // bits left unread at the current byte pos. No bit read means diff --git a/src/rust/build.rs b/src/rust/build.rs index 85c401744..482694e86 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -27,6 +27,7 @@ fn main() { ".*(?i)_?dtvcc_.*", "encoder_ctx", "lib_cc_decode", + "bitstream", "cc_subtitle", "ccx_output_format", "ccx_boundary_time", diff --git a/src/rust/lib_ccxr/src/common/bitstream.rs b/src/rust/lib_ccxr/src/common/bitstream.rs new file mode 100644 index 000000000..6e5c7cc59 --- /dev/null +++ b/src/rust/lib_ccxr/src/common/bitstream.rs @@ -0,0 +1,487 @@ +use crate::fatal; +use crate::util::log::ExitCause; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum BitstreamError { + #[error("Bitstream has negative length")] + NegativeLength, + #[error("Illegal bit position value: {0}")] + IllegalBitPosition(u8), + #[error("Argument is greater than maximum bit number (64): {0}")] + TooManyBits(u32), + #[error("Data is insufficient for operation")] + InsufficientData, +} + +pub struct BitStreamRust<'a> { + pub data: &'a [u8], + pub pos: usize, + pub bpos: u8, + pub bits_left: i64, + pub error: bool, + pub _i_pos: usize, + pub _i_bpos: u8, +} + +impl<'a> BitStreamRust<'a> { + /// Create a new bitstream. Empty data is allowed (bits_left = 0). + pub fn new(data: &'a [u8]) -> Result { + if data.is_empty() { + return Err(BitstreamError::NegativeLength); + } + + Ok(Self { + data, + pos: 0, + bpos: 8, + bits_left: (data.len() as i64) * 8, + error: false, + _i_pos: 0, + _i_bpos: 0, + }) + } + + /// Peek at next `bnum` bits without advancing. MSB first. + pub fn next_bits(&mut self, bnum: u32) -> Result { + if bnum > 64 { + fatal!(cause = ExitCause::Bug; "In next_bits: Argument is greater than the maximum bit number i.e. 64: {}!", bnum); + } + + // Sanity check - equivalent to (bstr->end - bstr->pos < 0) + // In Rust: data.len() is equivalent to end, pos is current position + if self.pos > self.data.len() { + fatal!(cause = ExitCause::Bug; "In next_bits: Bitstream can not have negative length!"); + } + + // Keep a negative bitstream.bitsleft, but correct it. + if self.bits_left <= 0 { + self.bits_left -= bnum as i64; + return Ok(0); + } + + // Calculate the remaining number of bits in bitstream after reading. + // C: bstr->bitsleft = 0LL + (bstr->end - bstr->pos - 1) * 8 + bstr->bpos - bnum; + self.bits_left = + (self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64 - bnum as i64; + if self.bits_left < 0 { + return Ok(0); + } + + // Special case for reading zero bits. Return zero + if bnum == 0 { + return Ok(0); + } + + let mut vbit = self.bpos as i32; + let mut vpos = self.pos; + let mut res = 0u64; + let mut remaining_bits = bnum; + + if !(1..=8).contains(&vbit) { + fatal!(cause = ExitCause::Bug; "In next_bits: Illegal bit position value {}!", vbit); + } + + loop { + if vpos >= self.data.len() { + // We should not get here ... + fatal!(cause = ExitCause::Bug; "In next_bits: Trying to read after end of data ..."); + } + + // C: res |= (*vpos & (0x01 << (vbit - 1)) ? 1 : 0); + res |= if self.data[vpos] & (0x01 << (vbit - 1)) != 0 { + 1 + } else { + 0 + }; + vbit -= 1; + remaining_bits -= 1; + + if vbit == 0 { + vpos += 1; + vbit = 8; + } + + if remaining_bits != 0 { + res <<= 1; + } else { + break; + } + } + + // Remember the bitstream position + self._i_bpos = vbit as u8; + self._i_pos = vpos; + + Ok(res) + } + /// Read and commit `bnum` bits. On underflow or zero, returns 0. + pub fn read_bits(&mut self, bnum: u32) -> Result { + let res = self.next_bits(bnum)?; + + if bnum == 0 || self.bits_left < 0 { + return Ok(0); + } + + self.bpos = self._i_bpos; + self.pos = self._i_pos; + + Ok(res) + } + /// Skip `bnum` bits, advancing the position. + pub fn skip_bits(&mut self, bnum: u32) -> Result { + // Sanity check - equivalent to (bstr->end - bstr->pos < 0) + // In Rust: data.len() is equivalent to end, pos is current position + if self.pos > self.data.len() { + fatal!(cause = ExitCause::Bug; "In skip_bits: bitstream length cannot be negative!"); + } + + // Keep a negative bitstream.bitsleft, but correct it. + if self.bits_left < 0 { + self.bits_left -= bnum as i64; + return Ok(false); + } + + // Calculate the remaining number of bits in bitstream after reading. + // C: bstr->bitsleft = 0LL + (bstr->end - bstr->pos - 1) * 8 + bstr->bpos - bnum; + self.bits_left = + (self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64 - bnum as i64; + if self.bits_left < 0 { + return Ok(false); + } + + // Special case for reading zero bits. Return true + if bnum == 0 { + return Ok(true); + } + + // Handle the bit position arithmetic more carefully + // C: bstr->bpos -= bnum % 8; + // C: bstr->pos += bnum / 8; + let mut new_bpos = self.bpos as i32 - (bnum % 8) as i32; + let mut new_pos = self.pos + (bnum / 8) as usize; + + // C: if (bstr->bpos < 1) { bstr->bpos += 8; bstr->pos += 1; } + if new_bpos < 1 { + new_bpos += 8; + new_pos += 1; + } + + self.bpos = new_bpos as u8; + self.pos = new_pos; + + Ok(true) + } + + /// Check alignment: true if on next-byte boundary. + pub fn is_byte_aligned(&self) -> Result { + if self.pos > self.data.len() { + fatal!(cause = ExitCause::Bug; "In is_byte_aligned: bitstream length can not be negative!"); + } + let vbit = self.bpos as i32; + if vbit == 0 || vbit > 8 { + fatal!(cause = ExitCause::Bug; "In is_byte_aligned: Illegal bit position value {}!", vbit); + } + if vbit == 8 { + Ok(true) + } else { + Ok(false) + } + } + + /// Align to next byte boundary (commit state). + pub fn make_byte_aligned(&mut self) -> Result<(), BitstreamError> { + // Sanity check - equivalent to (bstr->end - bstr->pos < 0) + // In Rust: data.len() is equivalent to end, pos is current position + if self.pos > self.data.len() { + fatal!(cause = ExitCause::Bug; "In make_byte_aligned: bitstream length can not be negative!"); + } + + let vbit = self.bpos as i32; + + if vbit == 0 || vbit > 8 { + fatal!(cause = ExitCause::Bug; "In make_byte_aligned: Illegal bit position value {}!", vbit); + } + + // Keep a negative bstr->bitsleft, but correct it. + if self.bits_left < 0 { + // Pay attention to the bit alignment + // C: bstr->bitsleft = (bstr->bitsleft - 7) / 8 * 8; + self.bits_left = (self.bits_left - 7) / 8 * 8; + return Ok(()); + } + + if self.bpos != 8 { + self.bpos = 8; + self.pos += 1; + } + + // Reset, in case a next_???() function was used before + // C: bstr->bitsleft = 0LL + 8 * (bstr->end - bstr->pos - 1) + bstr->bpos; + self.bits_left = 8 * (self.data.len() as i64 - self.pos as i64 - 1) + self.bpos as i64; + + Ok(()) + } + + /// Peek at next `bynum` bytes without advancing. + /// Errors if not aligned or insufficient data. + pub fn next_bytes(&mut self, bynum: usize) -> Result<&'a [u8], BitstreamError> { + // Sanity check - equivalent to (bstr->end - bstr->pos < 0) + // In Rust: data.len() is equivalent to end, pos is current position + if self.pos > self.data.len() { + fatal!(cause = ExitCause::Bug; "In next_bytes: bitstream length can not be negative!"); + } + + // Keep a negative bstr->bitsleft, but correct it. + if self.bits_left < 0 { + self.bits_left -= (bynum * 8) as i64; + return Err(BitstreamError::InsufficientData); + } + + // C: bstr->bitsleft = 0LL + (bstr->end - bstr->pos - 1) * 8 + bstr->bpos - bynum * 8; + self.bits_left = (self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64 + - (bynum * 8) as i64; + + // C: if (!is_byte_aligned(bstr) || bstr->bitsleft < 0 || bynum < 1) + if !self.is_byte_aligned()? || self.bits_left < 0 || bynum < 1 { + return Err(BitstreamError::InsufficientData); + } + + // Remember the bitstream position + // C: bstr->_i_bpos = 8; + // C: bstr->_i_pos = bstr->pos + bynum; + self._i_bpos = 8; + self._i_pos = self.pos + bynum; + + // Return slice of the requested bytes + // C: return bstr->pos; + if self.pos + bynum <= self.data.len() { + Ok(&self.data[self.pos..self.pos + bynum]) + } else { + Err(BitstreamError::InsufficientData) + } + } + /// Read and commit `bynum` bytes. + pub fn read_bytes(&mut self, bynum: usize) -> Result<&'a [u8], BitstreamError> { + let res = self.next_bytes(bynum)?; + + // Advance the bitstream when a read was possible + // C: if (res) { bstr->bpos = bstr->_i_bpos; bstr->pos = bstr->_i_pos; } + self.bpos = self._i_bpos; + self.pos = self._i_pos; + + Ok(res) + } + /// Return an integer number with "bytes" precision from the current bitstream position. + /// Allowed "bytes" values are 1,2,4,8. + /// This function advances the bitstream pointer when "advance" is true. + /// Numbers come MSB (most significant first). + pub fn bitstream_get_num( + &mut self, + bytes: usize, + advance: bool, + ) -> Result { + let bpos = if advance { + self.read_bytes(bytes)? + } else { + self.next_bytes(bytes)? + }; + + match bytes { + 1 | 2 | 4 | 8 => {} + _ => { + fatal!(cause = ExitCause::Bug; "In bitstream_get_num: Illegal precision value [{}]!", bytes); + } + } + + let mut rval = 0u64; + for i in 0..bytes { + // Read backwards - C: unsigned char *ucpos = ((unsigned char *)bpos) + bytes - i - 1; + let uc = bpos[bytes - i - 1]; + rval = (rval << 8) + uc as u64; + } + + Ok(rval) + } + + /// Read unsigned Exp-Golomb code from bitstream + pub fn read_exp_golomb_unsigned(&mut self) -> Result { + let mut zeros = 0; + + // Count leading zeros + while self.read_bits(1)? == 0 && self.bits_left >= 0 { + zeros += 1; + } + + // Read the remaining bits + let remaining_bits = self.read_bits(zeros)?; + let res = ((1u64 << zeros) - 1) + remaining_bits; + + Ok(res) + } + + /// Read signed Exp-Golomb code from bitstream + pub fn read_exp_golomb(&mut self) -> Result { + let res = self.read_exp_golomb_unsigned()? as i64; + + // The following function might truncate when res+1 overflows + // res = (res+1)/2 * (res % 2 ? 1 : -1); + // Use this: + // C: res = (res / 2 + (res % 2 ? 1 : 0)) * (res % 2 ? 1 : -1); + let result = + (res / 2 + if res % 2 != 0 { 1 } else { 0 }) * if res % 2 != 0 { 1 } else { -1 }; + + Ok(result) + } + /// Read signed integer with bnum bits length. + pub fn read_int(&mut self, bnum: u32) -> Result { + let res = self.read_bits(bnum)?; + + // Special case for reading zero bits. Return zero + if bnum == 0 { + return Ok(0); + } + + // C: return (0xFFFFFFFFFFFFFFFFULL << bnum) | res; + // Sign extend by filling upper bits with 1s + let result = (0xFFFFFFFFFFFFFFFFu64 << bnum) | res; + + Ok(result as i64) + } + + /// Return the value with the bit order reversed. + pub fn reverse8(data: u8) -> u8 { + let mut res = 0u8; + + for k in 0..8 { + res <<= 1; + res |= if data & (0x01 << k) != 0 { 1 } else { 0 }; + } + + res + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bitstream_creation() { + let data = vec![0xFF, 0x00]; + let bs = BitStreamRust::new(&data); + assert!(bs.is_ok()); + } + + #[test] + fn test_read_bits() { + let data = vec![0b10101010]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + assert_eq!(bs.read_bits(1).unwrap(), 1); + assert_eq!(bs.read_bits(1).unwrap(), 0); + assert_eq!(bs.read_bits(1).unwrap(), 1); + } + + #[test] + fn test_byte_alignment() { + let data = vec![0xFF]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + assert!(bs.is_byte_aligned().unwrap()); + bs.read_bits(1).unwrap(); + assert!(!bs.is_byte_aligned().unwrap()); + bs.make_byte_aligned().unwrap(); + assert!(bs.is_byte_aligned().unwrap()); + } + + #[test] + fn test_multi_bit_reads() { + // Test data: 0xFF, 0x00 = 11111111 00000000 + let data = [0xFF, 0x00]; + let mut bs = BitStreamRust::new(&data).unwrap(); + assert_eq!(bs.next_bits(4).unwrap(), 0xF); // 1111 + assert_eq!(bs.next_bits(4).unwrap(), 0xF); // 1111 + } + + #[test] + fn test_cross_byte_boundary() { + // Test data: 0xF0, 0x0F = 11110000 00001111 + let data = [0xF0, 0x0F]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + // Read 6 bits crossing byte boundary: 111100 (should be 0x3C = 60) + assert_eq!(bs.next_bits(6).unwrap(), 0x3C); + + // Read remaining 10 bits: 0000001111 (should be 0x0F = 15) + } + + #[test] + fn test_large_reads() { + // Test reading up to 64 bits + let data = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + // Read all 64 bits at once + let result = bs.next_bits(64).unwrap(); + let expected = 0xFFEEDDCCBBAA9988u64; + assert_eq!(result, expected); + } + + #[test] + fn test_zero_bits_read() { + let data = [0xAA]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + // Reading 0 bits should return 0 and not advance position + assert_eq!(bs.next_bits(0).unwrap(), 0); + assert_eq!(bs._i_pos, 0); + + // Next read should still work normally + assert_eq!(bs.next_bits(1).unwrap(), 1); + } + + #[test] + fn test_insufficient_data() { + let data = [0xAA]; // Only 8 bits available + let mut bs = BitStreamRust::new(&data).unwrap(); + + // Try to read more bits than available + let result = bs.next_bits(16).unwrap(); + assert_eq!(result, 0); // Should return 0 when not enough bits + assert!(bs.bits_left < 0); // bits_left should be negative + + // Subsequent reads should also return 0 + assert_eq!(bs.next_bits(8).unwrap(), 0); + } + + #[test] + fn test_negative_bits_left_behavior() { + let data = [0xFF]; + let mut bs = BitStreamRust::new(&data).unwrap(); + + // Exhaust all bits + bs.next_bits(8).unwrap(); + + // Now bits_left should be 0 + assert_eq!(bs.bits_left, 0); + + // Try to read more - should return 0 and make bits_left negative + assert_eq!(bs.next_bits(4).unwrap(), 0); + assert_eq!(bs.bits_left, -4); + + // Another read should make it more negative + assert_eq!(bs.next_bits(2).unwrap(), 0); + assert_eq!(bs.bits_left, -6); + } + + #[test] + fn test_bits_left_calculation() { + let data = [0xFF, 0xFF, 0xFF]; // 24 bits total + let mut bs = BitStreamRust::new(&data).unwrap(); + + assert_eq!(bs.bits_left, 24); + + bs.next_bits(5).unwrap(); + assert_eq!(bs.bits_left, 19); + } +} diff --git a/src/rust/lib_ccxr/src/common/mod.rs b/src/rust/lib_ccxr/src/common/mod.rs index 4aaf4bb62..71d877516 100644 --- a/src/rust/lib_ccxr/src/common/mod.rs +++ b/src/rust/lib_ccxr/src/common/mod.rs @@ -16,8 +16,10 @@ //! | `cdp_section_type` | [`CdpSectionType`] | //! | `language[NB_LANGUAGE]` | [`Language`] | +mod bitstream; mod constants; mod options; +pub use bitstream::*; pub use constants::*; pub use options::*; diff --git a/src/rust/lib_ccxr/src/util/bits.rs b/src/rust/lib_ccxr/src/util/bits.rs index cc20e6152..1e0d2f676 100644 --- a/src/rust/lib_ccxr/src/util/bits.rs +++ b/src/rust/lib_ccxr/src/util/bits.rs @@ -229,10 +229,10 @@ mod tests { #[test] fn test_get_parity() { - assert_eq!(get_parity(0), false); - assert_eq!(get_parity(1), true); - assert_eq!(get_parity(128), true); - assert_eq!(get_parity(255), false); + assert!(!get_parity(0)); + assert!(get_parity(1)); + assert!(get_parity(128)); + assert!(!get_parity(255)); } #[test] diff --git a/src/rust/src/libccxr_exports/bitstream.rs b/src/rust/src/libccxr_exports/bitstream.rs new file mode 100644 index 000000000..2d94f14f7 --- /dev/null +++ b/src/rust/src/libccxr_exports/bitstream.rs @@ -0,0 +1,951 @@ +use crate::bindings::bitstream; +use lib_ccxr::common::BitStreamRust; +use lib_ccxr::info; +use std::os::raw::{c_int, c_uchar}; + +/// Copies the state from a Rust `BitStreamRust` to a C `bitstream` struct. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bitstream_ptr`) without null checking +/// - Assumes `bitstream_ptr` points to a valid, properly initialized and aligned `bitstream` struct +/// - Assumes the `bitstream` struct has sufficient memory allocated for all fields +/// - Assumes `rust_bitstream` is properly initialized with valid data +/// - Performs pointer arithmetic on `rust_bitstream.data` assuming it points to valid memory +/// - The caller must ensure the `bitstream` struct remains valid for the duration of the operation +/// - The data referenced by `rust_bitstream.data` must remain valid during the operation +unsafe fn copy_bitstream_from_rust_to_c( + bitstream_ptr: *mut bitstream, + rust_bitstream: &BitStreamRust, +) { + // Handle empty slice case + if rust_bitstream.data.is_empty() { + (*bitstream_ptr).pos = std::ptr::null_mut(); + (*bitstream_ptr).bpos = 8; + (*bitstream_ptr).end = std::ptr::null_mut(); + (*bitstream_ptr).bitsleft = 0; + (*bitstream_ptr).error = c_int::from(rust_bitstream.error); + (*bitstream_ptr)._i_pos = std::ptr::null_mut(); + (*bitstream_ptr)._i_bpos = 0; + return; + } + + // Get the original pos (which is the base of our slice) + let base_ptr = rust_bitstream.data.as_ptr() as *mut c_uchar; + let end_ptr = base_ptr.add(rust_bitstream.data.len()); + + // Current position should be base + rust pos + let current_pos_ptr = base_ptr.add(rust_bitstream.pos); + + // Internal position should be base + _i_pos + let i_pos_ptr = if rust_bitstream._i_pos <= rust_bitstream.data.len() { + base_ptr.add(rust_bitstream._i_pos) + } else { + base_ptr + }; + + (*bitstream_ptr).pos = current_pos_ptr; + (*bitstream_ptr).bpos = rust_bitstream.bpos as i32; + (*bitstream_ptr).end = end_ptr; + (*bitstream_ptr).bitsleft = rust_bitstream.bits_left; + (*bitstream_ptr).error = c_int::from(rust_bitstream.error); + (*bitstream_ptr)._i_pos = i_pos_ptr; + (*bitstream_ptr)._i_bpos = rust_bitstream._i_bpos as i32; +} + +/// Converts a C `bitstream` struct to a Rust `BitStreamRust` with static lifetime. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`value`) without null checking +/// - Assumes `value` points to a valid, properly initialized `bitstream` struct +/// - Assumes the `pos` and `end` pointers are either both null or both point to valid memory +/// - Creates a slice with `'static` lifetime from potentially non-static memory +/// - Assumes the memory region from `pos` to `end` contains valid, readable data +/// - Assumes `_i_pos` is either null or points to memory within the valid range +/// - The caller must ensure the memory referenced by the pointers remains valid for the `'static` lifetime +/// - Performs pointer arithmetic and offset calculations assuming valid pointer relationships +unsafe fn copy_bitstream_c_to_rust(value: *mut bitstream) -> BitStreamRust<'static> { + // We need to work with the original buffer, not just from current position + let mut slice: &[u8] = &[]; + let mut current_pos_in_slice = 0; + let mut i_pos_in_slice = 0; + + if !(*value).pos.is_null() && !(*value).end.is_null() { + // Calculate total buffer length from pos to end + let total_len = (*value).end.offset_from((*value).pos) as usize; + + if total_len > 0 { + // Create slice from current position to end + slice = std::slice::from_raw_parts((*value).pos, total_len); + // Current position in this slice is 0 (since slice starts from current pos) + current_pos_in_slice = 0; + + // Calculate _i_pos relative to the slice start (which is current pos) + if !(*value)._i_pos.is_null() { + let i_offset = (*value)._i_pos.offset_from((*value).pos); + i_pos_in_slice = i_offset.max(0) as usize; + } + } + } + + BitStreamRust { + data: slice, + pos: current_pos_in_slice, + bpos: (*value).bpos as u8, + bits_left: (*value).bitsleft, + error: (*value).error != 0, + _i_pos: i_pos_in_slice, + _i_bpos: (*value)._i_bpos as u8, + } +} +/// Updates only the internal state of a C bitstream without modifying current position pointers. +/// Used by functions like `ccxr_next_bits` that peek at data without advancing the main position. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bitstream_ptr`) without null checking +/// - Assumes `bitstream_ptr` points to a valid, properly initialized `bitstream` struct +/// - Assumes `rust_bitstream` is properly initialized with valid data +/// - Performs pointer arithmetic assuming valid memory layout +/// - The caller must ensure the `bitstream` struct remains valid during the operation +unsafe fn copy_internal_state_from_rust_to_c( + bitstream_ptr: *mut bitstream, + rust_bitstream: &BitStreamRust, +) { + // Only update internal positions and bits_left, NOT current position + (*bitstream_ptr).bitsleft = rust_bitstream.bits_left; + (*bitstream_ptr).error = c_int::from(rust_bitstream.error); + (*bitstream_ptr)._i_bpos = rust_bitstream._i_bpos as i32; + + // Handle _i_pos + if rust_bitstream.data.is_empty() { + (*bitstream_ptr)._i_pos = std::ptr::null_mut(); + } else { + let base_ptr = rust_bitstream.data.as_ptr() as *mut c_uchar; + let i_pos_ptr = if rust_bitstream._i_pos <= rust_bitstream.data.len() { + base_ptr.add(rust_bitstream._i_pos) + } else { + base_ptr // Fallback to start if out of bounds + }; + (*bitstream_ptr)._i_pos = i_pos_ptr; + } +} + +/// Free a bitstream created by Rust allocation functions. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer without null checking (though it does check for null) +/// - Assumes the pointer was allocated by Rust and is owned by a `Box` +/// - Assumes no other references to the `BitStreamRust` exist when this is called +/// - The pointer must not be used after this function returns +/// - This function must only be called once per allocated bitstream +/// - Double-free will occur if called multiple times with the same pointer +#[no_mangle] +pub unsafe extern "C" fn ccxr_free_bitstream(bs: *mut BitStreamRust<'static>) { + if !bs.is_null() { + drop(Box::from_raw(bs)); + } +} + +/// Read bits from a bitstream without advancing the position (peek operation). +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers (`pos`, `end`, `_i_pos`) point to valid, readable memory +/// - Assumes the memory region contains valid data and has sufficient bits available +/// - Modifies the internal state of the bitstream struct +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +/// - The underlying data must not be modified during the operation +#[no_mangle] +pub unsafe extern "C" fn ccxr_next_bits(bs: *mut bitstream, bnum: u32) -> u64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let val = match rust_bs.next_bits(bnum) { + Ok(val) => val, + Err(_) => return 0, + }; + // Only copy back internal state, NOT current position + copy_internal_state_from_rust_to_c(bs, &rust_bs); + val +} + +/// Read bits from a bitstream and advance the position. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Assumes the memory region contains sufficient readable bits for the requested operation +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +/// - The underlying data must not be modified during the operation +#[no_mangle] +pub unsafe extern "C" fn ccxr_read_bits(bs: *mut bitstream, bnum: u32) -> u64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let val = match rust_bs.read_bits(bnum) { + Ok(val) => val, + Err(_) => return 0, + }; + copy_bitstream_from_rust_to_c(bs, &rust_bs); + val +} + +/// Skip a number of bits in the bitstream. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid memory +/// - Assumes the bitstream contains at least the requested number of bits to skip +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +#[no_mangle] +pub unsafe extern "C" fn ccxr_skip_bits(bs: *mut bitstream, bnum: u32) -> i32 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let val = match rust_bs.skip_bits(bnum) { + Ok(val) => val, + Err(_) => return 0, + }; + copy_bitstream_from_rust_to_c(bs, &rust_bs); + if val { + 1 + } else { + 0 + } +} + +/// Check if the bitstream is byte aligned. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid memory +/// - The caller must ensure the bitstream remains valid during the operation +#[no_mangle] +pub unsafe extern "C" fn ccxr_is_byte_aligned(bs: *mut bitstream) -> i32 { + let rust_bs = copy_bitstream_c_to_rust(bs); + match rust_bs.is_byte_aligned() { + Ok(val) => { + if val { + 1 + } else { + 0 + } + } + Err(_) => 0, + } +} + +/// Align the bitstream to the next byte boundary. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Modifies the bitstream state including position pointers +/// - Assumes the bitstream has sufficient bits available to reach the next byte boundary +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +#[no_mangle] +pub unsafe extern "C" fn ccxr_make_byte_aligned(bs: *mut bitstream) { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + if rust_bs.make_byte_aligned().is_ok() { + copy_bitstream_from_rust_to_c(bs, &rust_bs); + } else { + info!("Bitstream : Failed to make bitstream byte aligned"); + } +} + +/// Get a pointer to the next bytes in the bitstream without advancing the position. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Returns a raw pointer that must not outlive the bitstream or be used after bitstream modification +/// - Assumes the bitstream is byte-aligned before calling this function +/// - Assumes the bitstream contains at least `bynum` readable bytes +/// - Modifies the internal state of the bitstream +/// - The returned pointer points to read-only memory that must not be modified +/// - The caller must ensure the bitstream and its data remain valid while using the returned pointer +#[no_mangle] +pub unsafe extern "C" fn ccxr_next_bytes(bs: *mut bitstream, bynum: usize) -> *const u8 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + match rust_bs.next_bytes(bynum) { + Ok(slice) => { + // Copy back internal state only (like next_bits does) + copy_internal_state_from_rust_to_c(bs, &rust_bs); + slice.as_ptr() + } + Err(_) => std::ptr::null(), + } +} + +/// Read bytes from the bitstream and advance the position. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Returns a raw pointer that must not outlive the bitstream or be used after bitstream modification +/// - Assumes the bitstream is byte-aligned before calling this function +/// - Assumes the bitstream contains at least `bynum` readable bytes +/// - Modifies the bitstream state including position pointers +/// - The returned pointer points to read-only memory that must not be modified +/// - The caller must ensure the bitstream and its data remain valid while using the returned pointer +#[no_mangle] +pub unsafe extern "C" fn ccxr_read_bytes(bs: *mut bitstream, bynum: usize) -> *const u8 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + match rust_bs.read_bytes(bynum) { + Ok(slice) => { + copy_bitstream_from_rust_to_c(bs, &rust_bs); + slice.as_ptr() + } + Err(_) => std::ptr::null(), + } +} +/// Read a multi-byte number from the bitstream with optional position advancement. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Assumes the bitstream contains at least `bytes` readable bytes +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +/// - The `bytes` parameter should be reasonable (typically 1-8) to avoid potential overflow +#[no_mangle] +pub unsafe extern "C" fn ccxr_bitstream_get_num( + bs: *mut bitstream, + bytes: usize, + advance: i32, +) -> u64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let result = rust_bs.bitstream_get_num(bytes, advance != 0).unwrap_or(0); + copy_bitstream_from_rust_to_c(bs, &rust_bs); + result +} + +/// Read an unsigned Exp-Golomb code from the bitstream. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Assumes the bitstream contains a valid Exp-Golomb encoded value +/// - Assumes the bitstream has sufficient bits to read the complete encoded value +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +#[no_mangle] +pub unsafe extern "C" fn ccxr_read_exp_golomb_unsigned(bs: *mut bitstream) -> u64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let result = rust_bs.read_exp_golomb_unsigned().unwrap_or(0); + copy_bitstream_from_rust_to_c(bs, &rust_bs); + result +} + +/// Read a signed Exp-Golomb code from the bitstream. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Assumes the bitstream contains a valid signed Exp-Golomb encoded value +/// - Assumes the bitstream has sufficient bits to read the complete encoded value +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +#[no_mangle] +pub unsafe extern "C" fn ccxr_read_exp_golomb(bs: *mut bitstream) -> i64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let result = rust_bs.read_exp_golomb().unwrap_or(0); + copy_bitstream_from_rust_to_c(bs, &rust_bs); + result +} +/// Read a signed integer value from the specified number of bits. +/// +/// # Safety +/// This function is unsafe because it: +/// - Dereferences a raw pointer (`bs`) without null checking +/// - Assumes `bs` points to a valid, properly initialized `bitstream` struct +/// - Assumes the bitstream's data pointers point to valid, readable memory +/// - Assumes the bitstream contains at least `bnum` readable bits +/// - Modifies the bitstream state including position pointers +/// - The caller must ensure the bitstream remains valid and is not accessed concurrently +/// - The `bnum` parameter should be reasonable (typically 1-64) for integer representation +#[no_mangle] +pub unsafe extern "C" fn ccxr_read_int(bs: *mut bitstream, bnum: u32) -> i64 { + let mut rust_bs = copy_bitstream_c_to_rust(bs); + let result = rust_bs.read_int(bnum).unwrap_or(0); + copy_bitstream_from_rust_to_c(bs, &rust_bs); + result +} + +/// Reverse the bits in a byte (bit 0 becomes bit 7, etc.). +/// +/// # Safety +/// This function is marked unsafe only because it's part of the FFI interface. +/// It does not perform any unsafe operations internally and is safe to call with any `u8` value. +/// The safety requirements exist only due to the C calling convention. +#[no_mangle] +pub unsafe extern "C" fn ccxr_reverse8(data: u8) -> u8 { + BitStreamRust::reverse8(data) +} + +mod tests { + // FFI binding tests + #[test] + fn test_ffi_next_bits() { + let data = vec![0b10101010]; + let mut c_bs = crate::bindings::bitstream { + pos: data.as_ptr() as *mut u8, + bpos: 8, + end: unsafe { data.as_ptr().add(data.len()) } as *mut u8, + bitsleft: (data.len() as i64) * 8, + error: 0, + _i_pos: data.as_ptr() as *mut u8, + _i_bpos: 8, + }; + + assert_eq!(unsafe { super::ccxr_next_bits(&mut c_bs, 1) }, 1); + } + + #[test] + fn test_ffi_read_bits() { + let data = vec![0b10101010]; + let mut c_bs = crate::bindings::bitstream { + pos: data.as_ptr() as *mut u8, + bpos: 8, + end: unsafe { data.as_ptr().add(data.len()) } as *mut u8, + bitsleft: (data.len() as i64) * 8, + error: 0, + _i_pos: data.as_ptr() as *mut u8, + _i_bpos: 8, + }; + + assert_eq!(unsafe { super::ccxr_read_bits(&mut c_bs, 3) }, 0b101); + } + + #[test] + fn test_ffi_byte_alignment() { + let data = vec![0xFF]; + let mut c_bs = crate::bindings::bitstream { + pos: data.as_ptr() as *mut u8, + bpos: 8, + end: unsafe { data.as_ptr().add(data.len()) } as *mut u8, + bitsleft: (data.len() as i64) * 8, + error: 0, + _i_pos: data.as_ptr() as *mut u8, + _i_bpos: 8, + }; + + assert_eq!( + unsafe { super::ccxr_is_byte_aligned(&mut c_bs as *mut crate::bindings::bitstream) }, + 1 + ); + unsafe { super::ccxr_read_bits(&mut c_bs, 1) }; + assert_eq!( + unsafe { super::ccxr_is_byte_aligned(&mut c_bs as *mut crate::bindings::bitstream) }, + 0 + ); + } + + #[test] + fn test_ffi_read_bytes() { + static DATA: [u8; 3] = [0xAA, 0xBB, 0xCC]; + let mut c_bs = crate::bindings::bitstream { + pos: DATA.as_ptr() as *mut u8, + bpos: 8, + end: unsafe { DATA.as_ptr().add(DATA.len()) } as *mut u8, + bitsleft: (DATA.len() as i64) * 8, + error: 0, + _i_pos: DATA.as_ptr() as *mut u8, + _i_bpos: 8, + }; + + unsafe { + let ptr = super::ccxr_read_bytes(&mut c_bs, 2); + assert!(!ptr.is_null()); + let b1 = *ptr; + let b2 = *ptr.add(1); + assert_eq!([b1, b2], [0xAA, 0xBB]); + } + } + + #[test] + fn test_ffi_exp_golomb() { + let data = vec![0b10000000]; + let data_ptr = data.as_ptr(); + let mut c_bs = crate::bindings::bitstream { + pos: data_ptr as *mut u8, + bpos: 8, + end: unsafe { data_ptr.add(data.len()) } as *mut u8, + bitsleft: (data.len() as i64) * 8, + error: 0, + _i_pos: data_ptr as *mut u8, + _i_bpos: 8, + }; + + assert_eq!( + unsafe { super::ccxr_read_exp_golomb_unsigned(&mut c_bs) }, + 0 + ); + drop(data); + } + + #[test] + fn test_ffi_reverse8() { + assert_eq!(unsafe { super::ccxr_reverse8(0b10101010) }, 0b01010101); + } + + #[test] + fn test_ffi_state_updates() { + let data = vec![0xAA, 0xBB]; + let mut c_bs = crate::bindings::bitstream { + pos: data.as_ptr() as *mut u8, + bpos: 8, + end: unsafe { data.as_ptr().add(data.len()) } as *mut u8, + bitsleft: (data.len() as i64) * 8, + error: 0, + _i_pos: data.as_ptr() as *mut u8, + _i_bpos: 8, + }; + + unsafe { super::ccxr_read_bits(&mut c_bs, 4) }; + assert_eq!(c_bs.bpos, 4); + } +} +#[cfg(test)] +mod bitstream_copying_tests { + use super::*; + use std::ptr; + + // Test helper function to create a test buffer + fn create_test_buffer(size: usize) -> Vec { + (0..size).map(|i| (i % 256) as u8).collect() + } + + // Test helper to verify pointer arithmetic safety + unsafe fn verify_pointer_bounds(c_stream: &bitstream) -> bool { + !c_stream.pos.is_null() && !c_stream.end.is_null() && c_stream.pos <= c_stream.end + } + + #[test] + fn test_rust_to_c_basic_conversion() { + let buffer = create_test_buffer(100); + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 3, + bits_left: 789, + error: false, + _i_pos: 10, + _i_bpos: 5, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &*c_s; + // Verify basic field conversions + assert_eq!(c_stream.bpos, 3); + assert_eq!(c_stream.bitsleft, 789); + assert_eq!(c_stream.error, 0); + assert_eq!(c_stream._i_bpos, 5); + + // Verify pointer arithmetic + assert!(verify_pointer_bounds(&c_stream)); + assert_eq!(c_stream.end.offset_from(c_stream.pos), 100); + assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 10); + + // Verify data integrity + let reconstructed_slice = std::slice::from_raw_parts(c_stream.pos, 100); + assert_eq!(reconstructed_slice, &buffer[..]); + } + } + + #[test] + fn test_c_to_rust_basic_conversion() { + let mut buffer = create_test_buffer(50); + let buffer_ptr = buffer.as_mut_ptr(); + + unsafe { + let mut c_stream = bitstream { + pos: buffer_ptr, + bpos: 7, + end: buffer_ptr.add(50), + bitsleft: 400, + error: 1, + _i_pos: buffer_ptr.add(15), + _i_bpos: 2, + }; + + let rust_stream = copy_bitstream_c_to_rust(&mut c_stream as *mut bitstream); + + // Verify basic field conversions + assert_eq!(rust_stream.bpos, 7); + assert_eq!(rust_stream.bits_left, 400); + assert_eq!(rust_stream.error, true); + assert_eq!(rust_stream._i_pos, 15); + assert_eq!(rust_stream._i_bpos, 2); + + // Verify slice reconstruction + assert_eq!(rust_stream.data.len(), 50); + assert_eq!(rust_stream.data, &buffer[..]); + } + } + + #[test] + fn test_round_trip_conversion() { + let buffer = create_test_buffer(75); + let original = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 4, + bits_left: 600, + error: true, + _i_pos: 25, + _i_bpos: 1, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &original); + let reconstructed = copy_bitstream_c_to_rust(c_s); + + // Verify all fields match + assert_eq!(reconstructed.bpos, original.bpos); + assert_eq!(reconstructed.bits_left, original.bits_left); + assert_eq!(reconstructed.error, original.error); + assert_eq!(reconstructed._i_pos, original._i_pos); + assert_eq!(reconstructed._i_bpos, original._i_bpos); + assert_eq!(reconstructed.data, original.data); + } + } + + #[test] + fn test_empty_buffer() { + let buffer: &[u8] = &[]; + let rust_stream = BitStreamRust { + data: buffer, + pos: 0, + bpos: 0, + bits_left: 0, + error: false, + _i_pos: 0, + _i_bpos: 0, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + + // Verify empty buffer handling + assert_eq!(c_stream.end.offset_from(c_stream.pos), 0); + assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 0); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed.data.len(), 0); + assert_eq!(reconstructed._i_pos, 0); + } + } + + #[test] + fn test_single_byte_buffer() { + let buffer = vec![0x42u8]; + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 7, + bits_left: 1, + error: false, + _i_pos: 0, + _i_bpos: 3, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + assert_eq!(c_stream.end.offset_from(c_stream.pos), 1); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed.data.len(), 1); + assert_eq!(reconstructed.data[0], 0x42); + } + } + + #[test] + fn test_large_buffer() { + let buffer = create_test_buffer(10000); + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 2, + bits_left: 80000, + error: false, + _i_pos: 5000, + _i_bpos: 6, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + assert_eq!(c_stream.end.offset_from(c_stream.pos), 10000); + assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 5000); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed.data.len(), 10000); + assert_eq!(reconstructed._i_pos, 5000); + } + } + + #[test] + fn test_boundary_values() { + let buffer = create_test_buffer(256); + + // Test with maximum values + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 7, // Max value for 3 bits + bits_left: i64::MAX, + error: true, + _i_pos: 255, // Last valid index + _i_bpos: 7, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let reconstructed = copy_bitstream_c_to_rust(c_s); + + assert_eq!(reconstructed.bpos, 7); + assert_eq!(reconstructed.bits_left, i64::MAX); + assert_eq!(reconstructed.error, true); + assert_eq!(reconstructed._i_pos, 255); + assert_eq!(reconstructed._i_bpos, 7); + } + } + + #[test] + fn test_negative_values() { + let buffer = create_test_buffer(50); + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 0, + bits_left: -100, // Negative bits_left + error: false, // Negative error code + _i_pos: 0, + _i_bpos: 0, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let reconstructed = copy_bitstream_c_to_rust(c_s); + + assert_eq!(reconstructed.bits_left, -100); + assert_eq!(reconstructed.error, false); + } + } + + #[test] + fn test_null_i_pos_handling() { + let mut buffer = create_test_buffer(30); + let buffer_ptr = buffer.as_mut_ptr(); + + unsafe { + let mut c_stream = bitstream { + pos: buffer_ptr, + bpos: 0, + end: buffer_ptr.add(30), + bitsleft: 240, + error: 0, + _i_pos: ptr::null_mut(), // Null _i_pos + _i_bpos: 0, + }; + + let rust_stream = copy_bitstream_c_to_rust(&mut c_stream as *mut bitstream); + + // Should default to 0 when _i_pos is null + assert_eq!(rust_stream._i_pos, 0); + } + } + + #[test] + fn test_different_buffer_positions() { + let buffer = create_test_buffer(100); + + // Test different _i_pos values + for i_pos in [0, 1, 50, 99] { + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 0, + bits_left: 800, + error: false, + _i_pos: i_pos, + _i_bpos: 0, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), i_pos as isize); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed._i_pos, i_pos); + } + } + } + + #[test] + fn test_data_integrity_after_conversion() { + // Create a buffer with specific pattern + let mut buffer = Vec::new(); + for i in 0..256 { + buffer.push(i as u8); + } + + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 3, + bits_left: 2048, + error: false, + _i_pos: 128, + _i_bpos: 4, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + + // Verify we can read the data correctly through C pointers + for i in 0..256 { + let byte_val = *c_stream.pos.add(i); + assert_eq!(byte_val, i as u8); + } + + // Verify internal position pointer + let internal_byte = *c_stream._i_pos; + assert_eq!(internal_byte, 128); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed.data.len(), 256); + for (i, &byte) in reconstructed.data.iter().enumerate() { + assert_eq!(byte, i as u8); + } + } + } + + #[test] + fn test_multiple_conversions() { + let buffer = create_test_buffer(64); + let mut rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 1, + bits_left: 512, + error: false, + _i_pos: 32, + _i_bpos: 2, + }; + + // Test multiple round-trip conversions + for _ in 0..5 { + rust_stream.error = true; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let new_rust_stream = copy_bitstream_c_to_rust(c_s); + + assert_eq!(new_rust_stream.error, true); + assert_eq!(new_rust_stream.data.len(), 64); + assert_eq!(new_rust_stream._i_pos, 32); + + rust_stream = new_rust_stream; + } + } + } + + #[test] + fn test_bit_position_values() { + let buffer = create_test_buffer(10); + + // Test all valid bit positions (0-7) + for bpos in 0..8 { + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: bpos as u8, + bits_left: 80, + error: false, + _i_pos: 5, + _i_bpos: (7 - bpos) as u8, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + assert_eq!(c_stream.bpos, bpos); + assert_eq!(c_stream._i_bpos, 7 - bpos); + + let reconstructed = copy_bitstream_c_to_rust(c_s); + assert_eq!(reconstructed.bpos, bpos as u8); + assert_eq!(reconstructed._i_bpos, (7 - bpos) as u8); + } + } + } + + #[test] + fn test_memory_safety() { + let buffer = create_test_buffer(1000); + let rust_stream = BitStreamRust { + data: &buffer, + pos: 0, + bpos: 0, + bits_left: 8000, + error: false, + _i_pos: 500, + _i_bpos: 0, + }; + + unsafe { + let c_s = Box::into_raw(Box::new(bitstream::default())); + copy_bitstream_from_rust_to_c(c_s, &rust_stream); + let c_stream = &mut *c_s; + + // Verify all pointers are within bounds + assert!(verify_pointer_bounds(&c_stream)); + + // Verify we can safely access the boundaries + let first_byte = *c_stream.pos; + let last_byte = *c_stream.end.sub(1); + let internal_byte = *c_stream._i_pos; + + // These should not panic and should match our buffer + assert_eq!(first_byte, 0); + assert_eq!(last_byte, (999 % 256) as u8); + assert_eq!(internal_byte, (500 % 256) as u8); + } + } +} diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index c2f64a258..ca65bb36f 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,6 +1,8 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. +pub mod bitstream; pub mod time; + use crate::ccx_options; use lib_ccxr::util::log::*; use lib_ccxr::util::{bits::*, levenshtein::*}; diff --git a/src/rust/wrapper.h b/src/rust/wrapper.h index a7297478f..2400d7d08 100644 --- a/src/rust/wrapper.h +++ b/src/rust/wrapper.h @@ -11,3 +11,4 @@ #include "../lib_ccx/hardsubx.h" #include "../lib_ccx/utility.h" #include "../lib_ccx/ccx_encoders_helpers.h" +#include "../lib_ccx/cc_bitstream.h"