Merge branch 'CCExtractor:master' into final

This commit is contained in:
Rahul Tripathi
2026-01-16 16:05:36 +05:30
committed by GitHub
19 changed files with 297 additions and 84 deletions

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v4
uses: dawidd6/action-homebrew-bump-formula@v7
with:
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
formula: ccextractor

View File

@@ -66,6 +66,30 @@ You can find sample files on [our website](https://ccextractor.org/public/genera
- [Building on Windows using WSL](docs/build-wsl.md)
#### Linux (Autotools) build notes
CCExtractor also supports an autotools-based build system under the `linux/`
directory.
Important notes:
- The autotools workflow lives inside `linux/`. The `configure` script is
generated there and should be run from that directory.
- Typical build steps are:
```
cd linux
./autogen.sh
./configure
make
```
- Rust support is enabled automatically if `cargo` and `rustc` are available
on the system. In that case, Rust components are built and linked during
`make`.
- If you encounter unexpected build or linking issues, a clean rebuild
(`make clean` or a fresh clone) is recommended, especially when Rust is
involved.
This build flow has been tested on Linux and WSL.
## Compiling CCExtractor
To learn more about how to compile and build CCExtractor for your platform check the [compilation guide](https://github.com/CCExtractor/ccextractor/blob/master/docs/COMPILATION.MD).

View File

@@ -1,5 +1,12 @@
0.96.6 (unreleased)
-------------------
- Fix: Prevent infinite loop on truncated MKV files
- Fix: Various memory safety and stability fixes in demuxers (MP4, PS, MKV, DVB)
- Fix: Delete empty output files instead of leaving 0-byte files (#1282)
0.96.5 (2026-01-05)
-------------------
- New: CCExtractor is available again via Homebrew on macOS and Linux.
- New: Add support for raw CDP (Caption Distribution Packet) files (#1406)
- New: Add --scc-accurate-timing option for bandwidth-aware SCC output (#1120)
- Fix: MXF files containing CEA-708 captions not being detected/extracted (#1647)
@@ -56,6 +63,7 @@
- Extract multiple teletext pages simultaneously with separate output files
- Use --tpage multiple times (e.g., --tpage 100 --tpage 200)
- Output files are named with page suffix (e.g., output_p100.srt, output_p200.srt)
- Fix: SPUPNG subtitle offset calculation to center based on actual image dimensions
- New: Added --list-tracks (-L) option to list all tracks in media files without processing
New: Chinese, Korean, Japanese support - proper encoding and OCR.

View File

@@ -1,3 +1,16 @@
# Installation
## Homebrew
The easiest way to install CCExtractor for Mac and Linux is through Homebrew:
```bash
brew install ccextractor
```
Note: If you don't have Homebrew installed, see [brew.sh](https://brew.sh/)
for installation instructions.
---
# Compiling CCExtractor
You may compile CCExtractor across all major platforms using `CMakeLists.txt` stored under `ccextractor/src/` directory. Autoconf and custom build scripts are also available. See platform specific instructions in the below sections.

View File

@@ -778,6 +778,7 @@ struct encoder_ctx *init_encoder(struct encoder_cfg *opt)
return NULL;
}
ctx->in_fileformat = opt->in_format;
ctx->is_pal = (opt->in_format == 2);
/** used in case of SUB_EOD_MARKER */
ctx->prev_start = -1;

View File

@@ -182,10 +182,10 @@ struct encoder_ctx
// OCR in SPUPNG
int nospupngocr;
int is_pal;
// Teletext multi-page output (issue #665)
struct ccx_s_write *tlt_out[MAX_TLT_PAGES_EXTRACT]; // Output files per teletext page
uint16_t tlt_out_pages[MAX_TLT_PAGES_EXTRACT]; // Page numbers for each output slot
struct ccx_s_write *tlt_out[MAX_TLT_PAGES_EXTRACT]; // Output files per teletext page
uint16_t tlt_out_pages[MAX_TLT_PAGES_EXTRACT]; // Page numbers for each output slot
unsigned int tlt_srt_counter[MAX_TLT_PAGES_EXTRACT]; // SRT counter per page
int tlt_out_count; // Number of teletext output files
};

View File

@@ -251,6 +251,9 @@ void set_spupng_offset(void *ctx, int x, int y)
sp->xOffset = x;
sp->yOffset = y;
}
// Forward declaration for calculate_spupng_offsets
static void calculate_spupng_offsets(struct spupng_t *sp, struct encoder_ctx *ctx);
int save_spupng(const char *filename, uint8_t *bitmap, int w, int h,
png_color *palette, png_byte *alpha, int nb_color)
{
@@ -384,7 +387,7 @@ int write_cc_bitmap_as_spupng(struct cc_subtitle *sub, struct encoder_ctx *conte
struct cc_bitmap *rect;
png_color *palette = NULL;
png_byte *alpha = NULL;
int wrote_opentag = 1;
int wrote_opentag = 0; // Track if we actually wrote the tag
x_pos = -1;
y_pos = -1;
@@ -395,13 +398,11 @@ int write_cc_bitmap_as_spupng(struct cc_subtitle *sub, struct encoder_ctx *conte
return 0;
inc_spupng_fileindex(sp);
write_sputag_open(sp, sub->start_time, sub->end_time - 1);
if (sub->nb_data == 0 && (sub->flags & SUB_EOD_MARKER))
{
context->prev_start = -1;
if (wrote_opentag)
write_sputag_close(sp);
// No subtitle data, skip writing
return 0;
}
rect = sub->data;
@@ -440,7 +441,13 @@ int write_cc_bitmap_as_spupng(struct cc_subtitle *sub, struct encoder_ctx *conte
}
}
filename = get_spupng_filename(sp);
set_spupng_offset(sp, x_pos, y_pos);
// Set image dimensions for offset calculation
sp->img_w = width;
sp->img_h = height;
// Calculate centered offsets based on screen size (PAL/NTSC)
calculate_spupng_offsets(sp, context);
if (sub->flags & SUB_EOD_MARKER)
context->prev_start = sub->start_time;
pbuf = (uint8_t *)malloc(width * height);
@@ -475,6 +482,15 @@ int write_cc_bitmap_as_spupng(struct cc_subtitle *sub, struct encoder_ctx *conte
/* TODO do rectangle wise, one color table should not be used for all rectangles */
mapclut_paletee(palette, alpha, (uint32_t *)rect[0].data1, rect[0].nb_colors);
// Save PNG file first
save_spupng(filename, pbuf, width, height, palette, alpha, rect[0].nb_colors);
freep(&pbuf);
// Write XML tag with calculated centered offsets
write_sputag_open(sp, sub->start_time, sub->end_time - 1);
wrote_opentag = 1; // Mark that we wrote the tag
#ifdef ENABLE_OCR
if (!context->nospupngocr)
{
@@ -487,8 +503,6 @@ int write_cc_bitmap_as_spupng(struct cc_subtitle *sub, struct encoder_ctx *conte
}
}
#endif
save_spupng(filename, pbuf, width, height, palette, alpha, rect[0].nb_colors);
freep(&pbuf);
end:
if (wrote_opentag)
@@ -991,6 +1005,8 @@ int spupng_export_string2png(struct spupng_t *sp, char *str, FILE *output)
*/
// Save image
sp->img_w = canvas_width;
sp->img_h = canvas_height;
write_image(buffer, output, canvas_width, canvas_height);
free(tmp);
free(buffer);
@@ -1081,6 +1097,28 @@ int eia608_to_str(struct encoder_ctx *context, struct eia608_screen *data, char
// string needs to be in UTF-8 encoding.
// This function will take care of encoding.
static void calculate_spupng_offsets(struct spupng_t *sp, struct encoder_ctx *ctx)
{
int screen_w = 720;
int screen_h;
/* Teletext is always PAL */
if (ctx->in_fileformat == 2 || ctx->is_pal)
{
screen_h = 576;
}
else
{
screen_h = 480;
}
sp->xOffset = (screen_w - sp->img_w) / 2;
sp->yOffset = (screen_h - sp->img_h) / 2;
// SPU / DVD requires even yOffset (interlacing)
if (sp->yOffset & 1)
sp->yOffset++;
}
int spupng_write_string(struct spupng_t *sp, char *string, LLONG start_time, LLONG end_time,
struct encoder_ctx *context)
{
@@ -1099,6 +1137,7 @@ int spupng_write_string(struct spupng_t *sp, char *string, LLONG start_time, LLO
}
// free(string_utf32);
fclose(sp->fppng);
calculate_spupng_offsets(sp, context);
write_sputag_open(sp, start_time, end_time);
write_spucomment(sp, string);
write_sputag_close(sp);

View File

@@ -39,6 +39,8 @@ struct spupng_t
int fileIndex;
int xOffset;
int yOffset;
int img_w;
int img_h;
};
#endif

View File

@@ -1926,7 +1926,7 @@ static int write_dvb_sub(struct lib_cc_decode *dec_ctx, struct cc_subtitle *sub)
ctx->ocr_ctx = init_ocr(ctx->lang_index);
ctx->ocr_initialized = 1; // Mark as initialized even if init_ocr returns NULL
}
if (ctx->ocr_ctx)
if (ctx->ocr_ctx && region)
{
// DEBUG: Dump before OCR
// dump_rect_and_log("before_ocr", rect->data0, rect->w, rect->h, rect->linesize0, 1, 0, 0);

View File

@@ -66,6 +66,7 @@ void prepare_for_new_file(struct lib_ccx_ctx *ctx)
{
// Init per file variables
ctx->last_reported_progress = -1;
ctx->min_global_timestamp_offset = -1; // -1 means not yet initialized
ctx->stat_numuserheaders = 0;
ctx->stat_dvdccheaders = 0;
ctx->stat_scte20ccheaders = 0;

View File

@@ -1727,7 +1727,24 @@ int general_loop(struct lib_ccx_ctx *ctx)
if (ctx->live_stream)
{
int cur_sec = (int)(get_fts(dec_ctx->timing, dec_ctx->current_field) / 1000);
LLONG t = get_fts(dec_ctx->timing, dec_ctx->current_field);
if (!t && ctx->demux_ctx->global_timestamp_inited)
t = ctx->demux_ctx->global_timestamp - ctx->demux_ctx->min_global_timestamp;
// Handle multi-program TS timing
if (ctx->demux_ctx->global_timestamp_inited)
{
LLONG offset = ctx->demux_ctx->global_timestamp - ctx->demux_ctx->min_global_timestamp;
if (ctx->min_global_timestamp_offset < 0 || offset < ctx->min_global_timestamp_offset)
ctx->min_global_timestamp_offset = offset;
// Only use timestamps from the program with the lowest base
if (offset - ctx->min_global_timestamp_offset < 60000)
t = offset - ctx->min_global_timestamp_offset;
else
t = ctx->min_global_timestamp_offset > 0 ? 0 : t;
if (t < 0)
t = 0;
}
int cur_sec = (int)(t / 1000);
int th = cur_sec / 10;
if (ctx->last_reported_progress != th)
{
@@ -1745,6 +1762,28 @@ int general_loop(struct lib_ccx_ctx *ctx)
LLONG t = get_fts(dec_ctx->timing, dec_ctx->current_field);
if (!t && ctx->demux_ctx->global_timestamp_inited)
t = ctx->demux_ctx->global_timestamp - ctx->demux_ctx->min_global_timestamp;
// For multi-program TS files, different programs can have different
// PCR bases (e.g., one at 25h, another at 23h). This causes the
// global_timestamp to jump between different bases, resulting in
// wildly different offset values. Track the minimum offset seen
// and only display times from the program with the lowest base.
if (ctx->demux_ctx->global_timestamp_inited)
{
LLONG offset = ctx->demux_ctx->global_timestamp - ctx->demux_ctx->min_global_timestamp;
// Track minimum offset (this is the PCR base of the program
// with the lowest timestamp, which represents true file time)
if (ctx->min_global_timestamp_offset < 0 || offset < ctx->min_global_timestamp_offset)
ctx->min_global_timestamp_offset = offset;
// Only use timestamps from the program with the lowest base.
// If current offset is significantly larger than minimum (by > 60s),
// it's from a program with a higher PCR base - use minimum instead.
if (offset - ctx->min_global_timestamp_offset < 60000)
t = offset - ctx->min_global_timestamp_offset;
else
t = ctx->min_global_timestamp_offset > 0 ? 0 : t; // fallback to minimum-based time
if (t < 0)
t = 0;
}
int cur_sec = (int)(t / 1000);
activity_progress(progress, cur_sec / 60, cur_sec % 60);
ctx->last_reported_progress = progress;

View File

@@ -118,6 +118,7 @@ struct lib_ccx_ctx
LLONG total_past; // Only in binary concat mode
int last_reported_progress;
LLONG min_global_timestamp_offset; // Track minimum (global - min) for multi-program TS
/* Stats */
int stat_numuserheaders;

View File

@@ -122,6 +122,8 @@ void parse_ebml(FILE *file)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -186,6 +188,8 @@ void parse_segment_info(FILE *file)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -484,6 +488,8 @@ void parse_segment_cluster_block_group(struct matroska_ctx *mkv_ctx, ULLONG clus
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -612,6 +618,8 @@ void parse_segment_cluster(struct matroska_ctx *mkv_ctx)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -845,6 +853,8 @@ void parse_segment_track_entry(struct matroska_ctx *mkv_ctx)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -1197,6 +1207,8 @@ void parse_segment_tracks(struct matroska_ctx *mkv_ctx)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
@@ -1241,6 +1253,8 @@ void parse_segment(struct matroska_ctx *mkv_ctx)
{
code <<= 8;
code += mkv_read_byte(file);
if (feof(file))
break;
code_len++;
switch (code)
{
@@ -1915,6 +1929,9 @@ void matroska_parse(struct matroska_ctx *mkv_ctx)
{
code <<= 8;
code += mkv_read_byte(file);
// Check for EOF after reading - feof() is only set after a failed read
if (feof(file))
break;
code_len++;
switch (code)

View File

@@ -14,7 +14,19 @@ void dinit_write(struct ccx_s_write *wb)
return;
}
if (wb->fh > 0)
{
// Check if the file is empty before closing
off_t file_size = lseek(wb->fh, 0, SEEK_END);
close(wb->fh);
// Delete empty output files to avoid generating useless 0-byte files
// This commonly happens with -12 option when one field has no captions
if (file_size == 0 && wb->filename != NULL)
{
unlink(wb->filename);
mprint("Deleted empty output file: %s\n", wb->filename);
}
}
freep(&wb->filename);
freep(&wb->original_filename);
if (wb->with_semaphore && wb->semaphore_filename)

View File

@@ -225,9 +225,6 @@ impl Timestamp {
let m = millis / 60000 - 60 * h;
let s = millis / 1000 - 3600 * h - 60 * m;
let u = millis - 3600000 * h - 60000 * m - 1000 * s;
if h > 24 {
println!("{h}")
}
Ok((h.try_into()?, m as u8, s as u8, u as u16))
}

View File

@@ -226,12 +226,10 @@ pub fn user_data_registered_itu_t_t35(ctx: &mut AvcContextRust, userbuf: &[u8])
}
// Save the data and process once we know the sequence number
if ((ctx.cc_count as usize + local_cc_count) * 3) + 1 > ctx.cc_databufsize {
let required_size = ((ctx.cc_count as usize + local_cc_count) * 3) + 1;
if required_size > ctx.cc_data.len() {
let new_size = ((ctx.cc_count as usize + local_cc_count) * 6) + 1;
unsafe {
ctx.cc_data.set_len(new_size);
}
ctx.cc_data.reserve(new_size);
ctx.cc_data.resize(new_size, 0);
ctx.cc_databufsize = new_size;
}

View File

@@ -1,8 +1,13 @@
use crate::bindings::{lib_ccx_ctx, list_head};
use lib_ccxr::common::{Codec, Decoder608Report, DecoderDtvccReport, StreamMode, StreamType};
use lib_ccxr::time::Timestamp;
use std::os::raw::c_void;
use std::ptr::null_mut;
extern "C" {
fn free(ptr: *mut c_void);
}
// Size of the Startbytes Array in CcxDemuxer - const 1MB
pub(crate) const ARRAY_SIZE: usize = 1024 * 1024;
@@ -109,7 +114,9 @@ impl Default for PSIBuffer {
fn default() -> Self {
PSIBuffer {
prev_ccounter: 0,
buffer: Box::into_raw(Box::new(0u8)),
// Initialize with null to avoid unnecessary heap allocations and
// signal that the buffer is currently empty.
buffer: std::ptr::null_mut(),
buffer_length: 0,
ccounter: 0,
}
@@ -274,21 +281,21 @@ impl Default for CcxDemuxer<'_> {
/// null pointers which are safely ignored.
impl Drop for CcxDemuxer<'_> {
fn drop(&mut self) {
// Free all non-null PSIBuffer pointers (Rust-owned from Box::into_raw)
// Free all non-null PSIBuffer pointers.
// These are freed using C's free to be compatible with memory that might be allocated by C.
for ptr in self.pid_buffers.drain(..) {
if !ptr.is_null() {
// SAFETY: These pointers were created via Box::into_raw in copy_demuxer_from_c_to_rust
unsafe {
drop(Box::from_raw(ptr));
free(ptr as *mut c_void);
}
}
}
// Free all non-null PMTEntry pointers (Rust-owned from Box::into_raw)
// Free all non-null PMTEntry pointers.
// These are freed using C's free to be compatible with memory that might be allocated by C.
for ptr in self.pids_programs.drain(..) {
if !ptr.is_null() {
// SAFETY: These pointers were created via Box::into_raw in copy_demuxer_from_c_to_rust
unsafe {
drop(Box::from_raw(ptr));
free(ptr as *mut c_void);
}
}
}

View File

@@ -331,10 +331,15 @@ unsafe fn detect_stream_type_common(ctx: &mut CcxDemuxer, ccx_options: &mut Opti
}
// Now check for PS (Needs PACK header)
// The loop below checks 4 consecutive bytes (i, i+1, i+2, i+3), so we need
// to stop 3 bytes before the end to avoid out-of-bounds access.
// - If buffer < 50000: limit = buffer_size - 3 (scan entire buffer)
// - If buffer >= 50000: limit = 49997 (= 50000 - 3, cap the scan range)
// We use saturating_sub to safely handle tiny buffers (< 3 bytes).
let limit = if ctx.startbytes_avail < 50000 {
ctx.startbytes_avail - 3
ctx.startbytes_avail.saturating_sub(3)
} else {
49997
50000 - 3 // Don't scan huge buffers entirely; 50KB is enough
} as usize;
for i in 0..limit {
if ctx.startbytes[i] == 0x00
@@ -427,15 +432,21 @@ pub fn is_valid_mp4_box(
)
);
// If the box type is "moov", check if it contains a valid movie header (mvhd)
if idx == 2
&& !(buffer[position + 12] == b'm'
// If the box type is "moov", it must contain "mvhd" to be valid.
// We need 16 bytes from position to check bytes 12-15 for "mvhd".
if idx == 2 {
if position + 16 > buffer.len() {
// Not enough bytes to verify mvhd - skip this box
continue;
}
if !(buffer[position + 12] == b'm'
&& buffer[position + 13] == b'v'
&& buffer[position + 14] == b'h'
&& buffer[position + 15] == b'd')
{
// If "moov" doesn't have "mvhd", skip it.
continue;
{
// moov without mvhd is not valid - skip it
continue;
}
}
// Box name matches. Do a crude validation of possible box size,

View File

@@ -7,7 +7,6 @@ use crate::demuxer::common_types::{
};
use lib_ccxr::common::{Codec, Options, StreamMode, StreamType};
use lib_ccxr::time::Timestamp;
use std::alloc::{alloc_zeroed, Layout};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
@@ -18,10 +17,12 @@ const POISON_PTR_PATTERN: usize = 0xcdcdcdcdcdcdcdcd;
#[cfg(target_pointer_width = "32")]
const POISON_PTR_PATTERN: usize = 0xcdcdcdcd;
// External C function declarations
extern "C" {
fn activity_input_file_closed();
fn close(fd: c_int) -> c_int;
fn malloc(size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
fn calloc(nmemb: usize, size: usize) -> *mut c_void;
}
pub fn copy_c_array_to_rust_vec(
@@ -98,61 +99,89 @@ pub unsafe fn copy_demuxer_from_rust_to_c(c_demuxer: *mut ccx_demuxer, rust_demu
c.global_timestamp_inited = rust_demuxer.global_timestamp_inited.millis() as c_int;
// PID buffers - extra defensive version
// We iterate through all possible PIDs (up to 8191 for PSI) to ensure state synchronization.
// CRITICAL: We must free existing pointers in the C structure before overwriting them
// to prevent massive memory leaks during the demuxing process, as this function
// is called repeatedly to sync state between Rust and C.
let pid_buffers_len = rust_demuxer.pid_buffers.len().min(8191);
for i in 0..pid_buffers_len {
let pid_buffer = rust_demuxer.pid_buffers[i];
if !pid_buffer.is_null() {
// Try to safely access the pointer
match std::panic::catch_unwind(|| unsafe { &*pid_buffer }) {
Ok(rust_psi) => {
let c_psi = unsafe { rust_psi.to_ctype() };
let c_ptr = Box::into_raw(Box::new(c_psi));
c.PID_buffers[i] = c_ptr;
}
Err(_) => {
// Pointer was invalid, set to null
eprintln!("Warning: Invalid PID buffer pointer at index {i}");
c.PID_buffers[i] = std::ptr::null_mut();
for i in 0..8191 {
// Free existing pointer if any.
// SAFETY: We use C's free to be compatible with memory that might be allocated by C.
// We also check for POISON_PTR_PATTERN for safety in debug builds.
if !c.PID_buffers[i].is_null() && c.PID_buffers[i] as usize != POISON_PTR_PATTERN {
unsafe {
free(c.PID_buffers[i] as *mut c_void);
c.PID_buffers[i] = std::ptr::null_mut();
}
}
if i < pid_buffers_len {
let pid_buffer = rust_demuxer.pid_buffers[i];
if !pid_buffer.is_null() {
// Try to safely access the pointer using catch_unwind to prevent
// a panic in Rust from crashing the entire C application.
// This is a defensive measure for FFI robustness.
match std::panic::catch_unwind(|| unsafe { &*pid_buffer }) {
Ok(rust_psi) => {
let c_psi = unsafe { rust_psi.to_ctype() };
let c_ptr =
unsafe { malloc(std::mem::size_of::<crate::bindings::PSI_buffer>()) }
as *mut crate::bindings::PSI_buffer;
if !c_ptr.is_null() {
unsafe {
std::ptr::write(c_ptr, c_psi);
}
c.PID_buffers[i] = c_ptr;
}
}
Err(_) => {
// Pointer was invalid, log and skip
eprintln!("Warning: Invalid PID buffer pointer at index {i}");
}
}
}
} else {
c.PID_buffers[i] = std::ptr::null_mut();
}
}
// Clear remaining slots if rust array is smaller than C array
for i in pid_buffers_len..8191 {
c.PID_buffers[i] = std::ptr::null_mut();
}
// PIDs programs - extra defensive version
// Similar to PID_buffers, we manage ownership of PMT entries.
// We check for POISON_PTR_PATTERN to avoid freeing uninitialized memory in debug builds.
let pids_programs_len = rust_demuxer.pids_programs.len().min(65536);
for i in 0..pids_programs_len {
let pmt_entry = rust_demuxer.pids_programs[i];
if !pmt_entry.is_null() {
// Try to safely access the pointer
match std::panic::catch_unwind(|| unsafe { &*pmt_entry }) {
Ok(rust_pmt) => {
let c_pmt = unsafe { rust_pmt.to_ctype() };
let c_ptr = Box::into_raw(Box::new(c_pmt));
c.PIDs_programs[i] = c_ptr;
}
Err(_) => {
// Pointer was invalid, set to null
eprintln!("Warning: Invalid PMT entry pointer at index {i}");
c.PIDs_programs[i] = std::ptr::null_mut();
for i in 0..65536 {
// Free existing pointer if any and it's not a poison pattern.
// SAFETY: We use C's free to be compatible with memory that might be allocated by C.
if !c.PIDs_programs[i].is_null() && c.PIDs_programs[i] as usize != POISON_PTR_PATTERN {
unsafe {
free(c.PIDs_programs[i] as *mut c_void);
c.PIDs_programs[i] = std::ptr::null_mut();
}
}
if i < pids_programs_len {
let pmt_entry = rust_demuxer.pids_programs[i];
if !pmt_entry.is_null() {
// Safely convert and move ownership to C
match std::panic::catch_unwind(|| unsafe { &*pmt_entry }) {
Ok(rust_pmt) => {
let c_pmt = unsafe { rust_pmt.to_ctype() };
let c_ptr =
unsafe { malloc(std::mem::size_of::<crate::bindings::PMT_entry>()) }
as *mut crate::bindings::PMT_entry;
if !c_ptr.is_null() {
unsafe {
std::ptr::write(c_ptr, c_pmt);
}
c.PIDs_programs[i] = c_ptr;
}
}
Err(_) => {
eprintln!("Warning: Invalid PMT entry pointer at index {i}");
}
}
}
} else {
c.PIDs_programs[i] = std::ptr::null_mut();
}
}
// Clear remaining slots if rust array is smaller than C array
for i in pids_programs_len..65536 {
c.PIDs_programs[i] = std::ptr::null_mut();
}
// PIDs seen array
for (i, &val) in rust_demuxer.pids_seen.iter().take(65536).enumerate() {
c.PIDs_seen[i] = val as c_int;
@@ -265,7 +294,15 @@ pub unsafe fn copy_demuxer_from_c_to_rust(ccx: *const ccx_demuxer) -> CcxDemuxer
if buffer_ptr.is_null() {
None
} else {
Some(Box::into_raw(Box::new(PSIBuffer::from_ctype(*buffer_ptr)?)))
let rust_item = PSIBuffer::from_ctype(*buffer_ptr)?;
let rust_ptr =
unsafe { malloc(std::mem::size_of::<PSIBuffer>()) } as *mut PSIBuffer;
if !rust_ptr.is_null() {
unsafe {
std::ptr::write(rust_ptr, rust_item);
}
}
Some(rust_ptr)
}
})
.collect::<Vec<_>>();
@@ -276,7 +313,14 @@ pub unsafe fn copy_demuxer_from_c_to_rust(ccx: *const ccx_demuxer) -> CcxDemuxer
if buffer_ptr.is_null() || buffer_ptr as usize == POISON_PTR_PATTERN {
None
} else {
Some(Box::into_raw(Box::new(PMTEntry::from_ctype(*buffer_ptr)?)))
let rust_item = PMTEntry::from_ctype(*buffer_ptr)?;
let rust_ptr = unsafe { malloc(std::mem::size_of::<PMTEntry>()) } as *mut PMTEntry;
if !rust_ptr.is_null() {
unsafe {
std::ptr::write(rust_ptr, rust_item);
}
}
Some(rust_ptr)
}
})
.collect::<Vec<_>>();
@@ -367,8 +411,7 @@ pub unsafe fn copy_demuxer_from_c_to_rust(ccx: *const ccx_demuxer) -> CcxDemuxer
///
/// This function is unsafe because we are calling a C struct and using alloc_zeroed to initialize it.
pub unsafe fn alloc_new_demuxer() -> *mut ccx_demuxer {
let layout = Layout::new::<ccx_demuxer>();
let ptr = alloc_zeroed(layout) as *mut ccx_demuxer;
let ptr = calloc(1, std::mem::size_of::<ccx_demuxer>()) as *mut ccx_demuxer;
if ptr.is_null() {
panic!("Failed to allocate memory for ccx_demuxer");