[FEAT] Add bitstream module in lib_ccxr (#1649)

* feat: Add bitstream module

* run code formatters

* Run cargo clippy --fix

* Run cargo fmt --all

* refactor: remove rust pointer from C struct

* feat: Add bitstream module

* run code formatters

* Run cargo clippy --fix

* Run cargo fmt --all

* refactor: remove rust pointer from C struct

* Added Bitstream to libccxr_exports

* Minor Formatting Issue

* Bitstream: Removed redundant CType

* bitstream: recommended changes for is_byte_aligned

* bitstream: recommended changes for long comments

* bitstream: comment fix

* bitstream: removed redundant comparism comments

---------

Co-authored-by: Deepnarayan Sett <depnra1@gmail.com>
Co-authored-by: Deepnarayan Sett <71217129+steel-bucket@users.noreply.github.com>
This commit is contained in:
Swastik Patel
2025-07-07 04:41:31 +05:30
committed by GitHub
parent 099fa059c7
commit 81fdecd5af
8 changed files with 1382 additions and 2 deletions

View File

@@ -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!");
@@ -140,7 +166,7 @@ int skip_bits(struct bitstream *bstr, unsigned bnum)
if (bstr->bitsleft < 0)
return 0;
// Special case for reading zero bits. Return zero
// Special case for reading zero bits. Return one == success
if (bnum == 0)
return 1;
@@ -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
}

View File

@@ -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

View File

@@ -27,6 +27,7 @@ fn main() {
".*(?i)_?dtvcc_.*",
"encoder_ctx",
"lib_cc_decode",
"bitstream",
"cc_subtitle",
"ccx_output_format",
"ccx_boundary_time",

View File

@@ -0,0 +1,460 @@
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<Self, BitstreamError> {
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<u64, BitstreamError> {
if bnum > 64 {
fatal!(cause = ExitCause::Bug; "In next_bits: Argument is greater than the maximum bit number i.e. 64: {}!", bnum);
}
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.
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 ...");
}
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<u64, BitstreamError> {
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<bool, BitstreamError> {
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.
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
let mut new_bpos = self.bpos as i32 - (bnum % 8) as i32;
let mut new_pos = self.pos + (bnum / 8) as usize;
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<bool, BitstreamError> {
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);
}
Ok(vbit == 8)
}
/// Align to next byte boundary (commit state).
pub fn make_byte_aligned(&mut self) -> Result<(), BitstreamError> {
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 {
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
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> {
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);
}
self.bits_left = (self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64
- (bynum * 8) as i64;
if !self.is_byte_aligned()? || self.bits_left < 0 || bynum < 1 {
return Err(BitstreamError::InsufficientData);
}
// Remember the bitstream position
self._i_bpos = 8;
self._i_pos = self.pos + bynum;
// Return slice of the requested bytes
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
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<u64, BitstreamError> {
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<u64, BitstreamError> {
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<i64, BitstreamError> {
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<i64, BitstreamError> {
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);
}
}

View File

@@ -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::*;

View File

@@ -0,0 +1,852 @@
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`)
/// - Performs pointer arithmetic and assumes valid memory layout
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 and
/// 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:
/// This function is unsafe because it Dereferences a raw pointer (`value`) without null checking and
/// Performs pointer arithmetic and offset calculations assuming valid pointer relationships
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 Drops the `Box` created from a raw pointer (`bs`) without null checking.
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_internal_state_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_internal_state_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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 calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
#[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.
#[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<u8> {
(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);
}
}
}

View File

@@ -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::*};

View File

@@ -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"