From 94095a5094dc54fa8e07538fc2573bc9a4ddfcee Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 12:56:40 +0530 Subject: [PATCH 01/34] xds module : boilerplate created --- src/lib_ccx/lib_ccx.h | 2 ++ src/rust/build.rs | 1 + src/rust/src/lib.rs | 1 + src/rust/src/libccxr_exports/time.rs | 2 +- src/rust/src/xds/mod.rs | 0 src/rust/wrapper.h | 1 + 6 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/rust/src/xds/mod.rs diff --git a/src/lib_ccx/lib_ccx.h b/src/lib_ccx/lib_ccx.h index 4a07a4f0d..5e68efab3 100644 --- a/src/lib_ccx/lib_ccx.h +++ b/src/lib_ccx/lib_ccx.h @@ -21,6 +21,8 @@ #include "avc_functions.h" #include "teletext.h" +#include "ccx_decoders_xds.h" + #ifdef WITH_LIBCURL #include #endif diff --git a/src/rust/build.rs b/src/rust/build.rs index 6cce8fc38..23bca6d63 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -63,6 +63,7 @@ fn main() { "ccx_encoding_type", "ccx_decoder_608_settings", "ccx_decoder_608_report", + "ccx_decoders_xds_context", "ccx_gxf", "MXFContext", "demuxer_data", diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index f4b838cda..330e561c1 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -29,6 +29,7 @@ pub mod libccxr_exports; pub mod parser; pub mod track_lister; pub mod utils; +pub mod xds; #[cfg(windows)] use std::os::windows::io::{FromRawHandle, RawHandle}; diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs index 82df6838c..348d67223 100644 --- a/src/rust/src/libccxr_exports/time.rs +++ b/src/rust/src/libccxr_exports/time.rs @@ -206,7 +206,7 @@ unsafe fn generate_timing_context(ctx: *const ccx_common_timing_ctx) -> TimingCo /// # Safety /// /// `ctx` should not be null. -unsafe fn write_back_to_common_timing_ctx( +pub unsafe fn write_back_to_common_timing_ctx( ctx: *mut ccx_common_timing_ctx, timing_ctx: &TimingContext, ) { diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/rust/wrapper.h b/src/rust/wrapper.h index 25b90c297..51ab5e80e 100644 --- a/src/rust/wrapper.h +++ b/src/rust/wrapper.h @@ -14,3 +14,4 @@ #include "../lib_ccx/ccx_gxf.h" #include "../lib_ccx/ccx_demuxer_mxf.h" #include "../lib_ccx/cc_bitstream.h" +#include "../lib_ccx/ccx_decoders_xds.h" \ No newline at end of file From f03ff26f4fd97e8a071d6a2db1d13f530506f06c Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett <71217129+steel-bucket@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:20:38 +0530 Subject: [PATCH 02/34] xds module : added datatypes(structs, enums) and C<->Rust data transfer library --- src/rust/src/xds/mod.rs | 1 + src/rust/src/xds/types.rs | 559 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 560 insertions(+) create mode 100644 src/rust/src/xds/types.rs diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index e69de29bb..cd408564e 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs new file mode 100644 index 000000000..609f5a92f --- /dev/null +++ b/src/rust/src/xds/types.rs @@ -0,0 +1,559 @@ +//! XDS (Extended Data Services) types and structures for decoding extended data. +//! +//! This module provides types for handling XDS packets which carry metadata about programs, +//! channels, and other broadcast information embedded in the vertical blanking interval. +//! +//! # Key Types +//! +//! - [`XdsClass`] - Classification of XDS packet (Current, Future, Channel, Misc, Private, OutOfBand) +//! - [`XdsType`] - Specific type within each class (PIN, Program Name, Content Advisory, etc.) +//! - [`XdsBuffer`] - Buffer for accumulating XDS packet bytes +//! - [`CcxDecodersXdsContext`] - Main context structure for XDS decoding state +//! +//! # Constants +//! +//! - [`NUM_XDS_BUFFERS`] - Maximum number of concurrent XDS buffers (9) +//! - [`NUM_BYTES_PER_PACKET`] - Maximum bytes per XDS packet (35) +//! - [`XDS_CLASSES`] - Human-readable names for XDS classes +//! - [`XDS_PROGRAM_TYPES`] - Program type descriptions (Education, Entertainment, etc.) +//! +//! # Conversion Guide +//! +//! | C (ccx_decoders_xds.c/.h) | Rust (types.rs) | +//! |---------------------------------------|---------------------------------------------------| +//! | `XDS_CLASS_*` constants | [`XdsClass`] enum | +//! | `XDS_TYPE_*` constants | [`XdsType`] enum variants | +//! | `struct xds_buffer` | [`XdsBuffer`] | +//! | `ccx_decoders_xds_context` | [`CcxDecodersXdsContext`] | +//! | `xds_class` (int) | [`XdsClass::from_c_int`], [`XdsClass::to_c_int`] | +//! | `xds_type` (int) | [`XdsType::from_c_int`], [`XdsType::to_c_int`] | +//! | `xds_program_type` array | [`XDS_PROGRAM_TYPES`] constant array | +//! | `xds_classes` array | [`XDS_CLASSES`] constant array | +//! | `clear_xds_buffer` | [`CcxDecodersXdsContext::clear_xds_buffer`] | +//! | `how_many_used` | [`CcxDecodersXdsContext::how_many_used`] | +//! | `process_xds_bytes` | [`CcxDecodersXdsContext::process_xds_bytes`] | +//! | `CcxDecodersXdsContext::from_ctype` | Convert from C `ccx_decoders_xds_context` | +//! | `copy_xds_context_from_rust_to_c` | Sync Rust context back to C struct | + +use crate::bindings::*; +use crate::common::CType; +use crate::ctorust::FromCType; +use crate::libccxr_exports::time::write_back_to_common_timing_ctx; +use lib_ccxr::time::TimingContext; +use std::os::raw::c_int; +use std::ptr::null_mut; + +pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero +pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe + +pub static XDS_CLASSES: [&str; 8] = [ + "Current", + "Future", + "Channel", + "Miscellaneous", + "Public service", + "Reserved", + "Private data", + "End", +]; + +pub static XDS_PROGRAM_TYPES: [&str; 96] = [ + "Education", + "Entertainment", + "Movie", + "News", + "Religious", + "Sports", + "Other", + "Action", + "Advertisement", + "Animated", + "Anthology", + "Automobile", + "Awards", + "Baseball", + "Basketball", + "Bulletin", + "Business", + "Classical", + "College", + "Combat", + "Comedy", + "Commentary", + "Concert", + "Consumer", + "Contemporary", + "Crime", + "Dance", + "Documentary", + "Drama", + "Elementary", + "Erotica", + "Exercise", + "Fantasy", + "Farm", + "Fashion", + "Fiction", + "Food", + "Football", + "Foreign", + "Fund-Raiser", + "Game/Quiz", + "Garden", + "Golf", + "Government", + "Health", + "High_School", + "History", + "Hobby", + "Hockey", + "Home", + "Horror", + "Information", + "Instruction", + "International", + "Interview", + "Language", + "Legal", + "Live", + "Local", + "Math", + "Medical", + "Meeting", + "Military", + "Mini-Series", + "Music", + "Mystery", + "National", + "Nature", + "Police", + "Politics", + "Premiere", + "Pre-Recorded", + "Product", + "Professional", + "Public", + "Racing", + "Reading", + "Repair", + "Repeat", + "Review", + "Romance", + "Science", + "Series", + "Service", + "Shopping", + "Soap_Opera", + "Special", + "Suspense", + "Talk", + "Technical", + "Tennis", + "Travel", + "Variety", + "Video", + "Weather", + "Western", +]; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsClass { + Current = 0, + Future = 1, + Channel = 2, + Misc = 3, + Public = 4, + Reserved = 5, + Private = 6, + End = 7, + OutOfBand = 0x40, +} + +impl XdsClass { + pub fn from_c_int(value: c_int) -> Option { + match value { + 0 => Some(XdsClass::Current), + 1 => Some(XdsClass::Future), + 2 => Some(XdsClass::Channel), + 3 => Some(XdsClass::Misc), + 4 => Some(XdsClass::Public), + 5 => Some(XdsClass::Reserved), + 6 => Some(XdsClass::Private), + 7 => Some(XdsClass::End), + 0x40 => Some(XdsClass::OutOfBand), + _ => None, + } + } + + pub fn to_c_int(&self) -> c_int { + match self { + XdsClass::Current => 0, + XdsClass::Future => 1, + XdsClass::Channel => 2, + XdsClass::Misc => 3, + XdsClass::Public => 4, + XdsClass::Reserved => 5, + XdsClass::Private => 6, + XdsClass::End => 7, + XdsClass::OutOfBand => 0x40, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsCurrentFutureType { + PinStartTime = 1, + LengthAndCurrentTime = 2, + ProgramName = 3, + ProgramType = 4, + ContentAdvisory = 5, + AudioServices = 6, + Cgms = 8, + AspectRatioInfo = 9, + ProgramDesc1 = 0x10, + ProgramDesc2 = 0x11, + ProgramDesc3 = 0x12, + ProgramDesc4 = 0x13, + ProgramDesc5 = 0x14, + ProgramDesc6 = 0x15, + ProgramDesc7 = 0x16, + ProgramDesc8 = 0x17, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsChannelType { + NetworkName = 1, + CallLettersAndChannel = 2, + Tsid = 4, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsMiscType { + TimeOfDay = 1, + LocalTimeZone = 4, + OutOfBandChannelNumber = 0x40, +} + +// this is new - was not there in my original code +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsType { + CurrentFuture(XdsCurrentFutureType), + Misc(XdsMiscType), + Channel(XdsChannelType), +} + +impl XdsType { + pub fn from_c_int(class: Option, type_value: c_int) -> Option { + match class? { + XdsClass::Current | XdsClass::Future => match type_value { + 1 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::PinStartTime)), + 2 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::LengthAndCurrentTime, + )), + 3 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)), + 4 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramType)), + 5 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::ContentAdvisory, + )), + 6 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::AudioServices)), + 8 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::Cgms)), + 9 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::AspectRatioInfo, + )), + 0x10 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc1)), + 0x11 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc2)), + 0x12 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc3)), + 0x13 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc4)), + 0x14 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc5)), + 0x15 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc6)), + 0x16 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc7)), + 0x17 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc8)), + _ => None, + }, + XdsClass::Channel => match type_value { + 1 => Some(XdsType::Channel(XdsChannelType::NetworkName)), + 2 => Some(XdsType::Channel(XdsChannelType::CallLettersAndChannel)), + 4 => Some(XdsType::Channel(XdsChannelType::Tsid)), + _ => None, + }, + XdsClass::Misc => match type_value { + 1 => Some(XdsType::Misc(XdsMiscType::TimeOfDay)), + 4 => Some(XdsType::Misc(XdsMiscType::LocalTimeZone)), + 0x40 => Some(XdsType::Misc(XdsMiscType::OutOfBandChannelNumber)), + _ => None, + }, + XdsClass::Public + | XdsClass::Reserved + | XdsClass::Private + | XdsClass::End + | XdsClass::OutOfBand => { + // These classes don't have specific types defined yet + // Return None for now + None + } + } + } + + pub fn to_c_int(&self) -> c_int { + match self { + XdsType::CurrentFuture(t) => match t { + XdsCurrentFutureType::PinStartTime => 1, + XdsCurrentFutureType::LengthAndCurrentTime => 2, + XdsCurrentFutureType::ProgramName => 3, + XdsCurrentFutureType::ProgramType => 4, + XdsCurrentFutureType::ContentAdvisory => 5, + XdsCurrentFutureType::AudioServices => 6, + XdsCurrentFutureType::Cgms => 8, + XdsCurrentFutureType::AspectRatioInfo => 9, + XdsCurrentFutureType::ProgramDesc1 => 0x10, + XdsCurrentFutureType::ProgramDesc2 => 0x11, + XdsCurrentFutureType::ProgramDesc3 => 0x12, + XdsCurrentFutureType::ProgramDesc4 => 0x13, + XdsCurrentFutureType::ProgramDesc5 => 0x14, + XdsCurrentFutureType::ProgramDesc6 => 0x15, + XdsCurrentFutureType::ProgramDesc7 => 0x16, + XdsCurrentFutureType::ProgramDesc8 => 0x17, + }, + XdsType::Channel(t) => match t { + XdsChannelType::NetworkName => 1, + XdsChannelType::CallLettersAndChannel => 2, + XdsChannelType::Tsid => 4, + }, + XdsType::Misc(t) => match t { + XdsMiscType::TimeOfDay => 1, + XdsMiscType::LocalTimeZone => 4, + XdsMiscType::OutOfBandChannelNumber => 0x40, + }, + } + } +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XdsBuffer { + pub in_use: u32, + pub xds_class: Option, + pub xds_type: Option, + pub bytes: [u8; NUM_BYTES_PER_PACKET], // Class + type (repeated for convenience) + data + zero + pub used_bytes: u8, +} + +impl Default for XdsBuffer { + fn default() -> Self { + XdsBuffer { + in_use: 0, + xds_class: None, + xds_type: None, + bytes: [0; NUM_BYTES_PER_PACKET], + used_bytes: 0, + } + } +} + +impl FromCType for XdsBuffer { + unsafe fn from_ctype(c_value: xds_buffer) -> Option { + let xds_class = if c_value.xds_class == -1 { + None + } else { + XdsClass::from_c_int(c_value.xds_class) + }; + + let xds_type = if c_value.xds_type == -1 { + None + } else { + XdsType::from_c_int(xds_class, c_value.xds_type) + }; + + Some(XdsBuffer { + in_use: c_value.in_use, + xds_class, + xds_type, + bytes: c_value.bytes, + used_bytes: c_value.used_bytes, + }) + } +} + +impl CType for XdsBuffer { + unsafe fn to_ctype(&self) -> xds_buffer { + xds_buffer { + in_use: self.in_use, + xds_class: self.xds_class.map(|c| c.to_c_int()).unwrap_or(-1), + xds_type: self.xds_type.map(|t| t.to_c_int()).unwrap_or(-1), + bytes: self.bytes, + used_bytes: self.used_bytes, + } + } +} + +#[repr(C)] +pub struct CcxDecodersXdsContext<'a> { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i32, + pub current_xds_hour: i32, + pub current_xds_date: i32, + pub current_xds_month: i32, + pub current_program_type_reported: i32, // No. + pub xds_start_time_shown: i32, + pub xds_program_length_shown: i32, + pub xds_program_description: [[i8; 33]; 8], + pub current_xds_network_name: [i8; 33], + pub current_xds_program_name: [i8; 33], + pub current_xds_call_letters: [i8; 7], + pub current_xds_program_type: [i8; 33], + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS], + pub cur_xds_buffer_idx: i32, + pub cur_xds_packet_class: i32, + pub cur_xds_payload: *mut u8, + pub cur_xds_payload_length: i32, + pub cur_xds_packet_type: i32, + pub timing: Option<&'a mut TimingContext>, + pub current_ar_start: u32, + pub current_ar_end: u32, + pub xds_write_to_file: bool, +} + +impl Default for CcxDecodersXdsContext<'_> { + fn default() -> Self { + CcxDecodersXdsContext { + current_xds_min: 0, + current_xds_hour: 0, + current_xds_date: 0, + current_xds_month: 0, + current_program_type_reported: 0, + xds_start_time_shown: 0, + xds_program_length_shown: 0, + xds_program_description: [[0; 33]; 8], + current_xds_network_name: [0; 33], + current_xds_program_name: [0; 33], + current_xds_call_letters: [0; 7], + current_xds_program_type: [0; 33], + xds_buffers: [XdsBuffer::default(); NUM_XDS_BUFFERS], + cur_xds_buffer_idx: 0, + cur_xds_packet_class: 0, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: None, + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: false, + } + } +} + +impl<'a> CcxDecodersXdsContext<'a> { + pub(crate) fn new(timing: &'a mut TimingContext, xds_write_to_file: i32) -> Box { + Box::new(Self { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + current_program_type_reported: 0, //No + xds_start_time_shown: 0, + xds_program_length_shown: 0, + + xds_program_description: [[0; 33]; 8], + + current_xds_network_name: [0; 33], + current_xds_program_name: [0; 33], + current_xds_call_letters: [0; 7], + current_xds_program_type: [0; 33], + + xds_buffers: [XdsBuffer::default(); NUM_XDS_BUFFERS], + + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), // unsafe raw pointer + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: Some(timing), + + current_ar_start: u32::MAX, // not set here + current_ar_end: u32::MAX, // not set here + + xds_write_to_file: xds_write_to_file != 0, + }) + } +} + +impl FromCType for CcxDecodersXdsContext<'_> { + unsafe fn from_ctype(c_value: ccx_decoders_xds_context) -> Option { + let mut xds_buffers = [XdsBuffer::default(); NUM_XDS_BUFFERS]; + + // Convert each xds_buffer from C to Rust + for (i, c_buffer) in c_value.xds_buffers.iter().enumerate() { + if let Some(rust_buffer) = XdsBuffer::from_ctype(*c_buffer) { + xds_buffers[i] = rust_buffer; + } + } + + Some(CcxDecodersXdsContext { + current_xds_min: c_value.current_xds_min, + current_xds_hour: c_value.current_xds_hour, + current_xds_date: c_value.current_xds_date, + current_xds_month: c_value.current_xds_month, + current_program_type_reported: c_value.current_program_type_reported, + xds_start_time_shown: c_value.xds_start_time_shown, + xds_program_length_shown: c_value.xds_program_length_shown, + xds_program_description: c_value.xds_program_description, + current_xds_network_name: c_value.current_xds_network_name, + current_xds_program_name: c_value.current_xds_program_name, + current_xds_call_letters: c_value.current_xds_call_letters, + current_xds_program_type: c_value.current_xds_program_type, + xds_buffers, + cur_xds_buffer_idx: c_value.cur_xds_buffer_idx, + cur_xds_packet_class: c_value.cur_xds_packet_class, + cur_xds_payload: c_value.cur_xds_payload, + cur_xds_payload_length: c_value.cur_xds_payload_length, + cur_xds_packet_type: c_value.cur_xds_packet_type, + timing: None, // Cannot directly convert raw pointer to reference - needs to be handled separately + current_ar_start: c_value.current_ar_start, + current_ar_end: c_value.current_ar_end, + xds_write_to_file: c_value.xds_write_to_file != 0, + }) + } +} + +pub unsafe fn copy_xds_context_from_rust_to_c( + bitstream_ptr: *mut ccx_decoders_xds_context, + rust_ctx: &CcxDecodersXdsContext<'_>, +) { + if bitstream_ptr.is_null() { + return; + } + let output = ccx_decoders_xds_context { + current_xds_min: rust_ctx.current_xds_min, + current_xds_hour: rust_ctx.current_xds_hour, + current_xds_date: rust_ctx.current_xds_date, + current_xds_month: rust_ctx.current_xds_month, + current_program_type_reported: rust_ctx.current_program_type_reported, + xds_start_time_shown: rust_ctx.xds_start_time_shown, + xds_program_length_shown: rust_ctx.xds_program_length_shown, + xds_program_description: rust_ctx.xds_program_description, + current_xds_network_name: rust_ctx.current_xds_network_name, + current_xds_program_name: rust_ctx.current_xds_program_name, + current_xds_call_letters: rust_ctx.current_xds_call_letters, + current_xds_program_type: rust_ctx.current_xds_program_type, + xds_buffers: rust_ctx.xds_buffers.map(|buf| buf.to_ctype()), + cur_xds_buffer_idx: rust_ctx.cur_xds_buffer_idx, + cur_xds_packet_class: rust_ctx.cur_xds_packet_class, + cur_xds_payload: rust_ctx.cur_xds_payload, + cur_xds_payload_length: rust_ctx.cur_xds_payload_length, + cur_xds_packet_type: rust_ctx.cur_xds_packet_type, + timing: null_mut(), + current_ar_start: rust_ctx.current_ar_start, + current_ar_end: rust_ctx.current_ar_end, + xds_write_to_file: if rust_ctx.xds_write_to_file { 1 } else { 0 }, + }; + std::ptr::write(bitstream_ptr, output); + if let Some(ref timing_ctx) = rust_ctx.timing { + write_back_to_common_timing_ctx((*bitstream_ptr).timing, timing_ctx); + } +} From 801ce5ecbf974cd2885d5a2dce703bc3496717c0 Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett <71217129+steel-bucket@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:26:28 +0530 Subject: [PATCH 03/34] xds module : added xdsprint(function) rust implementation --- src/rust/src/xds/handlers.rs | 113 +++++++++++++++++++++++++++++++++++ src/rust/src/xds/mod.rs | 1 + 2 files changed, 114 insertions(+) create mode 100644 src/rust/src/xds/handlers.rs diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs new file mode 100644 index 000000000..0f4cb0158 --- /dev/null +++ b/src/rust/src/xds/handlers.rs @@ -0,0 +1,113 @@ +#[allow(clippy::all)] +pub mod bindings { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +use std::ffi::CString; +use std::os::raw::{c_int, c_ulong, c_void}; +use std::ptr::null_mut; +use std::sync::atomic::{AtomicI64, Ordering}; +use std::sync::Mutex; + +use crate::bindings::{cc_subtitle, ccx_decoders_xds_context, eia608_screen, realloc}; + +use lib_ccxr::debug; +use lib_ccxr::info; +use lib_ccxr::time::c_functions::get_fts; +use lib_ccxr::time::CaptionField; +use lib_ccxr::util::log::{hex_dump, send_gui, DebugMessageFlag, GuiXdsMessage}; + +use crate::xds::types::*; + +static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet + // Usage example: + // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); + // let value = TS_START_OF_XDS.load(Ordering::SeqCst); + +/// Represents failures during XDS string writing or processing. +pub enum XDSError { + Err, +} + +/// Writes an XDS string to the subtitle output buffer. +/// +/// # Safety +/// - `sub.data` must be a valid pointer previously allocated by C's malloc/realloc, or null. +/// - The caller must ensure `sub` and `ctx` remain valid for the duration of the call. +/// - The returned `xds_str` pointer in the screen data must be freed with `CString::from_raw`. +pub unsafe fn write_xds_string( + sub: &mut cc_subtitle, + ctx: &mut CcxDecodersXdsContext, + p: String, + len: usize, + ts_start_of_xds: i64, +) -> Result<(), XDSError> { + let new_size = (sub.nb_data + 1) as usize * size_of::(); + let new_data = + unsafe { realloc(sub.data as *mut c_void, new_size as c_ulong) as *mut eia608_screen }; + if new_data.is_null() { + freep(&mut sub.data); + sub.nb_data = 0; + info!("No Memory left"); + return Err(XDSError::Err); + } + sub.data = new_data as *mut c_void; + sub.datatype = 0; + let data_element = &mut *new_data.add(sub.nb_data as usize); + let c_str = CString::new(p).map_err(|_| XDSError::Err)?; + let c_str_ptr = c_str.into_raw(); + data_element.format = 2; + data_element.start_time = ts_start_of_xds; + if let Some(timing) = ctx.timing.as_mut() { + data_element.end_time = get_fts(timing, CaptionField::Cea708).millis(); + } + data_element.xds_str = c_str_ptr; + data_element.xds_len = len; + data_element.cur_xds_packet_class = ctx.cur_xds_packet_class; + sub.nb_data += 1; + sub.type_ = 1; + sub.got_output = 1; + Ok(()) +} + +/// Prints an XDS message to the subtitle output if XDS file writing is enabled. +/// +/// # Safety +/// - Same safety requirements as `write_xds_string`. +/// - Relies on the global `TS_START_OF_XDS` atomic value being correctly set. +pub unsafe fn xdsprint( + sub: &mut cc_subtitle, + ctx: &mut CcxDecodersXdsContext, + message: String, +) -> Result<(), XDSError> { + if !ctx.xds_write_to_file { + return Ok(()); + } + + let len = message.len(); + write_xds_string( + sub, + ctx, + message, + len, + TS_START_OF_XDS.load(Ordering::SeqCst), + ) +} + +/// Frees a pointer and sets it to null. +/// +/// Converts the raw pointer back into a `Box` to deallocate the memory, +/// then sets the original pointer to null to prevent use-after-free. +/// +/// # Safety +/// - The pointer must have been originally allocated by Rust's `Box::into_raw` +/// or equivalent. Using this with C-allocated memory will cause undefined behavior. +/// - The pointer must not be used after this call. +pub fn freep(ptr: &mut *mut T) { + unsafe { + if !ptr.is_null() { + let _ = Box::from_raw(*ptr); + *ptr = null_mut(); + } + } +} diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index cd408564e..159962446 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -1 +1,2 @@ +mod handlers; pub mod types; From 5c168d03a79089d55e5aa717c89a7ad9ae6b22c1 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 20:32:46 +0530 Subject: [PATCH 04/34] xds module : added complete internal rust implementation --- src/rust/src/xds/handlers.rs | 1293 ++++++++++++++++++++++++++++++++++ 1 file changed, 1293 insertions(+) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 0f4cb0158..d344dd29f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -1,3 +1,32 @@ +//! XDS (Extended Data Services) handler functions for processing extended data packets. +//! +//! This module provides functions for handling XDS packets including content advisory, +//! copy generation management, program information, channel data, and miscellaneous metadata. +//! +//! # Conversion Guide +//! +//! | C (ccx_decoders_xds.c) | Rust (handlers.rs) | +//! |-------------------------------------------|-------------------------------------------------------| +//! | `write_xds_string` | [`write_xds_string`] | +//! | `xdsprint` | [`xdsprint`] | +//! | `xds_do_copy_generation_management_system`| [`xds_do_copy_generation_management_system`] | +//! | `xds_do_content_advisory` | [`xds_do_content_advisory`] | +//! | `xds_do_current_and_future` | [`xds_do_current_and_future`] | +//! | `xds_do_channel` | [`xds_do_channel`] | +//! | `xds_do_misc` | [`xds_do_misc`] | +//! | `xds_do_private_data` | [`xds_do_private_data`] | +//! | `do_end_of_xds` | [`do_end_of_xds`] | +//! | `xds_debug_test` | [`xds_debug_test`] | +//! | `xds_cea608_test` | [`xds_cea608_test`] | +//! | `XDS_TYPE_*` defines | [`XdsType`] enum or constants | +//! | `XDS_CLASS_*` defines | [`XdsClass`] enum | +//! | `ts_start_of_xds` global | Parameter or context field | +//! | `last_c1`, `last_c2` statics | Context fields or function-local state | +//! | `cc_subtitle` struct | [`cc_subtitle`] (C binding) | +//! | `eia608_screen` struct | [`eia608_screen`] (C binding) | +//! | `SFORMAT_XDS` | [`SFORMAT_XDS`] constant | +//! | `get_fts(ctx->timing, 2)` | [`TimingContext::get_fts`] | + #[allow(clippy::all)] pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); @@ -111,3 +140,1267 @@ pub fn freep(ptr: &mut *mut T) { } } } + +/// Utility methods for XDS buffer management and process_xds_bytes (function). +impl CcxDecodersXdsContext<'_> { + /// Count how many XDS buffers are currently in use + fn how_many_used(&self) -> usize { + self.xds_buffers + .iter() + .filter(|buf| buf.in_use != 0) + .count() + } +} + +/// XDS byte processing and packet handling. +impl CcxDecodersXdsContext<'_> { + pub(crate) fn process_xds_bytes(&mut self, hi: u8, lo: u8) { + if (hi >= 0x01 && hi <= 0x0f) { + let xds_class = ((hi - 1) / 2) as i32; // Start codes 1 and 2 are "class type" 0, 3-4 are 2, and so on. + let is_new = (hi % 2) != 0; // Start codes are even + + log::debug!( + "XDS Start: {}.{} Is new: {} | Class: {} ({}), Used buffers: {}", + hi, + lo, + is_new, + xds_class, + XDS_CLASSES.get(xds_class as usize).unwrap_or(&"Unknown"), + self.how_many_used() + ); + + let mut first_free_buf = -1i32; + let mut matching_buf = -1i32; + + for i in 0..NUM_XDS_BUFFERS { + if self.xds_buffers[i].in_use != 0 + && self.xds_buffers[i] + .xds_class + .map(|c| c.to_c_int()) + .unwrap_or(-1) + == xds_class + && self.xds_buffers[i] + .xds_type + .map(|t| t.to_c_int()) + .unwrap_or(-1) + == lo as i32 + { + matching_buf = i as i32; + break; + } + if first_free_buf == -1 && self.xds_buffers[i].in_use == 0 { + first_free_buf = i as i32; + } + } + + /* Here, 3 possibilities: + 1) We already had a buffer for this class/type and matching_buf points to it + 2) We didn't have a buffer for this class/type and first_free_buf points to an unused one + 3) All buffers are full and we will have to skip this packet. + */ + if matching_buf == -1 && first_free_buf == -1 { + log::info!( + "Note: All XDS buffers full (bug or suicidal stream). Ignoring this one ({},{}).", + xds_class, lo + ); + self.cur_xds_buffer_idx = -1; + return; + } + + self.cur_xds_buffer_idx = if matching_buf != -1 { + matching_buf + } else { + first_free_buf + }; + let idx = self.cur_xds_buffer_idx as usize; + + if is_new || self.xds_buffers[idx].in_use == 0 { + // Whatever we had before we discard; must belong to an interrupted packet + self.xds_buffers[idx].xds_class = XdsClass::from_c_int(xds_class); + self.xds_buffers[idx].xds_type = + XdsType::from_c_int(self.xds_buffers[idx].xds_class, lo as i32); + self.xds_buffers[idx].used_bytes = 0; + self.xds_buffers[idx].in_use = 1; + self.xds_buffers[idx].bytes = [0; NUM_BYTES_PER_PACKET]; + } + + if !is_new { + // Continue codes aren't added to packet. + return; + } + } else { + // Informational: 00, or 0x20-0x7F, so 01-0x1f forbidden + log::debug!( + "XDS: {:02X}.{:02X} ({}, {})", + hi, + lo, + hi as char, + lo as char + ); + + if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { + log::info!("\rNote: Illegal XDS data"); + return; + } + } + + if self.cur_xds_buffer_idx >= 0 { + let idx = self.cur_xds_buffer_idx as usize; + if idx < NUM_XDS_BUFFERS && (self.xds_buffers[idx].used_bytes as usize) <= 32 { + let pos = self.xds_buffers[idx].used_bytes as usize; + self.xds_buffers[idx].bytes[pos] = hi; + self.xds_buffers[idx].bytes[pos + 1] = lo; + self.xds_buffers[idx].used_bytes += 2; + if (self.xds_buffers[idx].used_bytes as usize) < NUM_BYTES_PER_PACKET { + self.xds_buffers[idx].bytes[self.xds_buffers[idx].used_bytes as usize] = 0; + } + } + } + } +} + +/// State for CGMS (Copy Generation Management System) caching +struct CgmsState { + last_c1: u32, + last_c2: u32, + copy_permitted: String, + aps: String, + rcd: String, +} + +impl Default for CgmsState { + fn default() -> Self { + CgmsState { + last_c1: u32::MAX, // equivalent to -1 for unsigned in C + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), + } + } +} + +/// Cached state for Copy Generation Management System (CGMS) to detect changes +static CGMS_STATE: Mutex = Mutex::new(CgmsState { + last_c1: u32::MAX, + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), +}); + +/// Copy Generation Management System text descriptions +const COPY_TEXT: [&str; 4] = [ + "Copy permitted (no restrictions)", + "No more copies (one generation copy has been made)", + "One generation of copies can be made", + "No copying is permitted", +]; + +/// APS (Analog Protection System) mode descriptions +const APS_TEXT: [&str; 4] = [ + "No APS", + "PSP On; Split Burst Off", + "PSP On; 2 line Split Burst On", + "PSP On; 4 line Split Burst On", +]; + +/// Decodes and outputs Copy Generation Management System (CGMS) data from XDS packets. +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - Both pointers must remain valid for the duration of the call +/// - The caller must ensure proper synchronization if accessed from multiple threads +pub unsafe fn xds_do_copy_generation_management_system( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u32, + c2: u32, +) { + // Extract bit fields from c1 + let c1_6 = (c1 & 0x40) >> 6; + // let _unused1 = (c1 & 0x20) >> 5; // unused in original + let cgms_a_b4 = (c1 & 0x10) >> 4; + let cgms_a_b3 = (c1 & 0x08) >> 3; + let aps_b2 = (c1 & 0x04) >> 2; + let aps_b1 = (c1 & 0x02) >> 1; + // let _asb_0 = c1 & 0x01; // unused in original + + // Extract bit fields from c2 + let c2_6 = (c2 & 0x40) >> 6; + // let _c2_5 = (c2 & 0x20) >> 5; // unused + // let _c2_4 = (c2 & 0x10) >> 4; // unused + // let _c2_3 = (c2 & 0x08) >> 3; // unused + // let _c2_2 = (c2 & 0x04) >> 2; // unused + // let _c2_1 = (c2 & 0x02) >> 1; // unused + let rcd0 = c2 & 0x01; + + // These bits must be high per specs + if c1_6 == 0 || c2_6 == 0 { + return; + } + + let mut state = match CGMS_STATE.lock() { + Ok(s) => s, + Err(_) => return, + }; + + let changed = if state.last_c1 != c1 || state.last_c2 != c2 { + state.last_c1 = c1; + state.last_c2 = c2; + + // Decode CGMS-A copy protection + let copy_idx = (cgms_a_b4 * 2 + cgms_a_b3) as usize; + state.copy_permitted = format!("CGMS: {}", COPY_TEXT[copy_idx]); + + // Decode APS (Analog Protection System) + let aps_idx = (aps_b2 * 2 + aps_b1) as usize; + state.aps = format!("APS: {}", APS_TEXT[aps_idx]); + + // Decode RCD (Redistribution Control Descriptor) + state.rcd = format!("Redistribution Control Descriptor: {}", rcd0); + + true + } else { + false + }; + + // Output via xdsprint + let _ = xdsprint(sub, ctx, state.copy_permitted.clone()); + let _ = xdsprint(sub, ctx, state.aps.clone()); + let _ = xdsprint(sub, ctx, state.rcd.clone()); + + // Log if changed + if changed { + info!("\rXDS: {}\n", state.copy_permitted); + info!("\rXDS: {}\n", state.aps); + info!("\rXDS: {}\n", state.rcd); + } + + // Debug output (always, when debug mask matches) + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state. copy_permitted); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state. aps); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); +} + +/// State for Content Advisory caching +struct ContentAdvisoryState { + last_c1: u32, + last_c2: u32, + age: String, + content: String, + rating: String, +} + +impl Default for ContentAdvisoryState { + fn default() -> Self { + ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), + } + } +} + +/// Cached state for Content Advisory to detect changes +static CONTENT_ADVISORY_STATE: Mutex = Mutex::new(ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), +}); + +/// US TV Parental Guidelines age ratings +const US_TV_AGE_TEXT: [&str; 8] = [ + "None", + "TV-Y (All Children)", + "TV-Y7 (Older Children)", + "TV-G (General Audience)", + "TV-PG (Parental Guidance Suggested)", + "TV-14 (Parents Strongly Cautioned)", + "TV-MA (Mature Audience Only)", + "None", +]; + +/// MPA rating text +const MPA_RATING_TEXT: [&str; 8] = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; + +/// Canadian English Language Rating +const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ + "Exempt", + "Children", + "Children eight years and older", + "General programming suitable for all audiences", + "Parental Guidance", + "Viewers 14 years and older", + "Adult Programming", + "[undefined]", +]; + +/// Canadian French Language Rating +const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ + "Exemptées", + "Général", + "Général - Déconseillé aux jeunes enfants", + "Cette émission peut ne pas convenir aux enfants de moins de 13 ans", + "Cette émission ne convient pas aux moins de 16 ans", + "Cette émission est réservée aux adultes", + "[invalid]", + "[invalid]", +]; + +/// Handles content advisory/rating information (US TV, MPA, Canadian ratings) +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - Both pointers must remain valid for the duration of the call +/// - The caller must ensure proper synchronization if accessed from multiple threads +pub unsafe fn xds_do_content_advisory( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u32, + c2: u32, +) { + // Extract bit fields from c1 (insane encoding per original comment) + let c1_6 = (c1 & 0x40) >> 6; + let da2 = (c1 & 0x20) >> 5; + let a1 = (c1 & 0x10) >> 4; + let a0 = (c1 & 0x08) >> 3; + let r2 = (c1 & 0x04) >> 2; + let r1 = (c1 & 0x02) >> 1; + let r0 = c1 & 0x01; + + // Extract bit fields from c2 + let c2_6 = (c2 & 0x40) >> 6; + let fv = (c2 & 0x20) >> 5; + let s = (c2 & 0x10) >> 4; + let la3 = (c2 & 0x08) >> 3; + let g2 = (c2 & 0x04) >> 2; + let g1 = (c2 & 0x02) >> 1; + let g0 = c2 & 0x01; + + // These bits must be high per specs + if c1_6 == 0 || c2_6 == 0 { + return; + } + + let mut state = match CONTENT_ADVISORY_STATE.lock() { + Ok(s) => s, + Err(_) => return, + }; + + let mut changed = false; + let mut supported = false; + + if state.last_c1 != c1 || state.last_c2 != c2 { + changed = true; + state.last_c1 = c1; + state.last_c2 = c2; + + // Bits a1 and a0 determine the encoding + if a1 == 0 && a0 != 0 { + // US TV parental guidelines + let age_idx = (g2 * 4 + g1 * 2 + g0) as usize; + state.age = format!( + "ContentAdvisory: US TV Parental Guidelines. Age Rating: {}", + US_TV_AGE_TEXT[age_idx] + ); + + // Build content string + let mut content_parts = Vec::new(); + + if g2 == 0 && g1 != 0 && g0 == 0 { + // For TV-Y7 (Older children), the Violence bit is "fantasy violence" + if fv != 0 { + content_parts.push("[Fantasy Violence] "); + } + } else { + // For all others, is real + if fv != 0 { + content_parts.push("[Violence] "); + } + } + + if s != 0 { + content_parts.push("[Sexual Situations] "); + } + if la3 != 0 { + content_parts.push("[Adult Language] "); + } + if da2 != 0 { + content_parts.push("[Sexually Suggestive Dialog] "); + } + + state.content = content_parts.join(""); // "" used instead of " " : to keep xds terminal(stdout) output same as that of c code + supported = true; + } + + if a0 == 0 { + // MPA + let rating_idx = (r2 * 4 + r1 * 2 + r0) as usize; + state.rating = format!( + "ContentAdvisory: MPA Rating: {}", + MPA_RATING_TEXT[rating_idx] + ); + supported = true; + } + + if a0 != 0 && a1 != 0 && da2 == 0 && la3 == 0 { + // Canadian English Language Rating + let rating_idx = (g2 * 4 + g1 * 2 + g0) as usize; + state.rating = format!( + "ContentAdvisory: Canadian English Rating: {}", + CANADIAN_ENGLISH_RATING_TEXT[rating_idx] + ); + supported = true; + } + + if a0 != 0 && a1 != 0 && da2 != 0 && la3 == 0 { + // Canadian French Language Rating + let rating_idx = (g2 * 4 + g1 * 2 + g0) as usize; + state.rating = format!( + "ContentAdvisory: Canadian French Rating: {}", + CANADIAN_FRENCH_RATING_TEXT[rating_idx] + ); + supported = true; + } + } + + // Output based on encoding type + // US TV parental guidelines + if a1 == 0 && a0 != 0 { + let _ = xdsprint(sub, ctx, state.age.clone()); + let _ = xdsprint(sub, ctx, state.content.clone()); + + if changed { + info!("\rXDS: {}\n ", state.age); + info!("\rXDS: {}\n ", state.content); + } + + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.age); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.content); + } + + // MPA, Canadian English, or Canadian French + if a0 == 0 + || (a0 != 0 && a1 != 0 && da2 == 0 && la3 == 0) + || (a0 != 0 && a1 != 0 && da2 != 0 && la3 == 0) + { + let _ = xdsprint(sub, ctx, state.rating.clone()); + + if changed { + info!("\rXDS: {}\n ", state.rating); + } + + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rating); + } + + if changed && !supported { + info!("XDS: Unsupported ContentAdvisory encoding, please submit sample.\n"); + } +} + +/// Handles current and future program info (PIN, length, name, type, descriptions) +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - `ctx.cur_xds_payload` must point to valid memory with at least `ctx.cur_xds_payload_length` bytes +/// - Both pointers must remain valid for the duration of the call +pub unsafe fn xds_do_current_and_future( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i32 { + let mut was_proc = 0; + + // Safety check for payload + if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + return -1; // CCX_EINVAL equivalent + } + + let payload = + std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); + + match ctx.cur_xds_packet_type { + // XDS_TYPE_PIN_START_TIME = 1 + 1 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 7 { + // We need 4 data bytes + return was_proc; + } + + let min = (payload[2] & 0x3f) as i32; // 6 bits + let hour = (payload[3] & 0x1f) as i32; // 5 bits + let date = (payload[4] & 0x1f) as i32; // 5 bits + let month = (payload[5] & 0x0f) as i32; // 4 bits + + if ctx.current_xds_min != min + || ctx.current_xds_hour != hour + || ctx.current_xds_date != date + || ctx.current_xds_month != month + { + ctx.xds_start_time_shown = 0; + ctx.current_xds_min = min; + ctx.current_xds_hour = hour; + ctx.current_xds_date = date; + ctx.current_xds_month = month; + } + + // XDS_CLASS_CURRENT = 0 + let class_str = if ctx.cur_xds_packet_class == 0 { + "Current" + } else { + "Future" + }; + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}\n", + class_str, date, month, hour, min + ); + + let pin_msg = format!( + "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}\n", + class_str, date, month, hour, min + ); + let _ = xdsprint(sub, ctx, pin_msg); + + // XDS_CLASS_CURRENT = 0 + if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == 0 { + info!("\rXDS: Program changed.\n"); + info!( + "XDS program start time (DD/MM HH:MM) {:02}-{:02} {:02}:{:02}\n", + date, month, hour, min + ); + send_gui(GuiXdsMessage::ProgramIdNr { + minute: ctx.current_xds_min as u8, + hour: ctx.current_xds_hour as u8, + date: ctx.current_xds_date as u8, + month: ctx.current_xds_month as u8, + }); + ctx.xds_start_time_shown = 1; + } + } + + // XDS_TYPE_LENGH_AND_CURRENT_TIME = 2 + 2 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need 2 data bytes + return was_proc; + } + + let min = (payload[2] & 0x3f) as i32; // 6 bits + let hour = (payload[3] & 0x1f) as i32; // 5 bits + + if ctx.xds_program_length_shown == 0 { + info!("\rXDS: Program length (HH:MM): {:02}:{:02} ", hour, min); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "\rXDS: Program length (HH:MM): {:02}:{:02} ", hour, min + ); + } + + let length_msg = format!("Program length (HH:MM): {:02}:{:02} ", hour, min); + let _ = xdsprint(sub, ctx, length_msg); + + if ctx.cur_xds_payload_length > 6 { + // Next two bytes (optional) available + let el_min = (payload[4] & 0x3f) as i32; // 6 bits + let el_hour = (payload[5] & 0x1f) as i32; // 5 bits + + if ctx.xds_program_length_shown == 0 { + info!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min + ); + } + + let elapsed_msg = format!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min); + let _ = xdsprint(sub, ctx, elapsed_msg); + } + + if ctx.cur_xds_payload_length > 8 { + // Next two bytes (optional) available + let el_sec = (payload[6] & 0x3f) as i32; // 6 bits + + if ctx.xds_program_length_shown == 0 { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + ":{:02}", el_sec + ); + } + + let elapsed_sec_msg = format!("Elapsed (SS) :{:02}", el_sec); + let _ = xdsprint(sub, ctx, elapsed_sec_msg); + } + + if ctx.xds_program_length_shown == 0 { + info!("\n"); + } else { + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\n"); + } + + ctx.xds_program_length_shown = 1; + } + + // XDS_TYPE_PROGRAM_NAME = 3 + 3 => { + was_proc = 1; + + // Extract program name from payload (bytes 2 to payload_length - 2) + let name_end = (ctx.cur_xds_payload_length - 1) as usize; + let name_bytes: Vec = payload[2..name_end] + .iter() + .copied() + .filter(|&b| b != 0) + .collect(); + + let xds_program_name = String::from_utf8_lossy(&name_bytes).to_string(); + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "\rXDS Program name: {}\n", xds_program_name + ); + + let name_msg = format!("Program name: {}", xds_program_name); + let _ = xdsprint(sub, ctx, name_msg); + + // Convert current_xds_program_name from i8 array to String for comparison + let current_name = i8_array_to_string(&ctx.current_xds_program_name); + + // XDS_CLASS_CURRENT = 0 + if ctx.cur_xds_packet_class == 0 && xds_program_name != current_name { + info!("\rXDS Notice: Program is now {}\n", xds_program_name); + string_to_i8_array(&xds_program_name, &mut ctx.current_xds_program_name); + send_gui(GuiXdsMessage::ProgramName(&xds_program_name)); + } + } + + // XDS_TYPE_PROGRAM_TYPE = 4 + 4 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need 2 data bytes + return was_proc; + } + + if ctx.current_program_type_reported != 0 { + // Check if we should do it again + let mut should_report = false; + for i in 0..ctx.cur_xds_payload_length as usize { + if i < 33 && payload[i] != ctx.current_xds_program_type[i] as u8 { + should_report = true; + break; + } + } + if should_report { + ctx.current_program_type_reported = 0; + } + } + + // Copy payload to current_xds_program_type + for i in 0..std::cmp::min(ctx.cur_xds_payload_length as usize, 32) { + ctx.current_xds_program_type[i] = payload[i] as i8; + } + if (ctx.cur_xds_payload_length as usize) < 33 { + ctx.current_xds_program_type[ctx.cur_xds_payload_length as usize] = 0; + } + + if ctx.current_program_type_reported == 0 { + info!("\rXDS Program Type: "); + } + + let mut type_str = String::new(); + + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = payload[i]; + if byte == 0 { + // Padding + continue; + } + + if ctx.current_program_type_reported == 0 { + info!("[{:02X}-", byte); + } + + if byte >= 0x20 && byte < 0x7F { + let type_idx = (byte - 0x20) as usize; + if type_idx < XDS_PROGRAM_TYPES.len() { + type_str.push_str(&format!("[{}] ", XDS_PROGRAM_TYPES[type_idx])); + } + } + + if ctx.current_program_type_reported == 0 { + if byte >= 0x20 && byte < 0x7F { + let type_idx = (byte - 0x20) as usize; + if type_idx < XDS_PROGRAM_TYPES.len() { + info!("{}", XDS_PROGRAM_TYPES[type_idx]); + } + } else { + info!("ILLEGAL VALUE"); + } + info!("] "); + } + } + + let type_msg = format!("Program type {}", type_str); + let _ = xdsprint(sub, ctx, type_msg); + + if ctx.current_program_type_reported == 0 { + info!("\n"); + } + + ctx.current_program_type_reported = 1; + } + + // XDS_TYPE_CONTENT_ADVISORY = 5 + 5 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need 2 data bytes + return was_proc; + } + xds_do_content_advisory(sub, ctx, payload[2] as u32, payload[3] as u32); + } + + // XDS_TYPE_AUDIO_SERVICES = 6 + 6 => { + was_proc = 1; + // No sample available - nothing to do + } + + // XDS_TYPE_CGMS = 8 + 8 => { + was_proc = 1; + xds_do_copy_generation_management_system( + sub, + ctx, + payload[2] as u32, + payload[3] as u32, + ); + } + + // XDS_TYPE_ASPECT_RATIO_INFO = 9 + 9 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need 2 data bytes + return was_proc; + } + + // Bit 6 must be 1 (note: C code checks bit 5, 0x20 = bit 5) + if (payload[2] & 0x20) == 0 || (payload[3] & 0x20) == 0 { + return was_proc; + } + + // CEA-608-B: The starting line is computed by adding 22 to the decimal number + // represented by bits S0 to S5. The ending line is computing by subtracting + // the decimal number represented by bits E0 to E5 from 262 + let ar_start = ((payload[2] & 0x1F) as u32) + 22; + let ar_end = 262 - ((payload[3] & 0x1F) as u32); + let active_picture_height = ar_end - ar_start; + let aspect_ratio = 320.0 / active_picture_height as f32; + + if ar_start != ctx.current_ar_start { + ctx.current_ar_start = ar_start; + ctx.current_ar_end = ar_end; + info!( + "\rXDS Notice: Aspect ratio info, start line={}, end line={}\n", + ar_start, ar_end + ); + info!( + "\rXDS Notice: Aspect ratio info, active picture height={}, ratio={}\n", + active_picture_height, aspect_ratio + ); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "\rXDS Notice: Aspect ratio info, start line={}, end line={}\n", + ar_start, ar_end + ); + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "\rXDS Notice: Aspect ratio info, active picture height={}, ratio={}\n", + active_picture_height, aspect_ratio + ); + } + } + + // XDS_TYPE_PROGRAM_DESC_1 through XDS_TYPE_PROGRAM_DESC_8 = 0x10-0x17 + 0x10..=0x17 => { + was_proc = 1; + + // Extract description from payload + let desc_end = (ctx.cur_xds_payload_length - 1) as usize; + let desc_bytes: Vec = payload[2..desc_end] + .iter() + .copied() + .filter(|&b| b != 0) + .collect(); + + let xds_desc = String::from_utf8_lossy(&desc_bytes).to_string(); + + if !xds_desc.is_empty() { + let line_num = ctx.cur_xds_packet_type - 0x10; // XDS_TYPE_PROGRAM_DESC_1 = 0x10 + + // Get current description for this line + let current_desc = if (line_num as usize) < 8 { + i8_array_to_string(&ctx.xds_program_description[line_num as usize]) + } else { + String::new() + }; + + let changed = xds_desc != current_desc; + + if changed { + info!("\rXDS description line {}: {}\n", line_num, xds_desc); + if (line_num as usize) < 8 { + string_to_i8_array( + &xds_desc, + &mut ctx.xds_program_description[line_num as usize], + ); + } + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "\rXDS description line {}: {}\n", line_num, xds_desc + ); + } + + let desc_msg = format!("XDS description line {}: {}", line_num, xds_desc); + let _ = xdsprint(sub, ctx, desc_msg); + + send_gui(GuiXdsMessage::ProgramDescription { + line_num, + desc: &xds_desc, + }); + } + } + + _ => { + // Unknown packet type + } + } + + was_proc +} + +/// Helper function to convert i8 array to String +fn i8_array_to_string(arr: &[i8]) -> String { + let bytes: Vec = arr + .iter() + .take_while(|&&b| b != 0) + .map(|&b| b as u8) + .collect(); + String::from_utf8_lossy(&bytes).to_string() +} + +/// Helper function to copy String into i8 array +fn string_to_i8_array(s: &str, arr: &mut [i8]) { + let bytes = s.as_bytes(); + let len = std::cmp::min(bytes.len(), arr.len() - 1); + for i in 0..len { + arr[i] = bytes[i] as i8; + } + if len < arr.len() { + arr[len] = 0; + } +} + +/// Processes channel-related XDS data (network name, call letters, TSID) +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - `ctx.cur_xds_payload` must point to valid memory with at least `ctx.cur_xds_payload_length` bytes +/// - Both pointers must remain valid for the duration of the call +pub unsafe fn xds_do_channel( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i32 { + let mut was_proc = 0; + + // Safety check for payload + if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + return -1; // CCX_EINVAL equivalent + } + + let payload = + std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); + + match ctx.cur_xds_packet_type { + // XDS_TYPE_NETWORK_NAME = 1 + 1 => { + was_proc = 1; + + // Extract network name from payload (bytes 2 to payload_length - 2) + let name_end = (ctx.cur_xds_payload_length - 1) as usize; + let name_bytes: Vec = payload[2..name_end] + .iter() + .copied() + .filter(|&b| b != 0) + .collect(); + + let xds_network_name = String::from_utf8_lossy(&name_bytes).to_string(); + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Network name: {}\n", xds_network_name + ); + + let network_msg = format!("Network: {}", xds_network_name); + let _ = xdsprint(sub, ctx, network_msg); + + // Convert current_xds_network_name from i8 array to String for comparison + let current_name = i8_array_to_string(&ctx.current_xds_network_name); + + if xds_network_name != current_name { + // Change of station + info!("XDS Notice: Network is now {}\n", xds_network_name); + string_to_i8_array(&xds_network_name, &mut ctx.current_xds_network_name); + } + } + + // XDS_TYPE_CALL_LETTERS_AND_CHANNEL = 2 + 2 => { + was_proc = 1; + + // We need 4-6 data bytes (payload_length 7 or 9) + if ctx.cur_xds_payload_length != 7 && ctx.cur_xds_payload_length != 9 { + return was_proc; + } + + // Extract call letters from payload (bytes 2 to payload_length - 2) + let letters_end = (ctx.cur_xds_payload_length - 1) as usize; + let letters_bytes: Vec = payload[2..letters_end] + .iter() + .copied() + .filter(|&b| b != 0) + .collect(); + + let xds_call_letters = String::from_utf8_lossy(&letters_bytes).to_string(); + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Network call letters: {}\n", xds_call_letters + ); + + let letters_msg = format!("Call Letters: {}", xds_call_letters); + let _ = xdsprint(sub, ctx, letters_msg); + + // Convert current_xds_call_letters from i8 array to String for comparison + let current_letters = i8_array_to_string(&ctx.current_xds_call_letters); + + if xds_call_letters != current_letters { + // Change of station + info!( + "XDS Notice: Network call letters now {}\n", + xds_call_letters + ); + string_to_i8_array(&xds_call_letters, &mut ctx.current_xds_call_letters); + send_gui(GuiXdsMessage::CallLetters(&xds_call_letters)); + } + } + + // XDS_TYPE_TSID = 4 + 4 => { + // According to CEA-608, data here (4 bytes) are used to identify the + // "originating analog licensee". No interesting data for us. + was_proc = 1; + + if ctx.cur_xds_payload_length < 7 { + // We need 4 data bytes + return was_proc; + } + + // Only low 4 bits from each byte + let b1 = (payload[2] & 0x10) as u32; + let b2 = (payload[3] & 0x10) as u32; + let b3 = (payload[4] & 0x10) as u32; + let b4 = (payload[5] & 0x10) as u32; + let tsid = (b4 << 12) | (b3 << 8) | (b2 << 4) | b1; + + if tsid != 0 { + let tsid_msg = format!("TSID: {}", tsid); + let _ = xdsprint(sub, ctx, tsid_msg); + } + } + + _ => { + // Unknown packet type + } + } + + was_proc +} + +/// Processes XDS Private Data class packets. +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - `ctx.cur_xds_payload` must point to valid memory with at least `ctx.cur_xds_payload_length` bytes +/// - Both pointers must remain valid for the duration of the call +pub unsafe fn xds_do_private_data( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i32 { + // Safety check for payload + if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + return -1; // CCX_EINVAL equivalent + } + + let payload = + std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); + + // Build hex string from payload bytes (bytes 2 to payload_length - 2) + let hex_str: String = payload[2..(ctx.cur_xds_payload_length - 1) as usize] + .iter() + .map(|b| format!("{:02X} ", b)) + .collect(); + + let _ = xdsprint(sub, ctx, hex_str); + + 1 +} + +/// Processes XDS Miscellaneous class packets. +/// +/// # Safety +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct +/// - `ctx.cur_xds_payload` must point to valid memory with at least `ctx.cur_xds_payload_length` bytes +/// - The pointer must remain valid for the duration of the call +pub unsafe fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> i32 { + let mut was_proc = 0; + + // Safety check for payload + if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + return -1; // CCX_EINVAL equivalent + } + + let payload = + std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); + + match ctx.cur_xds_packet_type { + // XDS_TYPE_TIME_OF_DAY = 1 + 1 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 9 { + // We need 6 data bytes + return was_proc; + } + + let min = (payload[2] & 0x3f) as i32; // 6 bits + let hour = (payload[3] & 0x1f) as i32; // 5 bits + let date = (payload[4] & 0x1f) as i32; // 5 bits + let month = (payload[5] & 0x0f) as i32; // 4 bits + let reset_seconds = (payload[5] & 0x20) != 0; + let day_of_week = (payload[6] & 0x07) as i32; + let year = ((payload[7] & 0x3f) as i32) + 1990; + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Time of day: (YYYY/MM/DD) {:04}/{:02}/{:02} (HH:SS) {:02}:{:02} DoW: {} Reset seconds: {}\n", + year, month, date, hour, min, day_of_week, if reset_seconds { 1 } else { 0 } + ); + } + + // XDS_TYPE_LOCAL_TIME_ZONE = 4 + 4 => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need 2 data bytes + return was_proc; + } + + // let b6 = (payload[2] & 0x40) >> 6; // Bit 6 should always be 1 + let dst = (payload[2] & 0x20) >> 5; // Daylight Saving Time + let hour = (payload[2] & 0x1f) as i32; // 5 bits + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Local Time Zone: {:02} DST: {}\n", + hour, dst + ); + } + + _ => { + was_proc = 0; + } + } + + was_proc +} + +/// Processes the end of an XDS packet, validates checksum, and dispatches to appropriate handler. +/// +/// # Safety +/// This function is marked unsafe because it dereferences raw pointers from +/// the C bindings in `cc_subtitle`. The caller must ensure: +/// * `sub` points to a valid, properly initialized `cc_subtitle` structure +/// * The `data` field in `sub` is either null or points to valid memory +/// * No other code is concurrently modifying the subtitle structure +pub unsafe fn do_end_of_xds( + sub: &mut crate::bindings::cc_subtitle, + ctx: &mut CcxDecodersXdsContext, + expected_checksum: u8, +) { + // Check if buffer index is valid and in use + if ctx.cur_xds_buffer_idx == -1 { + return; + } + + let idx = ctx.cur_xds_buffer_idx as usize; + if idx >= ctx.xds_buffers.len() || ctx.xds_buffers[idx].in_use == 0 { + return; + } + + // Set up context from buffer + ctx.cur_xds_packet_class = ctx.xds_buffers[idx] + .xds_class + .map(|c| c as i32) + .unwrap_or(-1); + ctx.cur_xds_payload = ctx.xds_buffers[idx].bytes.as_mut_ptr(); + ctx.cur_xds_payload_length = ctx.xds_buffers[idx].used_bytes as i32; + + // Get packet type from payload[1] + if ctx.cur_xds_payload_length >= 2 { + ctx.cur_xds_packet_type = ctx.xds_buffers[idx].bytes[1] as i32; + } + + // Add the end byte (0x0F) to the packet + if (ctx.cur_xds_payload_length as usize) < NUM_BYTES_PER_PACKET { + ctx.xds_buffers[idx].bytes[ctx.cur_xds_payload_length as usize] = 0x0F; + ctx.cur_xds_payload_length += 1; + ctx.xds_buffers[idx].used_bytes = ctx.cur_xds_payload_length as u8; + } + + // Calculate checksum + let mut cs: i32 = 0; + for i in 0..ctx.cur_xds_payload_length as usize { + let byte = ctx.xds_buffers[idx].bytes[i]; + cs = (cs + byte as i32) & 0x7f; // Keep 7 bits only + let c = byte & 0x7F; + let display_char = if c >= 0x20 { c as char } else { '?' }; + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "{:02X} - {} cs: {:02X}\n", c, display_char, cs + ); + } + cs = (128 - cs) & 0x7F; // Convert to 2's complement & discard high-order bit + + // Get class name for debug output + let class_name = if (ctx.cur_xds_packet_class as usize) < XDS_CLASSES.len() { + XDS_CLASSES[ctx.cur_xds_packet_class as usize] + } else { + "Unknown" + }; + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "End of XDS. Class={} ({}), size={} Checksum OK: {} Used buffers: {}\n", + ctx.cur_xds_packet_class, + class_name, + ctx. cur_xds_payload_length, + cs == expected_checksum as i32, + ctx.xds_buffers. iter().filter(|b| b.in_use != 0).count() + ); + + // Validate checksum and minimum length + if cs != expected_checksum as i32 || ctx.cur_xds_payload_length < 3 { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Expected checksum: {:02X} Calculated: {:02X}\n", expected_checksum, cs + ); + ctx.xds_buffers[idx] = XdsBuffer::default(); // clear_xds_buffer + return; // Bad packets ignored as per specs + } + + let mut was_proc = 0; + + // Check if bit 6 is set for out-of-band data + if (ctx.cur_xds_packet_type & 0x40) != 0 { + ctx.cur_xds_packet_class = XdsClass::OutOfBand as i32; + } + + // Convert cur_xds_packet_class to XdsClass for matching + let xds_class = match ctx.cur_xds_packet_class { + x if x == XdsClass::Current as i32 => Some(XdsClass::Current), + x if x == XdsClass::Future as i32 => Some(XdsClass::Future), + x if x == XdsClass::Channel as i32 => Some(XdsClass::Channel), + x if x == XdsClass::Misc as i32 => Some(XdsClass::Misc), + x if x == XdsClass::Private as i32 => Some(XdsClass::Private), + x if x == XdsClass::OutOfBand as i32 => Some(XdsClass::OutOfBand), + _ => None, + }; + + match xds_class { + Some(XdsClass::Future) => { + // Info on future program + // Check if debug mask includes DECODER_XDS + if let Some(logger) = lib_ccxr::util::log::logger() { + if !logger.is_debug_mode() { + // Don't bother processing something we don't need + was_proc = 1; + } else { + // Fall through to current processing + was_proc = xds_do_current_and_future(sub, ctx); + } + } else { + was_proc = 1; + } + } + + Some(XdsClass::Current) => { + // Info on current program + was_proc = xds_do_current_and_future(sub, ctx); + } + + Some(XdsClass::Channel) => { + was_proc = xds_do_channel(sub, ctx); + } + + Some(XdsClass::Misc) => { + was_proc = xds_do_misc(ctx); + } + + Some(XdsClass::Private) => { + // CEA-608: The Private Data Class is for use in any closed system + // for whatever that system wishes. It shall not be defined by this + // standard now or in the future. + was_proc = xds_do_private_data(sub, ctx); + } + + Some(XdsClass::OutOfBand) => { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Out-of-band data, ignored." + ); + was_proc = 1; + } + + _ => { + // Unknown class + } + } + + if was_proc == 0 { + info!("Note: We found a currently unsupported XDS packet.\n"); + // Dump the payload for debugging + let payload_slice = &ctx.xds_buffers[idx].bytes[0..ctx.cur_xds_payload_length as usize]; + hex_dump(DebugMessageFlag::DECODER_XDS, payload_slice, false); + } + + // Clear the buffer + ctx.xds_buffers[idx] = XdsBuffer::default(); +} From 32e3f202719554de8af1c337a12e613605cdf271 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 20:45:03 +0530 Subject: [PATCH 05/34] xds module : added ffi implementation --- src/lib_ccx/ccx_decoders_xds.c | 20 ++++++++ src/rust/src/xds/mod.rs | 84 +++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 529a32df4..62aebd6a4 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -4,6 +4,18 @@ #include "ccx_common_common.h" #include "utility.h" +// declare rust implementation of process_xds_bytes (function) +extern void ccxr_process_xds_bytes( + struct ccx_decoders_xds_context *ctx, + unsigned char hi, + int lo); + +// declare rust implementation of do_end_of_xds (function) +extern void ccxr_do_end_of_xds( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + unsigned char expected_checksum); + LLONG ts_start_of_xds = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet static const char *XDSclasses[] = @@ -238,6 +250,10 @@ void clear_xds_buffer(struct ccx_decoders_xds_context *ctx, int num) void process_xds_bytes(struct ccx_decoders_xds_context *ctx, const unsigned char hi, int lo) { +#ifndef DISABLE_RUST + ccxr_process_xds_bytes(ctx, hi, lo); // use the rust implementation + return; +#endif int is_new; if (!ctx) return; @@ -890,6 +906,10 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) void do_end_of_xds(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum) { +#ifndef DISABLE_RUST + ccxr_do_end_of_xds(sub, ctx, expected_checksum); // use the rust implementation + return; +#endif int cs = 0; int i; diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 159962446..47918d2f9 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -1,2 +1,84 @@ -mod handlers; +//! XDS (Extended Data Services) decoder module for processing CEA-608 extended data. +//! +//! This module provides Rust implementations for decoding XDS packets embedded in +//! closed caption streams, including program information, content ratings, network +//! identification, and time-of-day data. +//! +//! # Submodules +//! +//! - [`handlers`] - XDS packet processing and dispatch functions +//! - [`types`] - XDS-specific types, enums, and constants +//! +//! For detailed function-level mappings, see the [`handlers`] module documentation +//! For type definitions, see the [`types`] module + +pub mod handlers; pub mod types; + +use crate::bindings::*; +use crate::ctorust::FromCType; +use crate::xds::handlers::do_end_of_xds; +use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; +use std::os::raw::c_int; + +/// FFI wrapper for `do_end_of_xds`. +/// +/// Finalizes XDS packet processing, validates checksum, and dispatches to appropriate handler. +/// +/// # Safety +/// - `sub` must be a valid, non-null pointer to a `cc_subtitle` struct. +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct. +/// - The caller must ensure both pointers remain valid for the duration of the call. +#[no_mangle] +pub unsafe extern "C" fn ccxr_do_end_of_xds( + sub: *mut cc_subtitle, + ctx: *mut ccx_decoders_xds_context, + expected_checksum: u8, +) { + // Null pointer checks + if sub.is_null() || ctx.is_null() { + return; + } + + // Convert C context to Rust + let mut rust_ctx = match CcxDecodersXdsContext::from_ctype(*ctx) { + Some(c) => c, + None => return, + }; + + // Call the Rust implementation + do_end_of_xds(&mut *sub, &mut rust_ctx, expected_checksum); + + // Write changes back to C context + copy_xds_context_from_rust_to_c(ctx, &rust_ctx); +} + +/// FFI wrapper for `process_xds_bytes`. +/// +/// Processes a pair of XDS data bytes, managing buffer allocation and packet state. +/// +/// # Safety +/// - `ctx` must be a valid, non-null pointer to a `ccx_decoders_xds_context` struct. +/// - The caller must ensure the pointer remains valid for the duration of the call. +#[no_mangle] +pub unsafe extern "C" fn ccxr_process_xds_bytes( + ctx: *mut ccx_decoders_xds_context, + hi: u8, + lo: c_int, +) { + if ctx.is_null() { + return; + } + + // Convert C context to Rust + let mut rust_ctx = match CcxDecodersXdsContext::from_ctype(*ctx) { + Some(c) => c, + None => return, + }; + + // Process in Rust + rust_ctx.process_xds_bytes(hi, lo as u8); + + // Write changes back to C + copy_xds_context_from_rust_to_c(ctx, &rust_ctx); +} From e81a5f3cec8bc892690db03ee51b43aba898b74e Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 11:43:17 +0530 Subject: [PATCH 06/34] xds module : fixes requested : 1, 2, 3, 4 --- src/lib_ccx/ccx_decoders_xds.c | 6 +++--- src/rust/Cargo.lock | 1 + src/rust/Cargo.toml | 1 + src/rust/src/xds/handlers.rs | 30 ++++++++++++++++++++++-------- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 62aebd6a4..8cc5fefe8 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -6,9 +6,9 @@ // declare rust implementation of process_xds_bytes (function) extern void ccxr_process_xds_bytes( - struct ccx_decoders_xds_context *ctx, - unsigned char hi, - int lo); + struct ccx_decoders_xds_context *ctx, + unsigned char hi, + int lo); // declare rust implementation of do_end_of_xds (function) extern void ccxr_do_end_of_xds( diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 88edb6003..415a36e0f 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -184,6 +184,7 @@ dependencies = [ "env_logger", "leptonica-sys", "lib_ccxr", + "libc", "log", "num-integer", "palette", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 34b704122..de40de590 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -25,6 +25,7 @@ lib_ccxr = { path = "lib_ccxr" } url = "2.5.4" serial_test = "3.2.0" encoding_rs = "0.8.35" +libc = "0.2.178" # Use CCExtractor's forked rsmpeg with FFmpeg 7 # All platforms use ffmpeg7 feature with prebuilt bindings for API consistency diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index d344dd29f..ad594244d 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -33,12 +33,12 @@ pub mod bindings { } use std::ffi::CString; -use std::os::raw::{c_int, c_ulong, c_void}; +use std::os::raw::{c_ulong, c_void}; use std::ptr::null_mut; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Mutex; -use crate::bindings::{cc_subtitle, ccx_decoders_xds_context, eia608_screen, realloc}; +use crate::bindings::{cc_subtitle, eia608_screen, realloc}; use lib_ccxr::debug; use lib_ccxr::info; @@ -46,6 +46,9 @@ use lib_ccxr::time::c_functions::get_fts; use lib_ccxr::time::CaptionField; use lib_ccxr::util::log::{hex_dump, send_gui, DebugMessageFlag, GuiXdsMessage}; +use libc::free; +use libc::malloc; + use crate::xds::types::*; static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet @@ -73,7 +76,7 @@ pub unsafe fn write_xds_string( ) -> Result<(), XDSError> { let new_size = (sub.nb_data + 1) as usize * size_of::(); let new_data = - unsafe { realloc(sub.data as *mut c_void, new_size as c_ulong) as *mut eia608_screen }; + unsafe { realloc(sub.data as *mut c_void, new_size as u64) as *mut eia608_screen }; if new_data.is_null() { freep(&mut sub.data); sub.nb_data = 0; @@ -84,13 +87,22 @@ pub unsafe fn write_xds_string( sub.datatype = 0; let data_element = &mut *new_data.add(sub.nb_data as usize); let c_str = CString::new(p).map_err(|_| XDSError::Err)?; - let c_str_ptr = c_str.into_raw(); + + let len = c_str.as_bytes_with_nul().len(); + let ptr = unsafe { malloc(len) as *mut i8 }; // fixing c/rust mem mismatch + if ptr.is_null() { + return Err(XDSError::Err); + } + unsafe { + std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, len); // fixing c/rust mem mismatch + } + data_element.format = 2; data_element.start_time = ts_start_of_xds; if let Some(timing) = ctx.timing.as_mut() { data_element.end_time = get_fts(timing, CaptionField::Cea708).millis(); } - data_element.xds_str = c_str_ptr; + data_element.xds_str = ptr; data_element.xds_len = len; data_element.cur_xds_packet_class = ctx.cur_xds_packet_class; sub.nb_data += 1; @@ -135,7 +147,7 @@ pub unsafe fn xdsprint( pub fn freep(ptr: &mut *mut T) { unsafe { if !ptr.is_null() { - let _ = Box::from_raw(*ptr); + free(*ptr as *mut libc::c_void); *ptr = null_mut(); } } @@ -155,7 +167,7 @@ impl CcxDecodersXdsContext<'_> { /// XDS byte processing and packet handling. impl CcxDecodersXdsContext<'_> { pub(crate) fn process_xds_bytes(&mut self, hi: u8, lo: u8) { - if (hi >= 0x01 && hi <= 0x0f) { + if hi >= 0x01 && hi <= 0x0f { let xds_class = ((hi - 1) / 2) as i32; // Start codes 1 and 2 are "class type" 0, 3-4 are 2, and so on. let is_new = (hi % 2) != 0; // Start codes are even @@ -575,7 +587,9 @@ pub unsafe fn xds_do_content_advisory( // US TV parental guidelines if a1 == 0 && a0 != 0 { let _ = xdsprint(sub, ctx, state.age.clone()); - let _ = xdsprint(sub, ctx, state.content.clone()); + if !state.content.is_empty() { + let _ = xdsprint(sub, ctx, state.content.clone()); + } if changed { info!("\rXDS: {}\n ", state.age); From 4e252c20c73ba69f8de222dc718cb9e932a7f4cd Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 12:13:17 +0530 Subject: [PATCH 07/34] xds module : fix clippy warnings --- src/rust/src/xds/handlers.rs | 58 +++++++++++++++++------------------- src/rust/src/xds/types.rs | 45 +++++++--------------------- 2 files changed, 37 insertions(+), 66 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index ad594244d..0ad895144 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -33,7 +33,7 @@ pub mod bindings { } use std::ffi::CString; -use std::os::raw::{c_ulong, c_void}; +use std::os::raw::c_void; use std::ptr::null_mut; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Mutex; @@ -71,7 +71,6 @@ pub unsafe fn write_xds_string( sub: &mut cc_subtitle, ctx: &mut CcxDecodersXdsContext, p: String, - len: usize, ts_start_of_xds: i64, ) -> Result<(), XDSError> { let new_size = (sub.nb_data + 1) as usize * size_of::(); @@ -125,14 +124,7 @@ pub unsafe fn xdsprint( return Ok(()); } - let len = message.len(); - write_xds_string( - sub, - ctx, - message, - len, - TS_START_OF_XDS.load(Ordering::SeqCst), - ) + write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } /// Frees a pointer and sets it to null. @@ -167,9 +159,9 @@ impl CcxDecodersXdsContext<'_> { /// XDS byte processing and packet handling. impl CcxDecodersXdsContext<'_> { pub(crate) fn process_xds_bytes(&mut self, hi: u8, lo: u8) { - if hi >= 0x01 && hi <= 0x0f { + if (0x01..=0x0f).contains(&hi) { let xds_class = ((hi - 1) / 2) as i32; // Start codes 1 and 2 are "class type" 0, 3-4 are 2, and so on. - let is_new = (hi % 2) != 0; // Start codes are even + let is_new = !hi.is_multiple_of(2); // Start codes are even log::debug!( "XDS Start: {}.{} Is new: {} | Class: {} ({}), Used buffers: {}", @@ -601,10 +593,7 @@ pub unsafe fn xds_do_content_advisory( } // MPA, Canadian English, or Canadian French - if a0 == 0 - || (a0 != 0 && a1 != 0 && da2 == 0 && la3 == 0) - || (a0 != 0 && a1 != 0 && da2 != 0 && la3 == 0) - { + if a0 == 0 || (a1 != 0 && la3 == 0) { let _ = xdsprint(sub, ctx, state.rating.clone()); if changed { @@ -810,21 +799,26 @@ pub unsafe fn xds_do_current_and_future( if ctx.current_program_type_reported != 0 { // Check if we should do it again - let mut should_report = false; - for i in 0..ctx.cur_xds_payload_length as usize { - if i < 33 && payload[i] != ctx.current_xds_program_type[i] as u8 { - should_report = true; - break; - } - } + + let should_report = payload + .iter() + .zip(ctx.current_xds_program_type.iter()) + .take(33.min(ctx.cur_xds_payload_length as usize)) + .any(|(p, c)| *p != *c as u8); + if should_report { ctx.current_program_type_reported = 0; } } // Copy payload to current_xds_program_type - for i in 0..std::cmp::min(ctx.cur_xds_payload_length as usize, 32) { - ctx.current_xds_program_type[i] = payload[i] as i8; + for (dst, src) in ctx + .current_xds_program_type + .iter_mut() + .zip(payload.iter()) + .take(std::cmp::min(ctx.cur_xds_payload_length as usize, 32)) + { + *dst = *src as i8; } if (ctx.cur_xds_payload_length as usize) < 33 { ctx.current_xds_program_type[ctx.cur_xds_payload_length as usize] = 0; @@ -836,10 +830,12 @@ pub unsafe fn xds_do_current_and_future( let mut type_str = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = payload[i]; + for &byte in payload + .iter() + .take((ctx.cur_xds_payload_length - 1) as usize) + .skip(2) + { if byte == 0 { - // Padding continue; } @@ -847,7 +843,7 @@ pub unsafe fn xds_do_current_and_future( info!("[{:02X}-", byte); } - if byte >= 0x20 && byte < 0x7F { + if (0x20..0x7F).contains(&byte) { let type_idx = (byte - 0x20) as usize; if type_idx < XDS_PROGRAM_TYPES.len() { type_str.push_str(&format!("[{}] ", XDS_PROGRAM_TYPES[type_idx])); @@ -855,7 +851,7 @@ pub unsafe fn xds_do_current_and_future( } if ctx.current_program_type_reported == 0 { - if byte >= 0x20 && byte < 0x7F { + if (0x20..0x7F).contains(&byte) { let type_idx = (byte - 0x20) as usize; if type_idx < XDS_PROGRAM_TYPES.len() { info!("{}", XDS_PROGRAM_TYPES[type_idx]); @@ -1195,7 +1191,7 @@ pub unsafe fn xds_do_private_data( /// - `ctx.cur_xds_payload` must point to valid memory with at least `ctx.cur_xds_payload_length` bytes /// - The pointer must remain valid for the duration of the call pub unsafe fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> i32 { - let mut was_proc = 0; + let was_proc; // Safety check for payload if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 609f5a92f..6bfe7f9d0 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -448,41 +448,6 @@ impl Default for CcxDecodersXdsContext<'_> { } } -impl<'a> CcxDecodersXdsContext<'a> { - pub(crate) fn new(timing: &'a mut TimingContext, xds_write_to_file: i32) -> Box { - Box::new(Self { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - current_program_type_reported: 0, //No - xds_start_time_shown: 0, - xds_program_length_shown: 0, - - xds_program_description: [[0; 33]; 8], - - current_xds_network_name: [0; 33], - current_xds_program_name: [0; 33], - current_xds_call_letters: [0; 7], - current_xds_program_type: [0; 33], - - xds_buffers: [XdsBuffer::default(); NUM_XDS_BUFFERS], - - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), // unsafe raw pointer - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing: Some(timing), - - current_ar_start: u32::MAX, // not set here - current_ar_end: u32::MAX, // not set here - - xds_write_to_file: xds_write_to_file != 0, - }) - } -} - impl FromCType for CcxDecodersXdsContext<'_> { unsafe fn from_ctype(c_value: ccx_decoders_xds_context) -> Option { let mut xds_buffers = [XdsBuffer::default(); NUM_XDS_BUFFERS]; @@ -521,6 +486,16 @@ impl FromCType for CcxDecodersXdsContext<'_> { } } +/// # Safety +/// +/// - `bitstream_ptr` must be non-null and point to uniquely writable memory for a +/// `ccx_decoders_xds_context` for the duration of the call. +/// - `rust_ctx` must be valid and C-layout compatible for all fields copied. +/// - If `rust_ctx.cur_xds_payload` is non-null it must be valid for +/// `rust_ctx.cur_xds_payload_length` bytes. +/// - If `rust_ctx.timing` is `Some`, the C-side `timing` pointer in `bitstream_ptr` +/// must be a valid destination for `write_back_to_common_timing_ctx`. +/// - Violating these invariants is undefined behaviour; call only from `unsafe`. pub unsafe fn copy_xds_context_from_rust_to_c( bitstream_ptr: *mut ccx_decoders_xds_context, rust_ctx: &CcxDecodersXdsContext<'_>, From 12159858bb07e9dcb84b14931a7b72227015ad1a Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 26 Dec 2025 06:52:49 +0530 Subject: [PATCH 08/34] xds module : fixes requested : 5 --- src/rust/src/xds/handlers.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 0ad895144..69f80d102 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -447,12 +447,12 @@ const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ /// Canadian French Language Rating const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ - "Exemptées", - "Général", - "Général - Déconseillé aux jeunes enfants", - "Cette émission peut ne pas convenir aux enfants de moins de 13 ans", - "Cette émission ne convient pas aux moins de 16 ans", - "Cette émission est réservée aux adultes", + "Exempt?es", + "G?n?ral", + "G?n?ral - D?conseill? aux jeunes enfants", + "Cette ?mission peut ne pas convenir aux enfants de moins de 13 ans", + "Cette ?mission ne convient pas aux moins de 16 ans", + "Cette ?mission est r?serv?e aux adultes", "[invalid]", "[invalid]", ]; From 46ab8e6650021269c466923682dfef7dac8640db Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sun, 28 Dec 2025 19:56:11 +0530 Subject: [PATCH 09/34] xds module : add setter function for TS_START_OF_XDS from C code --- src/lib_ccx/ccx_decoders_xds.c | 4 ++++ src/rust/src/xds/handlers.rs | 8 ++++---- src/rust/src/xds/mod.rs | 9 ++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 8cc5fefe8..d0a320ae7 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -16,6 +16,9 @@ extern void ccxr_do_end_of_xds( struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum); +// declare setter function for TS_START_OF_XDS from C code +extern void ccxr_set_ts_start_of_xds(long long value); + LLONG ts_start_of_xds = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet static const char *XDSclasses[] = @@ -907,6 +910,7 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) void do_end_of_xds(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum) { #ifndef DISABLE_RUST + ccxr_set_ts_start_of_xds(ts_start_of_xds); ccxr_do_end_of_xds(sub, ctx, expected_checksum); // use the rust implementation return; #endif diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 69f80d102..12c184637 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -51,10 +51,10 @@ use libc::malloc; use crate::xds::types::*; -static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet - // Usage example: - // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); - // let value = TS_START_OF_XDS.load(Ordering::SeqCst); +pub static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet + // Usage example: + // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); + // let value = TS_START_OF_XDS.load(Ordering::SeqCst); /// Represents failures during XDS string writing or processing. pub enum XDSError { diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 47918d2f9..d743b963e 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -17,9 +17,10 @@ pub mod types; use crate::bindings::*; use crate::ctorust::FromCType; -use crate::xds::handlers::do_end_of_xds; +use crate::xds::handlers::{do_end_of_xds, TS_START_OF_XDS}; use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; use std::os::raw::c_int; +use std::sync::atomic::Ordering; /// FFI wrapper for `do_end_of_xds`. /// @@ -82,3 +83,9 @@ pub unsafe extern "C" fn ccxr_process_xds_bytes( // Write changes back to C copy_xds_context_from_rust_to_c(ctx, &rust_ctx); } + +/// setter function for TS_START_OF_XDS from C code +#[no_mangle] +pub extern "C" fn ccxr_set_ts_start_of_xds(value: i64) { + TS_START_OF_XDS.store(value, Ordering::SeqCst); +} From 450654dea18d6cda809ca70360c0a8d68a647fa7 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Tue, 3 Feb 2026 23:59:53 +0530 Subject: [PATCH 10/34] xds module : fix minor issue with timing ctx resulting in missing xds data in output --- src/rust/src/libccxr_exports/time.rs | 2 +- src/rust/src/xds/handlers.rs | 25 +++++++++++++------------ src/rust/src/xds/mod.rs | 10 ++++++++++ src/rust/src/xds/types.rs | 3 ++- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs index 348d67223..0c14cc83e 100644 --- a/src/rust/src/libccxr_exports/time.rs +++ b/src/rust/src/libccxr_exports/time.rs @@ -138,7 +138,7 @@ pub unsafe extern "C" fn ccxr_millis_to_time( /// # Safety /// /// `ctx` should not be null. -unsafe fn generate_timing_context(ctx: *const ccx_common_timing_ctx) -> TimingContext { +pub unsafe fn generate_timing_context(ctx: *const ccx_common_timing_ctx) -> TimingContext { let pts_set = match (*ctx).pts_set { 0 => PtsSet::No, 1 => PtsSet::Received, diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 12c184637..726c6fc89 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -87,19 +87,20 @@ pub unsafe fn write_xds_string( let data_element = &mut *new_data.add(sub.nb_data as usize); let c_str = CString::new(p).map_err(|_| XDSError::Err)?; - let len = c_str.as_bytes_with_nul().len(); - let ptr = unsafe { malloc(len) as *mut i8 }; // fixing c/rust mem mismatch + let len = c_str.as_bytes().len(); // length w/o null terminator - matching C's vsnprintf return + let alloc_len = len + 1; // allocate space for null terminator + let ptr = unsafe { malloc(alloc_len) as *mut i8 }; // fixing c/rust mem mismatch if ptr.is_null() { return Err(XDSError::Err); } unsafe { - std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, len); // fixing c/rust mem mismatch + std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, alloc_len); // copy including null terminator } data_element.format = 2; data_element.start_time = ts_start_of_xds; if let Some(timing) = ctx.timing.as_mut() { - data_element.end_time = get_fts(timing, CaptionField::Cea708).millis(); + data_element.end_time = get_fts(timing, CaptionField::Field2).millis(); } data_element.xds_str = ptr; data_element.xds_len = len; @@ -179,15 +180,15 @@ impl CcxDecodersXdsContext<'_> { for i in 0..NUM_XDS_BUFFERS { if self.xds_buffers[i].in_use != 0 && self.xds_buffers[i] - .xds_class - .map(|c| c.to_c_int()) - .unwrap_or(-1) - == xds_class + .xds_class + .map(|c| c.to_c_int()) + .unwrap_or(-1) + == xds_class && self.xds_buffers[i] - .xds_type - .map(|t| t.to_c_int()) - .unwrap_or(-1) - == lo as i32 + .xds_type + .map(|t| t.to_c_int()) + .unwrap_or(-1) + == lo as i32 { matching_buf = i as i32; break; diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index d743b963e..3c78f5f74 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -17,6 +17,7 @@ pub mod types; use crate::bindings::*; use crate::ctorust::FromCType; +use crate::libccxr_exports::time::generate_timing_context; use crate::xds::handlers::{do_end_of_xds, TS_START_OF_XDS}; use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; use std::os::raw::c_int; @@ -47,6 +48,15 @@ pub unsafe extern "C" fn ccxr_do_end_of_xds( None => return, }; + // populate timing from the C ctx's timing ptr + let c_timing_ptr = (*ctx).timing; + let mut timing_ctx = if !c_timing_ptr.is_null() { + Some(generate_timing_context(c_timing_ptr)) + } else { + None + }; + rust_ctx.timing = timing_ctx.as_mut(); + // Call the Rust implementation do_end_of_xds(&mut *sub, &mut rust_ctx, expected_checksum); diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 6bfe7f9d0..8a27e0102 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -503,6 +503,7 @@ pub unsafe fn copy_xds_context_from_rust_to_c( if bitstream_ptr.is_null() { return; } + let original_timing = (*bitstream_ptr).timing; let output = ccx_decoders_xds_context { current_xds_min: rust_ctx.current_xds_min, current_xds_hour: rust_ctx.current_xds_hour, @@ -522,7 +523,7 @@ pub unsafe fn copy_xds_context_from_rust_to_c( cur_xds_payload: rust_ctx.cur_xds_payload, cur_xds_payload_length: rust_ctx.cur_xds_payload_length, cur_xds_packet_type: rust_ctx.cur_xds_packet_type, - timing: null_mut(), + timing: original_timing, current_ar_start: rust_ctx.current_ar_start, current_ar_end: rust_ctx.current_ar_end, xds_write_to_file: if rust_ctx.xds_write_to_file { 1 } else { 0 }, From 9a548ef5eb29468378a14510ae65e99c42b15617 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 4 Feb 2026 12:26:52 +0530 Subject: [PATCH 11/34] xds module : trying to fix frame timing mismatch in regression test 106 --- src/rust/src/libccxr_exports/time.rs | 2 +- src/rust/src/xds/handlers.rs | 48 ++++++++++++++-------------- src/rust/src/xds/mod.rs | 4 +++ 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs index 0c14cc83e..8eaeff59f 100644 --- a/src/rust/src/libccxr_exports/time.rs +++ b/src/rust/src/libccxr_exports/time.rs @@ -275,7 +275,7 @@ pub unsafe fn write_back_to_common_timing_ctx( /// # Safety /// /// All the static variables should be initialized and in valid state. -unsafe fn apply_timing_info() { +pub(crate) unsafe fn apply_timing_info() { let Ok(mut timing_info) = GLOBAL_TIMING_INFO.write() else { // RwLock is poisoned, skip updating return; diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 726c6fc89..38bd4e99c 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -763,13 +763,13 @@ pub unsafe fn xds_do_current_and_future( // Extract program name from payload (bytes 2 to payload_length - 2) let name_end = (ctx.cur_xds_payload_length - 1) as usize; - let name_bytes: Vec = payload[2..name_end] - .iter() - .copied() - .filter(|&b| b != 0) - .collect(); + let name_slice = &payload[2..name_end]; + let name_bytes = match name_slice.iter().position(|&b| b == 0) { + Some(pos) => &name_slice[..pos], + None => name_slice, + }; - let xds_program_name = String::from_utf8_lossy(&name_bytes).to_string(); + let xds_program_name = String::from_utf8_lossy(name_bytes).to_string(); debug!( msg_type = DebugMessageFlag::DECODER_XDS; @@ -953,13 +953,13 @@ pub unsafe fn xds_do_current_and_future( // Extract description from payload let desc_end = (ctx.cur_xds_payload_length - 1) as usize; - let desc_bytes: Vec = payload[2..desc_end] - .iter() - .copied() - .filter(|&b| b != 0) - .collect(); + let desc_slice = &payload[2..desc_end]; + let desc_bytes = match desc_slice.iter().position(|&b| b == 0) { + Some(pos) => &desc_slice[..pos], + None => desc_slice, + }; - let xds_desc = String::from_utf8_lossy(&desc_bytes).to_string(); + let xds_desc = String::from_utf8_lossy(desc_bytes).to_string(); if !xds_desc.is_empty() { let line_num = ctx.cur_xds_packet_type - 0x10; // XDS_TYPE_PROGRAM_DESC_1 = 0x10 @@ -1056,13 +1056,13 @@ pub unsafe fn xds_do_channel( // Extract network name from payload (bytes 2 to payload_length - 2) let name_end = (ctx.cur_xds_payload_length - 1) as usize; - let name_bytes: Vec = payload[2..name_end] - .iter() - .copied() - .filter(|&b| b != 0) - .collect(); + let name_slice = &payload[2..name_end]; + let name_bytes = match name_slice.iter().position(|&b| b == 0) { + Some(pos) => &name_slice[..pos], + None => name_slice, + }; - let xds_network_name = String::from_utf8_lossy(&name_bytes).to_string(); + let xds_network_name = String::from_utf8_lossy(name_bytes).to_string(); debug!( msg_type = DebugMessageFlag::DECODER_XDS; @@ -1093,13 +1093,13 @@ pub unsafe fn xds_do_channel( // Extract call letters from payload (bytes 2 to payload_length - 2) let letters_end = (ctx.cur_xds_payload_length - 1) as usize; - let letters_bytes: Vec = payload[2..letters_end] - .iter() - .copied() - .filter(|&b| b != 0) - .collect(); + let letters_slice = &payload[2..letters_end]; + let letters_bytes = match letters_slice.iter().position(|&b| b == 0) { + Some(pos) => &letters_slice[..pos], + None => letters_slice, + }; - let xds_call_letters = String::from_utf8_lossy(&letters_bytes).to_string(); + let xds_call_letters = String::from_utf8_lossy(letters_bytes).to_string(); debug!( msg_type = DebugMessageFlag::DECODER_XDS; diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 3c78f5f74..250bccbba 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -18,6 +18,7 @@ pub mod types; use crate::bindings::*; use crate::ctorust::FromCType; use crate::libccxr_exports::time::generate_timing_context; +use crate::libccxr_exports::time::apply_timing_info; use crate::xds::handlers::{do_end_of_xds, TS_START_OF_XDS}; use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; use std::os::raw::c_int; @@ -42,6 +43,9 @@ pub unsafe extern "C" fn ccxr_do_end_of_xds( return; } + // trying to sync cb_field2 etc globals from C to GLOBAL_TIMING_INFO. hope this works + apply_timing_info(); + // Convert C context to Rust let mut rust_ctx = match CcxDecodersXdsContext::from_ctype(*ctx) { Some(c) => c, From bb55f8ad5a41900e67c538739b1fd8065885f07c Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 4 Feb 2026 13:58:08 +0530 Subject: [PATCH 12/34] xds module : fix clippy warnings and fmt errors --- src/rust/src/xds/handlers.rs | 16 ++++++++-------- src/rust/src/xds/mod.rs | 2 +- src/rust/src/xds/types.rs | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 38bd4e99c..20699acb6 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -180,15 +180,15 @@ impl CcxDecodersXdsContext<'_> { for i in 0..NUM_XDS_BUFFERS { if self.xds_buffers[i].in_use != 0 && self.xds_buffers[i] - .xds_class - .map(|c| c.to_c_int()) - .unwrap_or(-1) - == xds_class + .xds_class + .map(|c| c.to_c_int()) + .unwrap_or(-1) + == xds_class && self.xds_buffers[i] - .xds_type - .map(|t| t.to_c_int()) - .unwrap_or(-1) - == lo as i32 + .xds_type + .map(|t| t.to_c_int()) + .unwrap_or(-1) + == lo as i32 { matching_buf = i as i32; break; diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 250bccbba..074a9d25b 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -17,8 +17,8 @@ pub mod types; use crate::bindings::*; use crate::ctorust::FromCType; -use crate::libccxr_exports::time::generate_timing_context; use crate::libccxr_exports::time::apply_timing_info; +use crate::libccxr_exports::time::generate_timing_context; use crate::xds::handlers::{do_end_of_xds, TS_START_OF_XDS}; use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; use std::os::raw::c_int; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 8a27e0102..1f9f1968f 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -41,7 +41,6 @@ use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; use lib_ccxr::time::TimingContext; use std::os::raw::c_int; -use std::ptr::null_mut; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe From 251605a4a0d1c6b9716296f9c6bcf1bb16ed59ea Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 14:14:54 +0530 Subject: [PATCH 13/34] remove depreciated DISABLE_RUST flag --- src/lib_ccx/ccx_decoders_xds.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index d0a320ae7..bc9b88323 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -4,19 +4,16 @@ #include "ccx_common_common.h" #include "utility.h" -// declare rust implementation of process_xds_bytes (function) extern void ccxr_process_xds_bytes( struct ccx_decoders_xds_context *ctx, unsigned char hi, int lo); -// declare rust implementation of do_end_of_xds (function) extern void ccxr_do_end_of_xds( struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum); -// declare setter function for TS_START_OF_XDS from C code extern void ccxr_set_ts_start_of_xds(long long value); LLONG ts_start_of_xds = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet @@ -253,10 +250,9 @@ void clear_xds_buffer(struct ccx_decoders_xds_context *ctx, int num) void process_xds_bytes(struct ccx_decoders_xds_context *ctx, const unsigned char hi, int lo) { -#ifndef DISABLE_RUST - ccxr_process_xds_bytes(ctx, hi, lo); // use the rust implementation - return; -#endif + ccxr_process_xds_bytes(ctx, hi, lo); + return; // use the rust implementation + int is_new; if (!ctx) return; @@ -909,11 +905,10 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) void do_end_of_xds(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum) { -#ifndef DISABLE_RUST ccxr_set_ts_start_of_xds(ts_start_of_xds); - ccxr_do_end_of_xds(sub, ctx, expected_checksum); // use the rust implementation - return; -#endif + ccxr_do_end_of_xds(sub, ctx, expected_checksum); + return; // use the rust implementation + int cs = 0; int i; From 86aba3257f0a66c5d1d7f5923b9c6c12bc002a3a Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 14:35:54 +0530 Subject: [PATCH 14/34] replace libc::malloc,free usage with ffi usage --- src/rust/src/xds/handlers.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 20699acb6..f55c7651f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -46,11 +46,13 @@ use lib_ccxr::time::c_functions::get_fts; use lib_ccxr::time::CaptionField; use lib_ccxr::util::log::{hex_dump, send_gui, DebugMessageFlag, GuiXdsMessage}; -use libc::free; -use libc::malloc; - use crate::xds::types::*; +extern "C" { + fn malloc(size: usize) -> *mut c_void; + fn free(ptr: *mut c_void); +} + pub static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet // Usage example: // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); From 6d6a9528986843edb09a2ddd6cef044f8563effe Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 15:13:21 +0530 Subject: [PATCH 15/34] replace log::debug,info usage with libccxr::debug,info --- src/rust/src/xds/handlers.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index f55c7651f..4f2af0bca 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -166,7 +166,8 @@ impl CcxDecodersXdsContext<'_> { let xds_class = ((hi - 1) / 2) as i32; // Start codes 1 and 2 are "class type" 0, 3-4 are 2, and so on. let is_new = !hi.is_multiple_of(2); // Start codes are even - log::debug!( + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; "XDS Start: {}.{} Is new: {} | Class: {} ({}), Used buffers: {}", hi, lo, @@ -206,7 +207,7 @@ impl CcxDecodersXdsContext<'_> { 3) All buffers are full and we will have to skip this packet. */ if matching_buf == -1 && first_free_buf == -1 { - log::info!( + info!( "Note: All XDS buffers full (bug or suicidal stream). Ignoring this one ({},{}).", xds_class, lo ); @@ -237,7 +238,8 @@ impl CcxDecodersXdsContext<'_> { } } else { // Informational: 00, or 0x20-0x7F, so 01-0x1f forbidden - log::debug!( + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; "XDS: {:02X}.{:02X} ({}, {})", hi, lo, @@ -246,7 +248,7 @@ impl CcxDecodersXdsContext<'_> { ); if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { - log::info!("\rNote: Illegal XDS data"); + info!("\rNote: Illegal XDS data"); return; } } @@ -386,8 +388,8 @@ pub unsafe fn xds_do_copy_generation_management_system( } // Debug output (always, when debug mask matches) - debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state. copy_permitted); - debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state. aps); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.copy_permitted); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.aps); debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); } From 6a8381d464f31810b331e618b5898db947fb0dd9 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 17:30:02 +0530 Subject: [PATCH 16/34] add unit tests for FromCType and CType trait impls --- src/rust/src/xds/types.rs | 452 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 1f9f1968f..34d29a3e7 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -532,3 +532,455 @@ pub unsafe fn copy_xds_context_from_rust_to_c( write_back_to_common_timing_ctx((*bitstream_ptr).timing, timing_ctx); } } + +#[cfg(test)] +mod tests { + use super::*; + + // --- XdsClass --- + + #[test] + fn test_xds_class_from_c_int_valid() { + assert_eq!(XdsClass::from_c_int(0), Some(XdsClass::Current)); + assert_eq!(XdsClass::from_c_int(1), Some(XdsClass::Future)); + assert_eq!(XdsClass::from_c_int(2), Some(XdsClass::Channel)); + assert_eq!(XdsClass::from_c_int(3), Some(XdsClass::Misc)); + assert_eq!(XdsClass::from_c_int(4), Some(XdsClass::Public)); + assert_eq!(XdsClass::from_c_int(5), Some(XdsClass::Reserved)); + assert_eq!(XdsClass::from_c_int(6), Some(XdsClass::Private)); + assert_eq!(XdsClass::from_c_int(7), Some(XdsClass::End)); + assert_eq!(XdsClass::from_c_int(0x40), Some(XdsClass::OutOfBand)); + } + + #[test] + fn test_xds_class_from_c_int_invalid() { + assert_eq!(XdsClass::from_c_int(-1), None); + assert_eq!(XdsClass::from_c_int(8), None); + assert_eq!(XdsClass::from_c_int(100), None); + } + + #[test] + fn test_xds_class_roundtrip() { + let classes = [ + XdsClass::Current, + XdsClass::Future, + XdsClass::Channel, + XdsClass::Misc, + XdsClass::Public, + XdsClass::Reserved, + XdsClass::Private, + XdsClass::End, + XdsClass::OutOfBand, + ]; + for class in &classes { + let c_int = class.to_c_int(); + let roundtripped = XdsClass::from_c_int(c_int).unwrap(); + assert_eq!(*class, roundtripped); + } + } + + // --- XdsType --- + + #[test] + fn test_xds_type_current_future_from_c_int() { + let class = Some(XdsClass::Current); + assert_eq!( + XdsType::from_c_int(class, 1), + Some(XdsType::CurrentFuture(XdsCurrentFutureType::PinStartTime)) + ); + assert_eq!( + XdsType::from_c_int(class, 2), + Some(XdsType::CurrentFuture( + XdsCurrentFutureType::LengthAndCurrentTime + )) + ); + assert_eq!( + XdsType::from_c_int(class, 3), + Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)) + ); + assert_eq!( + XdsType::from_c_int(class, 4), + Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramType)) + ); + assert_eq!( + XdsType::from_c_int(class, 5), + Some(XdsType::CurrentFuture( + XdsCurrentFutureType::ContentAdvisory + )) + ); + assert_eq!( + XdsType::from_c_int(class, 6), + Some(XdsType::CurrentFuture(XdsCurrentFutureType::AudioServices)) + ); + assert_eq!( + XdsType::from_c_int(class, 8), + Some(XdsType::CurrentFuture(XdsCurrentFutureType::Cgms)) + ); + assert_eq!( + XdsType::from_c_int(class, 9), + Some(XdsType::CurrentFuture( + XdsCurrentFutureType::AspectRatioInfo + )) + ); + for i in 0x10..=0x17 { + assert!(XdsType::from_c_int(class, i).is_some()); + } + assert_eq!(XdsType::from_c_int(class, 7), None); + assert_eq!(XdsType::from_c_int(class, 0x18), None); + } + + #[test] + fn test_xds_type_future_class_same_as_current() { + for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] + { + assert_eq!( + XdsType::from_c_int(Some(XdsClass::Current), type_val), + XdsType::from_c_int(Some(XdsClass::Future), type_val) + ); + } + } + + #[test] + fn test_xds_type_channel_from_c_int() { + let class = Some(XdsClass::Channel); + assert_eq!( + XdsType::from_c_int(class, 1), + Some(XdsType::Channel(XdsChannelType::NetworkName)) + ); + assert_eq!( + XdsType::from_c_int(class, 2), + Some(XdsType::Channel(XdsChannelType::CallLettersAndChannel)) + ); + assert_eq!( + XdsType::from_c_int(class, 4), + Some(XdsType::Channel(XdsChannelType::Tsid)) + ); + assert_eq!(XdsType::from_c_int(class, 3), None); + assert_eq!(XdsType::from_c_int(class, 5), None); + } + + #[test] + fn test_xds_type_misc_from_c_int() { + let class = Some(XdsClass::Misc); + assert_eq!( + XdsType::from_c_int(class, 1), + Some(XdsType::Misc(XdsMiscType::TimeOfDay)) + ); + assert_eq!( + XdsType::from_c_int(class, 4), + Some(XdsType::Misc(XdsMiscType::LocalTimeZone)) + ); + assert_eq!( + XdsType::from_c_int(class, 0x40), + Some(XdsType::Misc(XdsMiscType::OutOfBandChannelNumber)) + ); + assert_eq!(XdsType::from_c_int(class, 2), None); + } + + #[test] + fn test_xds_type_unsupported_classes() { + for class in [ + XdsClass::Public, + XdsClass::Reserved, + XdsClass::Private, + XdsClass::End, + XdsClass::OutOfBand, + ] { + assert_eq!(XdsType::from_c_int(Some(class), 1), None); + } + } + + #[test] + fn test_xds_type_none_class() { + assert_eq!(XdsType::from_c_int(None, 1), None); + } + + #[test] + fn test_xds_type_roundtrip_current_future() { + let class = Some(XdsClass::Current); + for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] + { + let xds_type = XdsType::from_c_int(class, type_val).unwrap(); + assert_eq!(xds_type.to_c_int(), type_val); + } + } + + #[test] + fn test_xds_type_roundtrip_channel() { + let class = Some(XdsClass::Channel); + for type_val in [1, 2, 4] { + let xds_type = XdsType::from_c_int(class, type_val).unwrap(); + assert_eq!(xds_type.to_c_int(), type_val); + } + } + + #[test] + fn test_xds_type_roundtrip_misc() { + let class = Some(XdsClass::Misc); + for type_val in [1, 4, 0x40] { + let xds_type = XdsType::from_c_int(class, type_val).unwrap(); + assert_eq!(xds_type.to_c_int(), type_val); + } + } + + // --- XdsBuffer --- + + fn make_c_buf(in_use: u32, xds_class: i32, xds_type: i32, used_bytes: u8) -> xds_buffer { + let mut bytes = [0u8; NUM_BYTES_PER_PACKET]; + bytes[0] = 0x01; + bytes[1] = 0x03; + xds_buffer { + in_use, + xds_class, + xds_type, + bytes, + used_bytes, + } + } + + #[test] + fn test_xds_buffer_from_ctype_valid_current_program_name() { + let c_buf = make_c_buf(1, 0, 3, 4); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert_eq!(rust_buf.in_use, 1); + assert_eq!(rust_buf.xds_class, Some(XdsClass::Current)); + assert_eq!( + rust_buf.xds_type, + Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)) + ); + assert_eq!(rust_buf.used_bytes, 4); + assert_eq!(rust_buf.bytes[0], 0x01); + assert_eq!(rust_buf.bytes[1], 0x03); + } + + #[test] + fn test_xds_buffer_from_ctype_class_minus1_gives_none() { + let c_buf = make_c_buf(0, -1, -1, 0); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert_eq!(rust_buf.xds_class, None); + assert_eq!(rust_buf.xds_type, None); + } + + #[test] + fn test_xds_buffer_from_ctype_type_minus1_gives_none() { + let c_buf = make_c_buf(1, 0, -1, 2); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert_eq!(rust_buf.xds_class, Some(XdsClass::Current)); + assert_eq!(rust_buf.xds_type, None); + } + + #[test] + fn test_xds_buffer_from_ctype_invalid_class_gives_none() { + let c_buf = make_c_buf(1, 99, 1, 2); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert_eq!(rust_buf.xds_class, None); + assert_eq!(rust_buf.xds_type, None); + } + + #[test] + fn test_xds_buffer_from_ctype_invalid_type_for_class_gives_none() { + let c_buf = make_c_buf(1, 2, 99, 2); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert_eq!(rust_buf.xds_class, Some(XdsClass::Channel)); + assert_eq!(rust_buf.xds_type, None); + } + + #[test] + fn test_xds_buffer_to_ctype_valid() { + let c_buf = make_c_buf(1, 0, 3, 4); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + let back = unsafe { rust_buf.to_ctype() }; + assert_eq!(back.in_use, 1); + assert_eq!(back.xds_class, 0); + assert_eq!(back.xds_type, 3); + assert_eq!(back.used_bytes, 4); + assert_eq!(back.bytes[0], 0x01); + assert_eq!(back.bytes[1], 0x03); + } + + #[test] + fn test_xds_buffer_to_ctype_none_becomes_minus1() { + let c_buf = make_c_buf(0, -1, -1, 0); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + let back = unsafe { rust_buf.to_ctype() }; + assert_eq!(back.xds_class, -1); + assert_eq!(back.xds_type, -1); + } + + #[test] + fn test_xds_buffer_roundtrip_all_classes() { + let cases: &[(i32, i32)] = &[ + (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 8), (0, 9), + (0, 0x10), (0, 0x17), + (1, 3), // future + programName + (2, 1), (2, 2), (2, 4), // channel + (3, 1), (3, 4), (3, 0x40), // mics + ]; + for &(class, typ) in cases { + let c_buf = make_c_buf(1, class, typ, 2); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + let back = unsafe { rust_buf.to_ctype() }; + assert_eq!(back.xds_class, class, "class mismatch for ({}, {})", class, typ); + assert_eq!(back.xds_type, typ, "type mismatch for ({}, {})", class, typ); + } + } + + #[test] + fn test_xds_buffer_to_ctype_unsupported_class_type_is_none() { + for class in [4i32, 5, 6, 7, 0x40] { + let c_buf = make_c_buf(1, class, 1, 2); + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + assert!(rust_buf.xds_class.is_some(), "expected class Some for {}", class); + assert_eq!(rust_buf.xds_type, None, "expected type None for class {}", class); + let back = unsafe { rust_buf.to_ctype() }; + assert_eq!(back.xds_type, -1, "expected -1 in C for class {}", class); + } + } + + #[test] + fn test_xds_buffer_bytes_preserved_through_roundtrip() { + let mut c_buf = make_c_buf(1, 0, 3, 10); + for (i, b) in c_buf.bytes.iter_mut().enumerate() { + *b = (i as u8).wrapping_mul(7); + } + let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); + let back = unsafe { rust_buf.to_ctype() }; + assert_eq!(c_buf.bytes, back.bytes); + } + + // --- CcxDecodersXdsContext --- + + fn make_c_xds_ctx_zeroed() -> ccx_decoders_xds_context { + unsafe { std::mem::zeroed() } + } + + #[test] + fn test_xds_context_from_ctype_scalars() { + let mut c_ctx = make_c_xds_ctx_zeroed(); + c_ctx.current_xds_min = 42; + c_ctx.current_xds_hour = 13; + c_ctx.current_xds_date = 15; + c_ctx.current_xds_month = 7; + c_ctx.current_program_type_reported = 3; + c_ctx.xds_start_time_shown = 1; + c_ctx.xds_program_length_shown = 2; + c_ctx.cur_xds_buffer_idx = 5; + c_ctx.cur_xds_packet_class = 0; + c_ctx.cur_xds_payload_length = 10; + c_ctx.cur_xds_packet_type = 3; + c_ctx.current_ar_start = 100; + c_ctx.current_ar_end = 200; + + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert_eq!(rust_ctx.current_xds_min, 42); + assert_eq!(rust_ctx.current_xds_hour, 13); + assert_eq!(rust_ctx.current_xds_date, 15); + assert_eq!(rust_ctx.current_xds_month, 7); + assert_eq!(rust_ctx.current_program_type_reported, 3); + assert_eq!(rust_ctx.xds_start_time_shown, 1); + assert_eq!(rust_ctx.xds_program_length_shown, 2); + assert_eq!(rust_ctx.cur_xds_buffer_idx, 5); + assert_eq!(rust_ctx.cur_xds_packet_class, 0); + assert_eq!(rust_ctx.cur_xds_payload_length, 10); + assert_eq!(rust_ctx.cur_xds_packet_type, 3); + assert_eq!(rust_ctx.current_ar_start, 100); + assert_eq!(rust_ctx.current_ar_end, 200); + } + + #[test] + fn test_xds_context_from_ctype_write_to_file() { + let mut c_ctx = make_c_xds_ctx_zeroed(); + + c_ctx.xds_write_to_file = 0; + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert!(!rust_ctx.xds_write_to_file); + + c_ctx.xds_write_to_file = 1; + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert!(rust_ctx.xds_write_to_file); + + c_ctx.xds_write_to_file = 42; + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert!(rust_ctx.xds_write_to_file); + } + + #[test] + fn test_xds_context_from_ctype_timing_always_none() { + let c_ctx = make_c_xds_ctx_zeroed(); // timing pointer is null + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert!(rust_ctx.timing.is_none()); + } + + #[test] + fn test_xds_context_from_ctype_buffers_converted() { + let mut c_ctx = make_c_xds_ctx_zeroed(); + c_ctx.xds_buffers[0].in_use = 1; + c_ctx.xds_buffers[0].xds_class = 0; // curr + c_ctx.xds_buffers[0].xds_type = 3; // programName + c_ctx.xds_buffers[0].used_bytes = 5; + + let rust_ctx = unsafe { CcxDecodersXdsContext::from_ctype(c_ctx) }.unwrap(); + assert_eq!(rust_ctx.xds_buffers[0].in_use, 1); + assert_eq!(rust_ctx.xds_buffers[0].xds_class, Some(XdsClass::Current)); + assert_eq!( + rust_ctx.xds_buffers[0].xds_type, + Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)) + ); + assert_eq!(rust_ctx.xds_buffers[0].used_bytes, 5); + } + + #[test] + fn test_copy_xds_context_scalars() { + let mut rust_ctx = CcxDecodersXdsContext::default(); + rust_ctx.current_xds_min = 30; + rust_ctx.current_xds_hour = 9; + rust_ctx.current_xds_date = 25; + rust_ctx.current_xds_month = 12; + rust_ctx.cur_xds_buffer_idx = 3; + rust_ctx.current_ar_start = 50; + rust_ctx.current_ar_end = 150; + + let mut c_ctx = make_c_xds_ctx_zeroed(); + unsafe { copy_xds_context_from_rust_to_c(&mut c_ctx as *mut _, &rust_ctx) }; + + assert_eq!(c_ctx.current_xds_min, 30); + assert_eq!(c_ctx.current_xds_hour, 9); + assert_eq!(c_ctx.current_xds_date, 25); + assert_eq!(c_ctx.current_xds_month, 12); + assert_eq!(c_ctx.cur_xds_buffer_idx, 3); + assert_eq!(c_ctx.current_ar_start, 50); + assert_eq!(c_ctx.current_ar_end, 150); + } + + #[test] + fn test_copy_xds_context_write_to_file() { + let mut c_ctx = make_c_xds_ctx_zeroed(); + + let mut rust_ctx = CcxDecodersXdsContext::default(); + rust_ctx.xds_write_to_file = false; + unsafe { copy_xds_context_from_rust_to_c(&mut c_ctx as *mut _, &rust_ctx) }; + assert_eq!(c_ctx.xds_write_to_file, 0); + + rust_ctx.xds_write_to_file = true; + unsafe { copy_xds_context_from_rust_to_c(&mut c_ctx as *mut _, &rust_ctx) }; + assert_eq!(c_ctx.xds_write_to_file, 1); + } + + #[test] + fn test_copy_xds_context_timing_preserved() { + let mut c_ctx = make_c_xds_ctx_zeroed(); + let sentinel: usize = 0xDEAD_BEEF; + c_ctx.timing = sentinel as *mut _; + + let rust_ctx = CcxDecodersXdsContext::default(); // timing = None + unsafe { copy_xds_context_from_rust_to_c(&mut c_ctx as *mut _, &rust_ctx) }; + + assert_eq!(c_ctx.timing as usize, sentinel); + } + + #[test] + fn test_copy_xds_context_null_ptr_noop() { + let rust_ctx = CcxDecodersXdsContext::default(); + unsafe { copy_xds_context_from_rust_to_c(std::ptr::null_mut(), &rust_ctx) }; + } +} From e5f1a86683154191b78953966f74ac8773ded0f1 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 18:21:22 +0530 Subject: [PATCH 17/34] refactor handlers.rs to move structs/constants to top, added XdsPacketType enum, removed XDSerror enum --- src/rust/src/xds/handlers.rs | 463 ++++++++++++++++++++++------------- 1 file changed, 286 insertions(+), 177 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 4f2af0bca..d2d5e97e3 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -18,7 +18,7 @@ //! | `do_end_of_xds` | [`do_end_of_xds`] | //! | `xds_debug_test` | [`xds_debug_test`] | //! | `xds_cea608_test` | [`xds_cea608_test`] | -//! | `XDS_TYPE_*` defines | [`XdsType`] enum or constants | +//! | `XDS_TYPE_*` defines | [`XdsPackeType`] enum | //! | `XDS_CLASS_*` defines | [`XdsClass`] enum | //! | `ts_start_of_xds` global | Parameter or context field | //! | `last_c1`, `last_c2` statics | Context fields or function-local state | @@ -58,11 +58,265 @@ pub static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); // let value = TS_START_OF_XDS.load(Ordering::SeqCst); -/// Represents failures during XDS string writing or processing. -pub enum XDSError { - Err, + +/// XDS packet type codes used in `cur_xds_packet_type` matching +/// +/// Maps integer constants from the C `XDS_TYPE_*` defines to a Rust enum +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(i32)] +pub enum XdsPacketType { + PinStartTime = 1, + LengthAndCurrentTime = 2, + ProgramName = 3, + ProgramType = 4, + ContentAdvisory = 5, + AudioServices = 6, + Cgms = 8, + AspectRatioInfo = 9, + ProgramDesc1 = 0x10, + ProgramDesc2 = 0x11, + ProgramDesc3 = 0x12, + ProgramDesc4 = 0x13, + ProgramDesc5 = 0x14, + ProgramDesc6 = 0x15, + ProgramDesc7 = 0x16, + ProgramDesc8 = 0x17, + // channel class types + NetworkName = 0x101, + CallLettersAndChannel = 0x102, + Tsid = 0x104, + // misc class types + TimeOfDay = 0x201, + LocalTimeZone = 0x204, +} + +impl XdsPacketType { + /// Convert a raw `cur_xds_packet_type` integer (Current/Future class) to enum. + pub fn from_current_future(value: i32) -> Option { + match value { + 1 => Some(Self::PinStartTime), + 2 => Some(Self::LengthAndCurrentTime), + 3 => Some(Self::ProgramName), + 4 => Some(Self::ProgramType), + 5 => Some(Self::ContentAdvisory), + 6 => Some(Self::AudioServices), + 8 => Some(Self::Cgms), + 9 => Some(Self::AspectRatioInfo), + 0x10..=0x17 => Some(match value { + 0x10 => Self::ProgramDesc1, + 0x11 => Self::ProgramDesc2, + 0x12 => Self::ProgramDesc3, + 0x13 => Self::ProgramDesc4, + 0x14 => Self::ProgramDesc5, + 0x15 => Self::ProgramDesc6, + 0x16 => Self::ProgramDesc7, + 0x17 => Self::ProgramDesc8, + _ => unreachable!(), + }), + _ => None, + } + } + + /// Convert a raw `cur_xds_packet_type` integer (Channel class) to enum. + pub fn from_channel(value: i32) -> Option { + match value { + 1 => Some(Self::NetworkName), + 2 => Some(Self::CallLettersAndChannel), + 4 => Some(Self::Tsid), + _ => None, + } + } + + /// Convert a raw `cur_xds_packet_type` integer (Misc class) to enum. + pub fn from_misc(value: i32) -> Option { + match value { + 1 => Some(Self::TimeOfDay), + 4 => Some(Self::LocalTimeZone), + _ => None, + } + } + + /// Returns true if this is a program description type (0x10-0x17). + pub fn is_program_desc(&self) -> bool { + matches!( + self, + Self::ProgramDesc1 + | Self::ProgramDesc2 + | Self::ProgramDesc3 + | Self::ProgramDesc4 + | Self::ProgramDesc5 + | Self::ProgramDesc6 + | Self::ProgramDesc7 + | Self::ProgramDesc8 + ) + } +} + +// --- structs --- + +/// State for Content Advisory caching +struct ContentAdvisoryState { + last_c1: u32, + last_c2: u32, + age: String, + content: String, + rating: String, +} + +impl Default for ContentAdvisoryState { + fn default() -> Self { + ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), + } + } +} + + + +/// State for CGMS (Copy Generation Management System) caching +struct CgmsState { + last_c1: u32, + last_c2: u32, + copy_permitted: String, + aps: String, + rcd: String, +} + +impl Default for CgmsState { + fn default() -> Self { + CgmsState { + last_c1: u32::MAX, // equivalent to -1 for unsigned in C + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), + } + } +} + +// --- static / consts --- + +/// Cached state for Content Advisory to detect changes +static CONTENT_ADVISORY_STATE: Mutex = Mutex::new(ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), +}); + +/// US TV Parental Guidelines age ratings +const US_TV_AGE_TEXT: [&str; 8] = [ + "None", + "TV-Y (All Children)", + "TV-Y7 (Older Children)", + "TV-G (General Audience)", + "TV-PG (Parental Guidance Suggested)", + "TV-14 (Parents Strongly Cautioned)", + "TV-MA (Mature Audience Only)", + "None", +]; + +/// MPA rating text +const MPA_RATING_TEXT: [&str; 8] = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; + +/// Canadian English Language Rating +const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ + "Exempt", + "Children", + "Children eight years and older", + "General programming suitable for all audiences", + "Parental Guidance", + "Viewers 14 years and older", + "Adult Programming", + "[undefined]", +]; + +/// Canadian French Language Rating +const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ + "Exempt?es", + "G?n?ral", + "G?n?ral - D?conseill? aux jeunes enfants", + "Cette ?mission peut ne pas convenir aux enfants de moins de 13 ans", + "Cette ?mission ne convient pas aux moins de 16 ans", + "Cette ?mission est r?serv?e aux adultes", + "[invalid]", + "[invalid]", +]; + +/// Cached state for Copy Generation Management System (CGMS) to detect changes +static CGMS_STATE: Mutex = Mutex::new(CgmsState { + last_c1: u32::MAX, + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), +}); + +/// Copy Generation Management System text descriptions +const COPY_TEXT: [&str; 4] = [ + "Copy permitted (no restrictions)", + "No more copies (one generation copy has been made)", + "One generation of copies can be made", + "No copying is permitted", +]; + +/// APS (Analog Protection System) mode descriptions +const APS_TEXT: [&str; 4] = [ + "No APS", + "PSP On; Split Burst Off", + "PSP On; 2 line Split Burst On", + "PSP On; 4 line Split Burst On", +]; + +// --- helper func --- + +/// Frees a pointer and sets it to null. +/// +/// Converts the raw pointer back into a `Box` to deallocate the memory, +/// then sets the original pointer to null to prevent use-after-free. +/// +/// # Safety +/// - The pointer must have been originally allocated by Rust's `Box::into_raw` +/// or equivalent. Using this with C-allocated memory will cause undefined behavior. +/// - The pointer must not be used after this call. +pub fn freep(ptr: &mut *mut T) { + unsafe { + if !ptr.is_null() { + free(*ptr as *mut c_void); + *ptr = null_mut(); + } + } +} + +/// Helper function to copy String into i8 array +fn string_to_i8_array(s: &str, arr: &mut [i8]) { + let bytes = s.as_bytes(); + let len = std::cmp::min(bytes.len(), arr.len() - 1); + for i in 0..len { + arr[i] = bytes[i] as i8; + } + if len < arr.len() { + arr[len] = 0; + } } +/// Helper function to convert i8 array to String +fn i8_array_to_string(arr: &[i8]) -> String { + let bytes: Vec = arr + .iter() + .take_while(|&&b| b != 0) + .map(|&b| b as u8) + .collect(); + String::from_utf8_lossy(&bytes).to_string() +} + +// --- core XDS handlers --- + /// Writes an XDS string to the subtitle output buffer. /// /// # Safety @@ -74,7 +328,7 @@ pub unsafe fn write_xds_string( ctx: &mut CcxDecodersXdsContext, p: String, ts_start_of_xds: i64, -) -> Result<(), XDSError> { +) -> Result<(), ()> { let new_size = (sub.nb_data + 1) as usize * size_of::(); let new_data = unsafe { realloc(sub.data as *mut c_void, new_size as u64) as *mut eia608_screen }; @@ -82,18 +336,18 @@ pub unsafe fn write_xds_string( freep(&mut sub.data); sub.nb_data = 0; info!("No Memory left"); - return Err(XDSError::Err); + return Err(()); } sub.data = new_data as *mut c_void; sub.datatype = 0; let data_element = &mut *new_data.add(sub.nb_data as usize); - let c_str = CString::new(p).map_err(|_| XDSError::Err)?; + let c_str = CString::new(p).map_err(|_| ())?; let len = c_str.as_bytes().len(); // length w/o null terminator - matching C's vsnprintf return let alloc_len = len + 1; // allocate space for null terminator let ptr = unsafe { malloc(alloc_len) as *mut i8 }; // fixing c/rust mem mismatch if ptr.is_null() { - return Err(XDSError::Err); + return Err(()); } unsafe { std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, alloc_len); // copy including null terminator @@ -122,7 +376,7 @@ pub unsafe fn xdsprint( sub: &mut cc_subtitle, ctx: &mut CcxDecodersXdsContext, message: String, -) -> Result<(), XDSError> { +) -> Result<(), ()> { if !ctx.xds_write_to_file { return Ok(()); } @@ -130,23 +384,6 @@ pub unsafe fn xdsprint( write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } -/// Frees a pointer and sets it to null. -/// -/// Converts the raw pointer back into a `Box` to deallocate the memory, -/// then sets the original pointer to null to prevent use-after-free. -/// -/// # Safety -/// - The pointer must have been originally allocated by Rust's `Box::into_raw` -/// or equivalent. Using this with C-allocated memory will cause undefined behavior. -/// - The pointer must not be used after this call. -pub fn freep(ptr: &mut *mut T) { - unsafe { - if !ptr.is_null() { - free(*ptr as *mut libc::c_void); - *ptr = null_mut(); - } - } -} /// Utility methods for XDS buffer management and process_xds_bytes (function). impl CcxDecodersXdsContext<'_> { @@ -268,52 +505,6 @@ impl CcxDecodersXdsContext<'_> { } } -/// State for CGMS (Copy Generation Management System) caching -struct CgmsState { - last_c1: u32, - last_c2: u32, - copy_permitted: String, - aps: String, - rcd: String, -} - -impl Default for CgmsState { - fn default() -> Self { - CgmsState { - last_c1: u32::MAX, // equivalent to -1 for unsigned in C - last_c2: u32::MAX, - copy_permitted: String::new(), - aps: String::new(), - rcd: String::new(), - } - } -} - -/// Cached state for Copy Generation Management System (CGMS) to detect changes -static CGMS_STATE: Mutex = Mutex::new(CgmsState { - last_c1: u32::MAX, - last_c2: u32::MAX, - copy_permitted: String::new(), - aps: String::new(), - rcd: String::new(), -}); - -/// Copy Generation Management System text descriptions -const COPY_TEXT: [&str; 4] = [ - "Copy permitted (no restrictions)", - "No more copies (one generation copy has been made)", - "One generation of copies can be made", - "No copying is permitted", -]; - -/// APS (Analog Protection System) mode descriptions -const APS_TEXT: [&str; 4] = [ - "No APS", - "PSP On; Split Burst Off", - "PSP On; 2 line Split Burst On", - "PSP On; 4 line Split Burst On", -]; - /// Decodes and outputs Copy Generation Management System (CGMS) data from XDS packets. /// /// # Safety @@ -393,74 +584,6 @@ pub unsafe fn xds_do_copy_generation_management_system( debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); } -/// State for Content Advisory caching -struct ContentAdvisoryState { - last_c1: u32, - last_c2: u32, - age: String, - content: String, - rating: String, -} - -impl Default for ContentAdvisoryState { - fn default() -> Self { - ContentAdvisoryState { - last_c1: u32::MAX, - last_c2: u32::MAX, - age: String::new(), - content: String::new(), - rating: String::new(), - } - } -} - -/// Cached state for Content Advisory to detect changes -static CONTENT_ADVISORY_STATE: Mutex = Mutex::new(ContentAdvisoryState { - last_c1: u32::MAX, - last_c2: u32::MAX, - age: String::new(), - content: String::new(), - rating: String::new(), -}); - -/// US TV Parental Guidelines age ratings -const US_TV_AGE_TEXT: [&str; 8] = [ - "None", - "TV-Y (All Children)", - "TV-Y7 (Older Children)", - "TV-G (General Audience)", - "TV-PG (Parental Guidance Suggested)", - "TV-14 (Parents Strongly Cautioned)", - "TV-MA (Mature Audience Only)", - "None", -]; - -/// MPA rating text -const MPA_RATING_TEXT: [&str; 8] = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; - -/// Canadian English Language Rating -const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ - "Exempt", - "Children", - "Children eight years and older", - "General programming suitable for all audiences", - "Parental Guidance", - "Viewers 14 years and older", - "Adult Programming", - "[undefined]", -]; - -/// Canadian French Language Rating -const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ - "Exempt?es", - "G?n?ral", - "G?n?ral - D?conseill? aux jeunes enfants", - "Cette ?mission peut ne pas convenir aux enfants de moins de 13 ans", - "Cette ?mission ne convient pas aux moins de 16 ans", - "Cette ?mission est r?serv?e aux adultes", - "[invalid]", - "[invalid]", -]; /// Handles content advisory/rating information (US TV, MPA, Canadian ratings) /// @@ -634,9 +757,11 @@ pub unsafe fn xds_do_current_and_future( let payload = std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); - match ctx.cur_xds_packet_type { + let packet_type = XdsPacketType::from_current_future(ctx.cur_xds_packet_type); + + match packet_type { // XDS_TYPE_PIN_START_TIME = 1 - 1 => { + Some(XdsPacketType::PinStartTime) => { was_proc = 1; if ctx.cur_xds_payload_length < 7 { // We need 4 data bytes @@ -697,7 +822,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_LENGH_AND_CURRENT_TIME = 2 - 2 => { + Some(XdsPacketType::LengthAndCurrentTime) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -762,7 +887,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_PROGRAM_NAME = 3 - 3 => { + Some(XdsPacketType::ProgramName) => { was_proc = 1; // Extract program name from payload (bytes 2 to payload_length - 2) @@ -795,7 +920,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_PROGRAM_TYPE = 4 - 4 => { + Some(XdsPacketType::ProgramType) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -879,7 +1004,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_CONTENT_ADVISORY = 5 - 5 => { + Some(XdsPacketType::ContentAdvisory) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -889,13 +1014,13 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_AUDIO_SERVICES = 6 - 6 => { + Some(XdsPacketType::AudioServices) => { was_proc = 1; // No sample available - nothing to do } // XDS_TYPE_CGMS = 8 - 8 => { + Some(XdsPacketType::Cgms) => { was_proc = 1; xds_do_copy_generation_management_system( sub, @@ -906,7 +1031,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_ASPECT_RATIO_INFO = 9 - 9 => { + Some(XdsPacketType::AspectRatioInfo) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -952,7 +1077,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_PROGRAM_DESC_1 through XDS_TYPE_PROGRAM_DESC_8 = 0x10-0x17 - 0x10..=0x17 => { + Some(ptype) if ptype.is_program_desc() => { was_proc = 1; // Extract description from payload @@ -1010,27 +1135,7 @@ pub unsafe fn xds_do_current_and_future( was_proc } -/// Helper function to convert i8 array to String -fn i8_array_to_string(arr: &[i8]) -> String { - let bytes: Vec = arr - .iter() - .take_while(|&&b| b != 0) - .map(|&b| b as u8) - .collect(); - String::from_utf8_lossy(&bytes).to_string() -} -/// Helper function to copy String into i8 array -fn string_to_i8_array(s: &str, arr: &mut [i8]) { - let bytes = s.as_bytes(); - let len = std::cmp::min(bytes.len(), arr.len() - 1); - for i in 0..len { - arr[i] = bytes[i] as i8; - } - if len < arr.len() { - arr[len] = 0; - } -} /// Processes channel-related XDS data (network name, call letters, TSID) /// @@ -1053,9 +1158,11 @@ pub unsafe fn xds_do_channel( let payload = std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); - match ctx.cur_xds_packet_type { + let packet_type = XdsPacketType::from_channel(ctx.cur_xds_packet_type); + + match packet_type { // XDS_TYPE_NETWORK_NAME = 1 - 1 => { + Some(XdsPacketType::NetworkName) => { was_proc = 1; // Extract network name from payload (bytes 2 to payload_length - 2) @@ -1087,7 +1194,7 @@ pub unsafe fn xds_do_channel( } // XDS_TYPE_CALL_LETTERS_AND_CHANNEL = 2 - 2 => { + Some(XdsPacketType::CallLettersAndChannel) => { was_proc = 1; // We need 4-6 data bytes (payload_length 7 or 9) @@ -1128,7 +1235,7 @@ pub unsafe fn xds_do_channel( } // XDS_TYPE_TSID = 4 - 4 => { + Some(XdsPacketType::Tsid => { // According to CEA-608, data here (4 bytes) are used to identify the // "originating analog licensee". No interesting data for us. was_proc = 1; @@ -1206,9 +1313,11 @@ pub unsafe fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> i32 { let payload = std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize); - match ctx.cur_xds_packet_type { + let packet_type = XdsPacketType::from_misc(ctx.cur_xds_packet_type); + + match packet_type { // XDS_TYPE_TIME_OF_DAY = 1 - 1 => { + Some(XdsPacketType::TimeOfDay) => { was_proc = 1; if ctx.cur_xds_payload_length < 9 { // We need 6 data bytes @@ -1231,7 +1340,7 @@ pub unsafe fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> i32 { } // XDS_TYPE_LOCAL_TIME_ZONE = 4 - 4 => { + Some(XdsPacketType::LocalTimeZone) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes From 5b900a24631722f28ea454a37cd27547ffd45cbe Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 18:59:46 +0530 Subject: [PATCH 18/34] refactor to move constants, statics, enums to separate file from types.rs --- src/rust/src/xds/constants.rs | 299 ++++++++++++++++++++++++++++++++++ src/rust/src/xds/handlers.rs | 9 +- src/rust/src/xds/mod.rs | 4 +- src/rust/src/xds/types.rs | 35 ++-- 4 files changed, 317 insertions(+), 30 deletions(-) create mode 100644 src/rust/src/xds/constants.rs diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs new file mode 100644 index 000000000..b736fb9c8 --- /dev/null +++ b/src/rust/src/xds/constants.rs @@ -0,0 +1,299 @@ +//! XDS constants, enums, and static data +//! +//! # Conversion Guide +//! +//! | C (ccx_decoders_xds.c/.h) | Rust (constants.rs) | +//! |----------------------------------|--------------------------------------------------| +//! | `XDS_CLASS_*` defines | [`XdsClass`] enum | +//! | `XDS_TYPE_*` defines | [`XdsType`], [`XdsCurrentFutureType`], etc. | +//! | `xds_class` (int field) | [`XdsClass::from_c_int`], [`XdsClass::to_c_int`] | +//! | `xds_type` (int field) | [`XdsType::from_c_int`], [`XdsType::to_c_int`] | +//! | `xds_program_type[]` array | [`XDS_PROGRAM_TYPES`] | +//! | `xds_classes[]` array | [`XDS_CLASSES`] | + +use std::os::raw::c_int; + +pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero +pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe + +pub static XDS_CLASSES: [&str; 8] = [ + "Current", + "Future", + "Channel", + "Miscellaneous", + "Public service", + "Reserved", + "Private data", + "End", +]; + +pub static XDS_PROGRAM_TYPES: [&str; 96] = [ + "Education", + "Entertainment", + "Movie", + "News", + "Religious", + "Sports", + "Other", + "Action", + "Advertisement", + "Animated", + "Anthology", + "Automobile", + "Awards", + "Baseball", + "Basketball", + "Bulletin", + "Business", + "Classical", + "College", + "Combat", + "Comedy", + "Commentary", + "Concert", + "Consumer", + "Contemporary", + "Crime", + "Dance", + "Documentary", + "Drama", + "Elementary", + "Erotica", + "Exercise", + "Fantasy", + "Farm", + "Fashion", + "Fiction", + "Food", + "Football", + "Foreign", + "Fund-Raiser", + "Game/Quiz", + "Garden", + "Golf", + "Government", + "Health", + "High_School", + "History", + "Hobby", + "Hockey", + "Home", + "Horror", + "Information", + "Instruction", + "International", + "Interview", + "Language", + "Legal", + "Live", + "Local", + "Math", + "Medical", + "Meeting", + "Military", + "Mini-Series", + "Music", + "Mystery", + "National", + "Nature", + "Police", + "Politics", + "Premiere", + "Pre-Recorded", + "Product", + "Professional", + "Public", + "Racing", + "Reading", + "Repair", + "Repeat", + "Review", + "Romance", + "Science", + "Series", + "Service", + "Shopping", + "Soap_Opera", + "Special", + "Suspense", + "Talk", + "Technical", + "Tennis", + "Travel", + "Variety", + "Video", + "Weather", + "Western", +]; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsClass { + Current = 0, + Future = 1, + Channel = 2, + Misc = 3, + Public = 4, + Reserved = 5, + Private = 6, + End = 7, + OutOfBand = 0x40, +} + +impl XdsClass { + pub fn from_c_int(value: c_int) -> Option { + match value { + 0 => Some(XdsClass::Current), + 1 => Some(XdsClass::Future), + 2 => Some(XdsClass::Channel), + 3 => Some(XdsClass::Misc), + 4 => Some(XdsClass::Public), + 5 => Some(XdsClass::Reserved), + 6 => Some(XdsClass::Private), + 7 => Some(XdsClass::End), + 0x40 => Some(XdsClass::OutOfBand), + _ => None, + } + } + + pub fn to_c_int(&self) -> c_int { + match self { + XdsClass::Current => 0, + XdsClass::Future => 1, + XdsClass::Channel => 2, + XdsClass::Misc => 3, + XdsClass::Public => 4, + XdsClass::Reserved => 5, + XdsClass::Private => 6, + XdsClass::End => 7, + XdsClass::OutOfBand => 0x40, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsCurrentFutureType { + PinStartTime = 1, + LengthAndCurrentTime = 2, + ProgramName = 3, + ProgramType = 4, + ContentAdvisory = 5, + AudioServices = 6, + Cgms = 8, + AspectRatioInfo = 9, + ProgramDesc1 = 0x10, + ProgramDesc2 = 0x11, + ProgramDesc3 = 0x12, + ProgramDesc4 = 0x13, + ProgramDesc5 = 0x14, + ProgramDesc6 = 0x15, + ProgramDesc7 = 0x16, + ProgramDesc8 = 0x17, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsChannelType { + NetworkName = 1, + CallLettersAndChannel = 2, + Tsid = 4, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsMiscType { + TimeOfDay = 1, + LocalTimeZone = 4, + OutOfBandChannelNumber = 0x40, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum XdsType { + CurrentFuture(XdsCurrentFutureType), + Misc(XdsMiscType), + Channel(XdsChannelType), +} + +impl XdsType { + pub fn from_c_int(class: Option, type_value: c_int) -> Option { + match class? { + XdsClass::Current | XdsClass::Future => match type_value { + 1 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::PinStartTime)), + 2 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::LengthAndCurrentTime, + )), + 3 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)), + 4 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramType)), + 5 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::ContentAdvisory, + )), + 6 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::AudioServices)), + 8 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::Cgms)), + 9 => Some(XdsType::CurrentFuture( + XdsCurrentFutureType::AspectRatioInfo, + )), + 0x10 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc1)), + 0x11 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc2)), + 0x12 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc3)), + 0x13 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc4)), + 0x14 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc5)), + 0x15 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc6)), + 0x16 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc7)), + 0x17 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc8)), + _ => None, + }, + XdsClass::Channel => match type_value { + 1 => Some(XdsType::Channel(XdsChannelType::NetworkName)), + 2 => Some(XdsType::Channel(XdsChannelType::CallLettersAndChannel)), + 4 => Some(XdsType::Channel(XdsChannelType::Tsid)), + _ => None, + }, + XdsClass::Misc => match type_value { + 1 => Some(XdsType::Misc(XdsMiscType::TimeOfDay)), + 4 => Some(XdsType::Misc(XdsMiscType::LocalTimeZone)), + 0x40 => Some(XdsType::Misc(XdsMiscType::OutOfBandChannelNumber)), + _ => None, + }, + XdsClass::Public + | XdsClass::Reserved + | XdsClass::Private + | XdsClass::End + | XdsClass::OutOfBand => None, + } + } + + pub fn to_c_int(&self) -> c_int { + match self { + XdsType::CurrentFuture(t) => match t { + XdsCurrentFutureType::PinStartTime => 1, + XdsCurrentFutureType::LengthAndCurrentTime => 2, + XdsCurrentFutureType::ProgramName => 3, + XdsCurrentFutureType::ProgramType => 4, + XdsCurrentFutureType::ContentAdvisory => 5, + XdsCurrentFutureType::AudioServices => 6, + XdsCurrentFutureType::Cgms => 8, + XdsCurrentFutureType::AspectRatioInfo => 9, + XdsCurrentFutureType::ProgramDesc1 => 0x10, + XdsCurrentFutureType::ProgramDesc2 => 0x11, + XdsCurrentFutureType::ProgramDesc3 => 0x12, + XdsCurrentFutureType::ProgramDesc4 => 0x13, + XdsCurrentFutureType::ProgramDesc5 => 0x14, + XdsCurrentFutureType::ProgramDesc6 => 0x15, + XdsCurrentFutureType::ProgramDesc7 => 0x16, + XdsCurrentFutureType::ProgramDesc8 => 0x17, + }, + XdsType::Channel(t) => match t { + XdsChannelType::NetworkName => 1, + XdsChannelType::CallLettersAndChannel => 2, + XdsChannelType::Tsid => 4, + }, + XdsType::Misc(t) => match t { + XdsMiscType::TimeOfDay => 1, + XdsMiscType::LocalTimeZone => 4, + XdsMiscType::OutOfBandChannelNumber => 0x40, + }, + } + } +} \ No newline at end of file diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index d2d5e97e3..848fe9c8a 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -18,14 +18,11 @@ //! | `do_end_of_xds` | [`do_end_of_xds`] | //! | `xds_debug_test` | [`xds_debug_test`] | //! | `xds_cea608_test` | [`xds_cea608_test`] | -//! | `XDS_TYPE_*` defines | [`XdsPackeType`] enum | //! | `XDS_CLASS_*` defines | [`XdsClass`] enum | -//! | `ts_start_of_xds` global | Parameter or context field | -//! | `last_c1`, `last_c2` statics | Context fields or function-local state | +//! | `ts_start_of_xds` global | [`TS_START_OF_XDS`] | //! | `cc_subtitle` struct | [`cc_subtitle`] (C binding) | //! | `eia608_screen` struct | [`eia608_screen`] (C binding) | -//! | `SFORMAT_XDS` | [`SFORMAT_XDS`] constant | -//! | `get_fts(ctx->timing, 2)` | [`TimingContext::get_fts`] | +//! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | #[allow(clippy::all)] pub mod bindings { @@ -1235,7 +1232,7 @@ pub unsafe fn xds_do_channel( } // XDS_TYPE_TSID = 4 - Some(XdsPacketType::Tsid => { + Some(XdsPacketType::Tsid) => { // According to CEA-608, data here (4 bytes) are used to identify the // "originating analog licensee". No interesting data for us. was_proc = 1; diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 074a9d25b..f208c2c59 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -7,13 +7,15 @@ //! # Submodules //! //! - [`handlers`] - XDS packet processing and dispatch functions -//! - [`types`] - XDS-specific types, enums, and constants +//! - [`types`] - XDS-specific types +//! - [`constants`] - XDS enums and constants //! //! For detailed function-level mappings, see the [`handlers`] module documentation //! For type definitions, see the [`types`] module pub mod handlers; pub mod types; +pub mod constants; use crate::bindings::*; use crate::ctorust::FromCType; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 34d29a3e7..40db0a0b8 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -9,37 +9,26 @@ //! - [`XdsType`] - Specific type within each class (PIN, Program Name, Content Advisory, etc.) //! - [`XdsBuffer`] - Buffer for accumulating XDS packet bytes //! - [`CcxDecodersXdsContext`] - Main context structure for XDS decoding state -//! -//! # Constants -//! -//! - [`NUM_XDS_BUFFERS`] - Maximum number of concurrent XDS buffers (9) -//! - [`NUM_BYTES_PER_PACKET`] - Maximum bytes per XDS packet (35) -//! - [`XDS_CLASSES`] - Human-readable names for XDS classes -//! - [`XDS_PROGRAM_TYPES`] - Program type descriptions (Education, Entertainment, etc.) -//! +//! //! # Conversion Guide //! -//! | C (ccx_decoders_xds.c/.h) | Rust (types.rs) | -//! |---------------------------------------|---------------------------------------------------| -//! | `XDS_CLASS_*` constants | [`XdsClass`] enum | -//! | `XDS_TYPE_*` constants | [`XdsType`] enum variants | -//! | `struct xds_buffer` | [`XdsBuffer`] | -//! | `ccx_decoders_xds_context` | [`CcxDecodersXdsContext`] | -//! | `xds_class` (int) | [`XdsClass::from_c_int`], [`XdsClass::to_c_int`] | -//! | `xds_type` (int) | [`XdsType::from_c_int`], [`XdsType::to_c_int`] | -//! | `xds_program_type` array | [`XDS_PROGRAM_TYPES`] constant array | -//! | `xds_classes` array | [`XDS_CLASSES`] constant array | -//! | `clear_xds_buffer` | [`CcxDecodersXdsContext::clear_xds_buffer`] | -//! | `how_many_used` | [`CcxDecodersXdsContext::how_many_used`] | -//! | `process_xds_bytes` | [`CcxDecodersXdsContext::process_xds_bytes`] | -//! | `CcxDecodersXdsContext::from_ctype` | Convert from C `ccx_decoders_xds_context` | -//! | `copy_xds_context_from_rust_to_c` | Sync Rust context back to C struct | +//! | C (ccx_decoders_xds.c/.h) | Rust (types.rs) | +//! |------------------------------------|-----------------------------------------------| +//! | `struct xds_buffer` | [`XdsBuffer`] | +//! | `ccx_decoders_xds_context` | [`CcxDecodersXdsContext`] | +//! | `clear_xds_buffer` | [`CcxDecodersXdsContext::clear_xds_buffer`] | +//! | `how_many_used` | [`CcxDecodersXdsContext::how_many_used`] | +//! | `process_xds_bytes` | [`CcxDecodersXdsContext::process_xds_bytes`] | +//! | C struct -> Rust | [`CcxDecodersXdsContext::from_ctype`] | +//! | Rust -> C struct | [`copy_xds_context_from_rust_to_c`] | + use crate::bindings::*; use crate::common::CType; use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; use lib_ccxr::time::TimingContext; +pub use crate::xds::constants::*; use std::os::raw::c_int; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero From f66ab8fded324c631ffa5bea0890ba45f3089b49 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 19:18:08 +0530 Subject: [PATCH 19/34] refactor to move constants, statics, enums to separate file from types.rs, handlers.rs --- src/rust/src/xds/constants.rs | 234 +++++++++++++++++++++++++-- src/rust/src/xds/handlers.rs | 227 +------------------------- src/rust/src/xds/mod.rs | 3 +- src/rust/src/xds/types.rs | 295 +--------------------------------- 4 files changed, 230 insertions(+), 529 deletions(-) diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs index b736fb9c8..9e024ca47 100644 --- a/src/rust/src/xds/constants.rs +++ b/src/rust/src/xds/constants.rs @@ -2,16 +2,20 @@ //! //! # Conversion Guide //! -//! | C (ccx_decoders_xds.c/.h) | Rust (constants.rs) | -//! |----------------------------------|--------------------------------------------------| -//! | `XDS_CLASS_*` defines | [`XdsClass`] enum | -//! | `XDS_TYPE_*` defines | [`XdsType`], [`XdsCurrentFutureType`], etc. | -//! | `xds_class` (int field) | [`XdsClass::from_c_int`], [`XdsClass::to_c_int`] | -//! | `xds_type` (int field) | [`XdsType::from_c_int`], [`XdsType::to_c_int`] | -//! | `xds_program_type[]` array | [`XDS_PROGRAM_TYPES`] | -//! | `xds_classes[]` array | [`XDS_CLASSES`] | +//! | C (ccx_decoders_xds.c/.h) | Rust (constants.rs) | +//! |------------------------------------|--------------------------------------------------| +//! | `XDS_CLASS_*` defines | [`XdsClass`] enum | +//! | `XDS_TYPE_*` defines | [`XdsType`], [`XdsCurrentFutureType`], etc. | +//! | `xds_class` (int field) | [`XdsClass::from_c_int`], [`XdsClass::to_c_int`] | +//! | `xds_type` (int field) | [`XdsType::from_c_int`], [`XdsType::to_c_int`] | +//! | `xds_program_type[]` array | [`XDS_PROGRAM_TYPES`] | +//! | `xds_classes[]` array | [`XDS_CLASSES`] | +//! | `ts_start_of_xds` global | [`TS_START_OF_XDS`] | +//! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | use std::os::raw::c_int; +use std::sync::Mutex; +use std::sync::atomic::AtomicI64; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe @@ -296,4 +300,216 @@ impl XdsType { }, } } -} \ No newline at end of file +} + +pub static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet + +/// XDS packet type codes used in `cur_xds_packet_type` matching +/// +/// Maps integer constants from the C `XDS_TYPE_*` defines to a Rust enum +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(i32)] +pub enum XdsPacketType { + PinStartTime = 1, + LengthAndCurrentTime = 2, + ProgramName = 3, + ProgramType = 4, + ContentAdvisory = 5, + AudioServices = 6, + Cgms = 8, + AspectRatioInfo = 9, + ProgramDesc1 = 0x10, + ProgramDesc2 = 0x11, + ProgramDesc3 = 0x12, + ProgramDesc4 = 0x13, + ProgramDesc5 = 0x14, + ProgramDesc6 = 0x15, + ProgramDesc7 = 0x16, + ProgramDesc8 = 0x17, + // channel class types + NetworkName = 0x101, + CallLettersAndChannel = 0x102, + Tsid = 0x104, + // misc class types + TimeOfDay = 0x201, + LocalTimeZone = 0x204, +} + +impl XdsPacketType { + /// Convert a raw `cur_xds_packet_type` integer (Current/Future class) to enum. + pub fn from_current_future(value: i32) -> Option { + match value { + 1 => Some(Self::PinStartTime), + 2 => Some(Self::LengthAndCurrentTime), + 3 => Some(Self::ProgramName), + 4 => Some(Self::ProgramType), + 5 => Some(Self::ContentAdvisory), + 6 => Some(Self::AudioServices), + 8 => Some(Self::Cgms), + 9 => Some(Self::AspectRatioInfo), + 0x10..=0x17 => Some(match value { + 0x10 => Self::ProgramDesc1, + 0x11 => Self::ProgramDesc2, + 0x12 => Self::ProgramDesc3, + 0x13 => Self::ProgramDesc4, + 0x14 => Self::ProgramDesc5, + 0x15 => Self::ProgramDesc6, + 0x16 => Self::ProgramDesc7, + 0x17 => Self::ProgramDesc8, + _ => unreachable!(), + }), + _ => None, + } + } + + /// Convert a raw `cur_xds_packet_type` integer (Channel class) to enum. + pub fn from_channel(value: i32) -> Option { + match value { + 1 => Some(Self::NetworkName), + 2 => Some(Self::CallLettersAndChannel), + 4 => Some(Self::Tsid), + _ => None, + } + } + + /// Convert a raw `cur_xds_packet_type` integer (Misc class) to enum. + pub fn from_misc(value: i32) -> Option { + match value { + 1 => Some(Self::TimeOfDay), + 4 => Some(Self::LocalTimeZone), + _ => None, + } + } + + /// Returns true if this is a program description type (0x10-0x17). + pub fn is_program_desc(&self) -> bool { + matches!( + self, + Self::ProgramDesc1 + | Self::ProgramDesc2 + | Self::ProgramDesc3 + | Self::ProgramDesc4 + | Self::ProgramDesc5 + | Self::ProgramDesc6 + | Self::ProgramDesc7 + | Self::ProgramDesc8 + ) + } +} + +/// State for Content Advisory caching +pub(crate) struct ContentAdvisoryState { + pub last_c1: u32, + pub last_c2: u32, + pub age: String, + pub content: String, + pub rating: String, +} + +impl Default for ContentAdvisoryState { + fn default() -> Self { + ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), + } + } +} + +/// State for CGMS (Copy Generation Management System) caching +pub(crate) struct CgmsState { + pub last_c1: u32, + pub last_c2: u32, + pub copy_permitted: String, + pub aps: String, + pub rcd: String, +} + +impl Default for CgmsState { + fn default() -> Self { + CgmsState { + last_c1: u32::MAX, + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), + } + } +} + +/// Cached state for Content Advisory to detect changes +pub(crate) static CONTENT_ADVISORY_STATE: Mutex = + Mutex::new(ContentAdvisoryState { + last_c1: u32::MAX, + last_c2: u32::MAX, + age: String::new(), + content: String::new(), + rating: String::new(), + }); + +/// US TV Parental Guidelines age ratings +pub(crate) const US_TV_AGE_TEXT: [&str; 8] = [ + "None", + "TV-Y (All Children)", + "TV-Y7 (Older Children)", + "TV-G (General Audience)", + "TV-PG (Parental Guidance Suggested)", + "TV-14 (Parents Strongly Cautioned)", + "TV-MA (Mature Audience Only)", + "None", +]; + +/// MPA rating text +pub(crate) const MPA_RATING_TEXT: [&str; 8] = + ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; + +/// Canadian English Language Rating +pub(crate) const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ + "Exempt", + "Children", + "Children eight years and older", + "General programming suitable for all audiences", + "Parental Guidance", + "Viewers 14 years and older", + "Adult Programming", + "[undefined]", +]; + +/// Canadian French Language Rating +pub(crate) const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ + "Exempt?es", + "G?n?ral", + "G?n?ral - D?conseill? aux jeunes enfants", + "Cette ?mission peut ne pas convenir aux enfants de moins de 13 ans", + "Cette ?mission ne convient pas aux moins de 16 ans", + "Cette ?mission est r?serv?e aux adultes", + "[invalid]", + "[invalid]", +]; + +/// Cached state for Copy Generation Management System (CGMS) to detect changes +pub(crate) static CGMS_STATE: Mutex = Mutex::new(CgmsState { + last_c1: u32::MAX, + last_c2: u32::MAX, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), +}); + +/// Copy Generation Management System text descriptions +pub(crate) const COPY_TEXT: [&str; 4] = [ + "Copy permitted (no restrictions)", + "No more copies (one generation copy has been made)", + "One generation of copies can be made", + "No copying is permitted", +]; + +/// APS (Analog Protection System) mode descriptions +pub(crate) const APS_TEXT: [&str; 4] = [ + "No APS", + "PSP On; Split Burst Off", + "PSP On; 2 line Split Burst On", + "PSP On; 4 line Split Burst On", +]; \ No newline at end of file diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 848fe9c8a..6643efb5f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -18,11 +18,8 @@ //! | `do_end_of_xds` | [`do_end_of_xds`] | //! | `xds_debug_test` | [`xds_debug_test`] | //! | `xds_cea608_test` | [`xds_cea608_test`] | -//! | `XDS_CLASS_*` defines | [`XdsClass`] enum | -//! | `ts_start_of_xds` global | [`TS_START_OF_XDS`] | //! | `cc_subtitle` struct | [`cc_subtitle`] (C binding) | //! | `eia608_screen` struct | [`eia608_screen`] (C binding) | -//! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | #[allow(clippy::all)] pub mod bindings { @@ -32,8 +29,7 @@ pub mod bindings { use std::ffi::CString; use std::os::raw::c_void; use std::ptr::null_mut; -use std::sync::atomic::{AtomicI64, Ordering}; -use std::sync::Mutex; +use std::sync::atomic::Ordering; use crate::bindings::{cc_subtitle, eia608_screen, realloc}; @@ -43,6 +39,7 @@ use lib_ccxr::time::c_functions::get_fts; use lib_ccxr::time::CaptionField; use lib_ccxr::util::log::{hex_dump, send_gui, DebugMessageFlag, GuiXdsMessage}; +use crate::xds::constants::*; use crate::xds::types::*; extern "C" { @@ -50,226 +47,6 @@ extern "C" { fn free(ptr: *mut c_void); } -pub static TS_START_OF_XDS: AtomicI64 = AtomicI64::new(-1); // Time at which we switched to XDS mode, =-1 hasn't happened yet - // Usage example: - // TS_START_OF_XDS.store(new_value, Ordering::SeqCst); - // let value = TS_START_OF_XDS.load(Ordering::SeqCst); - - -/// XDS packet type codes used in `cur_xds_packet_type` matching -/// -/// Maps integer constants from the C `XDS_TYPE_*` defines to a Rust enum -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(i32)] -pub enum XdsPacketType { - PinStartTime = 1, - LengthAndCurrentTime = 2, - ProgramName = 3, - ProgramType = 4, - ContentAdvisory = 5, - AudioServices = 6, - Cgms = 8, - AspectRatioInfo = 9, - ProgramDesc1 = 0x10, - ProgramDesc2 = 0x11, - ProgramDesc3 = 0x12, - ProgramDesc4 = 0x13, - ProgramDesc5 = 0x14, - ProgramDesc6 = 0x15, - ProgramDesc7 = 0x16, - ProgramDesc8 = 0x17, - // channel class types - NetworkName = 0x101, - CallLettersAndChannel = 0x102, - Tsid = 0x104, - // misc class types - TimeOfDay = 0x201, - LocalTimeZone = 0x204, -} - -impl XdsPacketType { - /// Convert a raw `cur_xds_packet_type` integer (Current/Future class) to enum. - pub fn from_current_future(value: i32) -> Option { - match value { - 1 => Some(Self::PinStartTime), - 2 => Some(Self::LengthAndCurrentTime), - 3 => Some(Self::ProgramName), - 4 => Some(Self::ProgramType), - 5 => Some(Self::ContentAdvisory), - 6 => Some(Self::AudioServices), - 8 => Some(Self::Cgms), - 9 => Some(Self::AspectRatioInfo), - 0x10..=0x17 => Some(match value { - 0x10 => Self::ProgramDesc1, - 0x11 => Self::ProgramDesc2, - 0x12 => Self::ProgramDesc3, - 0x13 => Self::ProgramDesc4, - 0x14 => Self::ProgramDesc5, - 0x15 => Self::ProgramDesc6, - 0x16 => Self::ProgramDesc7, - 0x17 => Self::ProgramDesc8, - _ => unreachable!(), - }), - _ => None, - } - } - - /// Convert a raw `cur_xds_packet_type` integer (Channel class) to enum. - pub fn from_channel(value: i32) -> Option { - match value { - 1 => Some(Self::NetworkName), - 2 => Some(Self::CallLettersAndChannel), - 4 => Some(Self::Tsid), - _ => None, - } - } - - /// Convert a raw `cur_xds_packet_type` integer (Misc class) to enum. - pub fn from_misc(value: i32) -> Option { - match value { - 1 => Some(Self::TimeOfDay), - 4 => Some(Self::LocalTimeZone), - _ => None, - } - } - - /// Returns true if this is a program description type (0x10-0x17). - pub fn is_program_desc(&self) -> bool { - matches!( - self, - Self::ProgramDesc1 - | Self::ProgramDesc2 - | Self::ProgramDesc3 - | Self::ProgramDesc4 - | Self::ProgramDesc5 - | Self::ProgramDesc6 - | Self::ProgramDesc7 - | Self::ProgramDesc8 - ) - } -} - -// --- structs --- - -/// State for Content Advisory caching -struct ContentAdvisoryState { - last_c1: u32, - last_c2: u32, - age: String, - content: String, - rating: String, -} - -impl Default for ContentAdvisoryState { - fn default() -> Self { - ContentAdvisoryState { - last_c1: u32::MAX, - last_c2: u32::MAX, - age: String::new(), - content: String::new(), - rating: String::new(), - } - } -} - - - -/// State for CGMS (Copy Generation Management System) caching -struct CgmsState { - last_c1: u32, - last_c2: u32, - copy_permitted: String, - aps: String, - rcd: String, -} - -impl Default for CgmsState { - fn default() -> Self { - CgmsState { - last_c1: u32::MAX, // equivalent to -1 for unsigned in C - last_c2: u32::MAX, - copy_permitted: String::new(), - aps: String::new(), - rcd: String::new(), - } - } -} - -// --- static / consts --- - -/// Cached state for Content Advisory to detect changes -static CONTENT_ADVISORY_STATE: Mutex = Mutex::new(ContentAdvisoryState { - last_c1: u32::MAX, - last_c2: u32::MAX, - age: String::new(), - content: String::new(), - rating: String::new(), -}); - -/// US TV Parental Guidelines age ratings -const US_TV_AGE_TEXT: [&str; 8] = [ - "None", - "TV-Y (All Children)", - "TV-Y7 (Older Children)", - "TV-G (General Audience)", - "TV-PG (Parental Guidance Suggested)", - "TV-14 (Parents Strongly Cautioned)", - "TV-MA (Mature Audience Only)", - "None", -]; - -/// MPA rating text -const MPA_RATING_TEXT: [&str; 8] = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; - -/// Canadian English Language Rating -const CANADIAN_ENGLISH_RATING_TEXT: [&str; 8] = [ - "Exempt", - "Children", - "Children eight years and older", - "General programming suitable for all audiences", - "Parental Guidance", - "Viewers 14 years and older", - "Adult Programming", - "[undefined]", -]; - -/// Canadian French Language Rating -const CANADIAN_FRENCH_RATING_TEXT: [&str; 8] = [ - "Exempt?es", - "G?n?ral", - "G?n?ral - D?conseill? aux jeunes enfants", - "Cette ?mission peut ne pas convenir aux enfants de moins de 13 ans", - "Cette ?mission ne convient pas aux moins de 16 ans", - "Cette ?mission est r?serv?e aux adultes", - "[invalid]", - "[invalid]", -]; - -/// Cached state for Copy Generation Management System (CGMS) to detect changes -static CGMS_STATE: Mutex = Mutex::new(CgmsState { - last_c1: u32::MAX, - last_c2: u32::MAX, - copy_permitted: String::new(), - aps: String::new(), - rcd: String::new(), -}); - -/// Copy Generation Management System text descriptions -const COPY_TEXT: [&str; 4] = [ - "Copy permitted (no restrictions)", - "No more copies (one generation copy has been made)", - "One generation of copies can be made", - "No copying is permitted", -]; - -/// APS (Analog Protection System) mode descriptions -const APS_TEXT: [&str; 4] = [ - "No APS", - "PSP On; Split Burst Off", - "PSP On; 2 line Split Burst On", - "PSP On; 4 line Split Burst On", -]; - // --- helper func --- /// Frees a pointer and sets it to null. diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index f208c2c59..811fe82fa 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -21,7 +21,8 @@ use crate::bindings::*; use crate::ctorust::FromCType; use crate::libccxr_exports::time::apply_timing_info; use crate::libccxr_exports::time::generate_timing_context; -use crate::xds::handlers::{do_end_of_xds, TS_START_OF_XDS}; +use crate::xds::constants::TS_START_OF_XDS; +use crate::xds::handlers::do_end_of_xds; use crate::xds::types::{copy_xds_context_from_rust_to_c, CcxDecodersXdsContext}; use std::os::raw::c_int; use std::sync::atomic::Ordering; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 40db0a0b8..523ae51d5 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -5,11 +5,9 @@ //! //! # Key Types //! -//! - [`XdsClass`] - Classification of XDS packet (Current, Future, Channel, Misc, Private, OutOfBand) -//! - [`XdsType`] - Specific type within each class (PIN, Program Name, Content Advisory, etc.) //! - [`XdsBuffer`] - Buffer for accumulating XDS packet bytes //! - [`CcxDecodersXdsContext`] - Main context structure for XDS decoding state -//! +//! //! # Conversion Guide //! //! | C (ccx_decoders_xds.c/.h) | Rust (types.rs) | @@ -22,7 +20,6 @@ //! | C struct -> Rust | [`CcxDecodersXdsContext::from_ctype`] | //! | Rust -> C struct | [`copy_xds_context_from_rust_to_c`] | - use crate::bindings::*; use crate::common::CType; use crate::ctorust::FromCType; @@ -31,296 +28,6 @@ use lib_ccxr::time::TimingContext; pub use crate::xds::constants::*; use std::os::raw::c_int; -pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero -pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe - -pub static XDS_CLASSES: [&str; 8] = [ - "Current", - "Future", - "Channel", - "Miscellaneous", - "Public service", - "Reserved", - "Private data", - "End", -]; - -pub static XDS_PROGRAM_TYPES: [&str; 96] = [ - "Education", - "Entertainment", - "Movie", - "News", - "Religious", - "Sports", - "Other", - "Action", - "Advertisement", - "Animated", - "Anthology", - "Automobile", - "Awards", - "Baseball", - "Basketball", - "Bulletin", - "Business", - "Classical", - "College", - "Combat", - "Comedy", - "Commentary", - "Concert", - "Consumer", - "Contemporary", - "Crime", - "Dance", - "Documentary", - "Drama", - "Elementary", - "Erotica", - "Exercise", - "Fantasy", - "Farm", - "Fashion", - "Fiction", - "Food", - "Football", - "Foreign", - "Fund-Raiser", - "Game/Quiz", - "Garden", - "Golf", - "Government", - "Health", - "High_School", - "History", - "Hobby", - "Hockey", - "Home", - "Horror", - "Information", - "Instruction", - "International", - "Interview", - "Language", - "Legal", - "Live", - "Local", - "Math", - "Medical", - "Meeting", - "Military", - "Mini-Series", - "Music", - "Mystery", - "National", - "Nature", - "Police", - "Politics", - "Premiere", - "Pre-Recorded", - "Product", - "Professional", - "Public", - "Racing", - "Reading", - "Repair", - "Repeat", - "Review", - "Romance", - "Science", - "Series", - "Service", - "Shopping", - "Soap_Opera", - "Special", - "Suspense", - "Talk", - "Technical", - "Tennis", - "Travel", - "Variety", - "Video", - "Weather", - "Western", -]; - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum XdsClass { - Current = 0, - Future = 1, - Channel = 2, - Misc = 3, - Public = 4, - Reserved = 5, - Private = 6, - End = 7, - OutOfBand = 0x40, -} - -impl XdsClass { - pub fn from_c_int(value: c_int) -> Option { - match value { - 0 => Some(XdsClass::Current), - 1 => Some(XdsClass::Future), - 2 => Some(XdsClass::Channel), - 3 => Some(XdsClass::Misc), - 4 => Some(XdsClass::Public), - 5 => Some(XdsClass::Reserved), - 6 => Some(XdsClass::Private), - 7 => Some(XdsClass::End), - 0x40 => Some(XdsClass::OutOfBand), - _ => None, - } - } - - pub fn to_c_int(&self) -> c_int { - match self { - XdsClass::Current => 0, - XdsClass::Future => 1, - XdsClass::Channel => 2, - XdsClass::Misc => 3, - XdsClass::Public => 4, - XdsClass::Reserved => 5, - XdsClass::Private => 6, - XdsClass::End => 7, - XdsClass::OutOfBand => 0x40, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum XdsCurrentFutureType { - PinStartTime = 1, - LengthAndCurrentTime = 2, - ProgramName = 3, - ProgramType = 4, - ContentAdvisory = 5, - AudioServices = 6, - Cgms = 8, - AspectRatioInfo = 9, - ProgramDesc1 = 0x10, - ProgramDesc2 = 0x11, - ProgramDesc3 = 0x12, - ProgramDesc4 = 0x13, - ProgramDesc5 = 0x14, - ProgramDesc6 = 0x15, - ProgramDesc7 = 0x16, - ProgramDesc8 = 0x17, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum XdsChannelType { - NetworkName = 1, - CallLettersAndChannel = 2, - Tsid = 4, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum XdsMiscType { - TimeOfDay = 1, - LocalTimeZone = 4, - OutOfBandChannelNumber = 0x40, -} - -// this is new - was not there in my original code -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum XdsType { - CurrentFuture(XdsCurrentFutureType), - Misc(XdsMiscType), - Channel(XdsChannelType), -} - -impl XdsType { - pub fn from_c_int(class: Option, type_value: c_int) -> Option { - match class? { - XdsClass::Current | XdsClass::Future => match type_value { - 1 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::PinStartTime)), - 2 => Some(XdsType::CurrentFuture( - XdsCurrentFutureType::LengthAndCurrentTime, - )), - 3 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramName)), - 4 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramType)), - 5 => Some(XdsType::CurrentFuture( - XdsCurrentFutureType::ContentAdvisory, - )), - 6 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::AudioServices)), - 8 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::Cgms)), - 9 => Some(XdsType::CurrentFuture( - XdsCurrentFutureType::AspectRatioInfo, - )), - 0x10 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc1)), - 0x11 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc2)), - 0x12 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc3)), - 0x13 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc4)), - 0x14 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc5)), - 0x15 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc6)), - 0x16 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc7)), - 0x17 => Some(XdsType::CurrentFuture(XdsCurrentFutureType::ProgramDesc8)), - _ => None, - }, - XdsClass::Channel => match type_value { - 1 => Some(XdsType::Channel(XdsChannelType::NetworkName)), - 2 => Some(XdsType::Channel(XdsChannelType::CallLettersAndChannel)), - 4 => Some(XdsType::Channel(XdsChannelType::Tsid)), - _ => None, - }, - XdsClass::Misc => match type_value { - 1 => Some(XdsType::Misc(XdsMiscType::TimeOfDay)), - 4 => Some(XdsType::Misc(XdsMiscType::LocalTimeZone)), - 0x40 => Some(XdsType::Misc(XdsMiscType::OutOfBandChannelNumber)), - _ => None, - }, - XdsClass::Public - | XdsClass::Reserved - | XdsClass::Private - | XdsClass::End - | XdsClass::OutOfBand => { - // These classes don't have specific types defined yet - // Return None for now - None - } - } - } - - pub fn to_c_int(&self) -> c_int { - match self { - XdsType::CurrentFuture(t) => match t { - XdsCurrentFutureType::PinStartTime => 1, - XdsCurrentFutureType::LengthAndCurrentTime => 2, - XdsCurrentFutureType::ProgramName => 3, - XdsCurrentFutureType::ProgramType => 4, - XdsCurrentFutureType::ContentAdvisory => 5, - XdsCurrentFutureType::AudioServices => 6, - XdsCurrentFutureType::Cgms => 8, - XdsCurrentFutureType::AspectRatioInfo => 9, - XdsCurrentFutureType::ProgramDesc1 => 0x10, - XdsCurrentFutureType::ProgramDesc2 => 0x11, - XdsCurrentFutureType::ProgramDesc3 => 0x12, - XdsCurrentFutureType::ProgramDesc4 => 0x13, - XdsCurrentFutureType::ProgramDesc5 => 0x14, - XdsCurrentFutureType::ProgramDesc6 => 0x15, - XdsCurrentFutureType::ProgramDesc7 => 0x16, - XdsCurrentFutureType::ProgramDesc8 => 0x17, - }, - XdsType::Channel(t) => match t { - XdsChannelType::NetworkName => 1, - XdsChannelType::CallLettersAndChannel => 2, - XdsChannelType::Tsid => 4, - }, - XdsType::Misc(t) => match t { - XdsMiscType::TimeOfDay => 1, - XdsMiscType::LocalTimeZone => 4, - XdsMiscType::OutOfBandChannelNumber => 0x40, - }, - } - } -} - #[repr(C)] #[derive(Copy, Clone)] pub struct XdsBuffer { From 9ec38faa05398d6cdcf524896c5ce6e0e3067855 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 21:54:21 +0530 Subject: [PATCH 20/34] fix minor console output regression due to replacement of log::info with lib_ccxr::info --- src/rust/src/xds/handlers.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 6643efb5f..e580a1141 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -259,7 +259,7 @@ impl CcxDecodersXdsContext<'_> { ); if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { - info!("\rNote: Illegal XDS data"); + info!("\r\x1b[KNote: Illegal XDS data"); return; } } @@ -347,9 +347,9 @@ pub unsafe fn xds_do_copy_generation_management_system( // Log if changed if changed { - info!("\rXDS: {}\n", state.copy_permitted); - info!("\rXDS: {}\n", state.aps); - info!("\rXDS: {}\n", state.rcd); + info!("\r\x1b[KXDS: {}\n", state.copy_permitted); + info!("\r\x1b[KXDS: {}\n", state.aps); + info!("\r\x1b[KXDS: {}\n", state.rcd); } // Debug output (always, when debug mask matches) @@ -486,8 +486,8 @@ pub unsafe fn xds_do_content_advisory( } if changed { - info!("\rXDS: {}\n ", state.age); - info!("\rXDS: {}\n ", state.content); + info!("\r\x1b[KXDS: {}\n ", state.age); + info!("\r\x1b[KXDS: {}\n ", state.content); } debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.age); @@ -499,7 +499,7 @@ pub unsafe fn xds_do_content_advisory( let _ = xdsprint(sub, ctx, state.rating.clone()); if changed { - info!("\rXDS: {}\n ", state.rating); + info!("\r\x1b[KXDS: {}\n ", state.rating); } debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rating); @@ -580,7 +580,7 @@ pub unsafe fn xds_do_current_and_future( // XDS_CLASS_CURRENT = 0 if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == 0 { - info!("\rXDS: Program changed.\n"); + info!("\r\x1b[KXDS: Program changed.\n"); info!( "XDS program start time (DD/MM HH:MM) {:02}-{:02} {:02}:{:02}\n", date, month, hour, min @@ -607,7 +607,7 @@ pub unsafe fn xds_do_current_and_future( let hour = (payload[3] & 0x1f) as i32; // 5 bits if ctx.xds_program_length_shown == 0 { - info!("\rXDS: Program length (HH:MM): {:02}:{:02} ", hour, min); + info!("\r\x1b[KXDS: Program length (HH:MM): {:02}:{:02} ", hour, min); } else { debug!( msg_type = DebugMessageFlag::DECODER_XDS; @@ -687,7 +687,7 @@ pub unsafe fn xds_do_current_and_future( // XDS_CLASS_CURRENT = 0 if ctx.cur_xds_packet_class == 0 && xds_program_name != current_name { - info!("\rXDS Notice: Program is now {}\n", xds_program_name); + info!("\r\x1b[KXDS Notice: Program is now {}\n", xds_program_name); string_to_i8_array(&xds_program_name, &mut ctx.current_xds_program_name); send_gui(GuiXdsMessage::ProgramName(&xds_program_name)); } @@ -729,7 +729,7 @@ pub unsafe fn xds_do_current_and_future( } if ctx.current_program_type_reported == 0 { - info!("\rXDS Program Type: "); + info!("\r\x1b[KXDS Program Type: "); } let mut type_str = String::new(); @@ -877,7 +877,7 @@ pub unsafe fn xds_do_current_and_future( let changed = xds_desc != current_desc; if changed { - info!("\rXDS description line {}: {}\n", line_num, xds_desc); + info!("\r\x1b[KXDS description line {}: {}\n", line_num, xds_desc); if (line_num as usize) < 8 { string_to_i8_array( &xds_desc, From 23f6ae7350c18edbd7636adfb45621aeaa73bbf9 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 22:02:46 +0530 Subject: [PATCH 21/34] fix minor windows build issue in ci --- src/rust/src/xds/handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index e580a1141..01eaf8647 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -105,7 +105,7 @@ pub unsafe fn write_xds_string( ) -> Result<(), ()> { let new_size = (sub.nb_data + 1) as usize * size_of::(); let new_data = - unsafe { realloc(sub.data as *mut c_void, new_size as u64) as *mut eia608_screen }; + unsafe { realloc(sub.data as *mut c_void, (new_size as u64).try_into().unwrap()) as *mut eia608_screen }; if new_data.is_null() { freep(&mut sub.data); sub.nb_data = 0; From f2bca5274b69cadd783d7c82bd8a2347c0488277 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 22:07:59 +0530 Subject: [PATCH 22/34] fix clippy errors and run cargo fmt --- src/rust/src/xds/constants.rs | 4 +-- src/rust/src/xds/handlers.rs | 20 +++++++------ src/rust/src/xds/mod.rs | 2 +- src/rust/src/xds/types.rs | 53 ++++++++++++++++++++++++++--------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs index 9e024ca47..ce26f956c 100644 --- a/src/rust/src/xds/constants.rs +++ b/src/rust/src/xds/constants.rs @@ -14,8 +14,8 @@ //! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | use std::os::raw::c_int; -use std::sync::Mutex; use std::sync::atomic::AtomicI64; +use std::sync::Mutex; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe @@ -512,4 +512,4 @@ pub(crate) const APS_TEXT: [&str; 4] = [ "PSP On; Split Burst Off", "PSP On; 2 line Split Burst On", "PSP On; 4 line Split Burst On", -]; \ No newline at end of file +]; diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 01eaf8647..d102eb775 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -26,6 +26,7 @@ pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } +use std::convert::TryInto; use std::ffi::CString; use std::os::raw::c_void; use std::ptr::null_mut; @@ -104,8 +105,12 @@ pub unsafe fn write_xds_string( ts_start_of_xds: i64, ) -> Result<(), ()> { let new_size = (sub.nb_data + 1) as usize * size_of::(); - let new_data = - unsafe { realloc(sub.data as *mut c_void, (new_size as u64).try_into().unwrap()) as *mut eia608_screen }; + let new_data = unsafe { + realloc( + sub.data as *mut c_void, + (new_size as u64).try_into().unwrap(), + ) as *mut eia608_screen + }; if new_data.is_null() { freep(&mut sub.data); sub.nb_data = 0; @@ -158,7 +163,6 @@ pub unsafe fn xdsprint( write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } - /// Utility methods for XDS buffer management and process_xds_bytes (function). impl CcxDecodersXdsContext<'_> { /// Count how many XDS buffers are currently in use @@ -358,7 +362,6 @@ pub unsafe fn xds_do_copy_generation_management_system( debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); } - /// Handles content advisory/rating information (US TV, MPA, Canadian ratings) /// /// # Safety @@ -596,7 +599,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_LENGH_AND_CURRENT_TIME = 2 - Some(XdsPacketType::LengthAndCurrentTime) => { + Some(XdsPacketType::LengthAndCurrentTime) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -607,7 +610,10 @@ pub unsafe fn xds_do_current_and_future( let hour = (payload[3] & 0x1f) as i32; // 5 bits if ctx.xds_program_length_shown == 0 { - info!("\r\x1b[KXDS: Program length (HH:MM): {:02}:{:02} ", hour, min); + info!( + "\r\x1b[KXDS: Program length (HH:MM): {:02}:{:02} ", + hour, min + ); } else { debug!( msg_type = DebugMessageFlag::DECODER_XDS; @@ -909,8 +915,6 @@ pub unsafe fn xds_do_current_and_future( was_proc } - - /// Processes channel-related XDS data (network name, call letters, TSID) /// /// # Safety diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 811fe82fa..37c4de0ec 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -13,9 +13,9 @@ //! For detailed function-level mappings, see the [`handlers`] module documentation //! For type definitions, see the [`types`] module +pub mod constants; pub mod handlers; pub mod types; -pub mod constants; use crate::bindings::*; use crate::ctorust::FromCType; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 523ae51d5..1ee561707 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -24,9 +24,8 @@ use crate::bindings::*; use crate::common::CType; use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; -use lib_ccxr::time::TimingContext; pub use crate::xds::constants::*; -use std::os::raw::c_int; +use lib_ccxr::time::TimingContext; #[repr(C)] #[derive(Copy, Clone)] @@ -327,8 +326,9 @@ mod tests { #[test] fn test_xds_type_future_class_same_as_current() { - for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] - { + for type_val in [ + 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ] { assert_eq!( XdsType::from_c_int(Some(XdsClass::Current), type_val), XdsType::from_c_int(Some(XdsClass::Future), type_val) @@ -394,8 +394,9 @@ mod tests { #[test] fn test_xds_type_roundtrip_current_future() { let class = Some(XdsClass::Current); - for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] - { + for type_val in [ + 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ] { let xds_type = XdsType::from_c_int(class, type_val).unwrap(); assert_eq!(xds_type.to_c_int(), type_val); } @@ -506,17 +507,33 @@ mod tests { #[test] fn test_xds_buffer_roundtrip_all_classes() { let cases: &[(i32, i32)] = &[ - (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 8), (0, 9), - (0, 0x10), (0, 0x17), - (1, 3), // future + programName - (2, 1), (2, 2), (2, 4), // channel - (3, 1), (3, 4), (3, 0x40), // mics + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 8), + (0, 9), + (0, 0x10), + (0, 0x17), + (1, 3), // future + programName + (2, 1), + (2, 2), + (2, 4), // channel + (3, 1), + (3, 4), + (3, 0x40), // mics ]; for &(class, typ) in cases { let c_buf = make_c_buf(1, class, typ, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); let back = unsafe { rust_buf.to_ctype() }; - assert_eq!(back.xds_class, class, "class mismatch for ({}, {})", class, typ); + assert_eq!( + back.xds_class, class, + "class mismatch for ({}, {})", + class, typ + ); assert_eq!(back.xds_type, typ, "type mismatch for ({}, {})", class, typ); } } @@ -526,8 +543,16 @@ mod tests { for class in [4i32, 5, 6, 7, 0x40] { let c_buf = make_c_buf(1, class, 1, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); - assert!(rust_buf.xds_class.is_some(), "expected class Some for {}", class); - assert_eq!(rust_buf.xds_type, None, "expected type None for class {}", class); + assert!( + rust_buf.xds_class.is_some(), + "expected class Some for {}", + class + ); + assert_eq!( + rust_buf.xds_type, None, + "expected type None for class {}", + class + ); let back = unsafe { rust_buf.to_ctype() }; assert_eq!(back.xds_type, -1, "expected -1 in C for class {}", class); } From 9e59457f41b7b4596431120bd53142241938a038 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 23:31:08 +0530 Subject: [PATCH 23/34] revert till f66ab8fd refactor to move constants, statics, enums to separate file from types.rs, handlers.rs --- src/rust/src/xds/constants.rs | 4 +-- src/rust/src/xds/handlers.rs | 42 +++++++++++++-------------- src/rust/src/xds/mod.rs | 2 +- src/rust/src/xds/types.rs | 53 +++++++++-------------------------- 4 files changed, 36 insertions(+), 65 deletions(-) diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs index ce26f956c..9e024ca47 100644 --- a/src/rust/src/xds/constants.rs +++ b/src/rust/src/xds/constants.rs @@ -14,8 +14,8 @@ //! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | use std::os::raw::c_int; -use std::sync::atomic::AtomicI64; use std::sync::Mutex; +use std::sync::atomic::AtomicI64; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe @@ -512,4 +512,4 @@ pub(crate) const APS_TEXT: [&str; 4] = [ "PSP On; Split Burst Off", "PSP On; 2 line Split Burst On", "PSP On; 4 line Split Burst On", -]; +]; \ No newline at end of file diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index d102eb775..6643efb5f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -26,7 +26,6 @@ pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } -use std::convert::TryInto; use std::ffi::CString; use std::os::raw::c_void; use std::ptr::null_mut; @@ -105,12 +104,8 @@ pub unsafe fn write_xds_string( ts_start_of_xds: i64, ) -> Result<(), ()> { let new_size = (sub.nb_data + 1) as usize * size_of::(); - let new_data = unsafe { - realloc( - sub.data as *mut c_void, - (new_size as u64).try_into().unwrap(), - ) as *mut eia608_screen - }; + let new_data = + unsafe { realloc(sub.data as *mut c_void, new_size as u64) as *mut eia608_screen }; if new_data.is_null() { freep(&mut sub.data); sub.nb_data = 0; @@ -163,6 +158,7 @@ pub unsafe fn xdsprint( write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } + /// Utility methods for XDS buffer management and process_xds_bytes (function). impl CcxDecodersXdsContext<'_> { /// Count how many XDS buffers are currently in use @@ -263,7 +259,7 @@ impl CcxDecodersXdsContext<'_> { ); if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { - info!("\r\x1b[KNote: Illegal XDS data"); + info!("\rNote: Illegal XDS data"); return; } } @@ -351,9 +347,9 @@ pub unsafe fn xds_do_copy_generation_management_system( // Log if changed if changed { - info!("\r\x1b[KXDS: {}\n", state.copy_permitted); - info!("\r\x1b[KXDS: {}\n", state.aps); - info!("\r\x1b[KXDS: {}\n", state.rcd); + info!("\rXDS: {}\n", state.copy_permitted); + info!("\rXDS: {}\n", state.aps); + info!("\rXDS: {}\n", state.rcd); } // Debug output (always, when debug mask matches) @@ -362,6 +358,7 @@ pub unsafe fn xds_do_copy_generation_management_system( debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); } + /// Handles content advisory/rating information (US TV, MPA, Canadian ratings) /// /// # Safety @@ -489,8 +486,8 @@ pub unsafe fn xds_do_content_advisory( } if changed { - info!("\r\x1b[KXDS: {}\n ", state.age); - info!("\r\x1b[KXDS: {}\n ", state.content); + info!("\rXDS: {}\n ", state.age); + info!("\rXDS: {}\n ", state.content); } debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.age); @@ -502,7 +499,7 @@ pub unsafe fn xds_do_content_advisory( let _ = xdsprint(sub, ctx, state.rating.clone()); if changed { - info!("\r\x1b[KXDS: {}\n ", state.rating); + info!("\rXDS: {}\n ", state.rating); } debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rating); @@ -583,7 +580,7 @@ pub unsafe fn xds_do_current_and_future( // XDS_CLASS_CURRENT = 0 if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == 0 { - info!("\r\x1b[KXDS: Program changed.\n"); + info!("\rXDS: Program changed.\n"); info!( "XDS program start time (DD/MM HH:MM) {:02}-{:02} {:02}:{:02}\n", date, month, hour, min @@ -599,7 +596,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_LENGH_AND_CURRENT_TIME = 2 - Some(XdsPacketType::LengthAndCurrentTime) => { + Some(XdsPacketType::LengthAndCurrentTime) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -610,10 +607,7 @@ pub unsafe fn xds_do_current_and_future( let hour = (payload[3] & 0x1f) as i32; // 5 bits if ctx.xds_program_length_shown == 0 { - info!( - "\r\x1b[KXDS: Program length (HH:MM): {:02}:{:02} ", - hour, min - ); + info!("\rXDS: Program length (HH:MM): {:02}:{:02} ", hour, min); } else { debug!( msg_type = DebugMessageFlag::DECODER_XDS; @@ -693,7 +687,7 @@ pub unsafe fn xds_do_current_and_future( // XDS_CLASS_CURRENT = 0 if ctx.cur_xds_packet_class == 0 && xds_program_name != current_name { - info!("\r\x1b[KXDS Notice: Program is now {}\n", xds_program_name); + info!("\rXDS Notice: Program is now {}\n", xds_program_name); string_to_i8_array(&xds_program_name, &mut ctx.current_xds_program_name); send_gui(GuiXdsMessage::ProgramName(&xds_program_name)); } @@ -735,7 +729,7 @@ pub unsafe fn xds_do_current_and_future( } if ctx.current_program_type_reported == 0 { - info!("\r\x1b[KXDS Program Type: "); + info!("\rXDS Program Type: "); } let mut type_str = String::new(); @@ -883,7 +877,7 @@ pub unsafe fn xds_do_current_and_future( let changed = xds_desc != current_desc; if changed { - info!("\r\x1b[KXDS description line {}: {}\n", line_num, xds_desc); + info!("\rXDS description line {}: {}\n", line_num, xds_desc); if (line_num as usize) < 8 { string_to_i8_array( &xds_desc, @@ -915,6 +909,8 @@ pub unsafe fn xds_do_current_and_future( was_proc } + + /// Processes channel-related XDS data (network name, call letters, TSID) /// /// # Safety diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 37c4de0ec..811fe82fa 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -13,9 +13,9 @@ //! For detailed function-level mappings, see the [`handlers`] module documentation //! For type definitions, see the [`types`] module -pub mod constants; pub mod handlers; pub mod types; +pub mod constants; use crate::bindings::*; use crate::ctorust::FromCType; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 1ee561707..523ae51d5 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -24,8 +24,9 @@ use crate::bindings::*; use crate::common::CType; use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; -pub use crate::xds::constants::*; use lib_ccxr::time::TimingContext; +pub use crate::xds::constants::*; +use std::os::raw::c_int; #[repr(C)] #[derive(Copy, Clone)] @@ -326,9 +327,8 @@ mod tests { #[test] fn test_xds_type_future_class_same_as_current() { - for type_val in [ - 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - ] { + for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] + { assert_eq!( XdsType::from_c_int(Some(XdsClass::Current), type_val), XdsType::from_c_int(Some(XdsClass::Future), type_val) @@ -394,9 +394,8 @@ mod tests { #[test] fn test_xds_type_roundtrip_current_future() { let class = Some(XdsClass::Current); - for type_val in [ - 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - ] { + for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] + { let xds_type = XdsType::from_c_int(class, type_val).unwrap(); assert_eq!(xds_type.to_c_int(), type_val); } @@ -507,33 +506,17 @@ mod tests { #[test] fn test_xds_buffer_roundtrip_all_classes() { let cases: &[(i32, i32)] = &[ - (0, 1), - (0, 2), - (0, 3), - (0, 4), - (0, 5), - (0, 6), - (0, 8), - (0, 9), - (0, 0x10), - (0, 0x17), - (1, 3), // future + programName - (2, 1), - (2, 2), - (2, 4), // channel - (3, 1), - (3, 4), - (3, 0x40), // mics + (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 8), (0, 9), + (0, 0x10), (0, 0x17), + (1, 3), // future + programName + (2, 1), (2, 2), (2, 4), // channel + (3, 1), (3, 4), (3, 0x40), // mics ]; for &(class, typ) in cases { let c_buf = make_c_buf(1, class, typ, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); let back = unsafe { rust_buf.to_ctype() }; - assert_eq!( - back.xds_class, class, - "class mismatch for ({}, {})", - class, typ - ); + assert_eq!(back.xds_class, class, "class mismatch for ({}, {})", class, typ); assert_eq!(back.xds_type, typ, "type mismatch for ({}, {})", class, typ); } } @@ -543,16 +526,8 @@ mod tests { for class in [4i32, 5, 6, 7, 0x40] { let c_buf = make_c_buf(1, class, 1, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); - assert!( - rust_buf.xds_class.is_some(), - "expected class Some for {}", - class - ); - assert_eq!( - rust_buf.xds_type, None, - "expected type None for class {}", - class - ); + assert!(rust_buf.xds_class.is_some(), "expected class Some for {}", class); + assert_eq!(rust_buf.xds_type, None, "expected type None for class {}", class); let back = unsafe { rust_buf.to_ctype() }; assert_eq!(back.xds_type, -1, "expected -1 in C for class {}", class); } From c1ca3a3732d30f6e6c5168cba09eefd4286c6e62 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 27 Mar 2026 23:50:11 +0530 Subject: [PATCH 24/34] fix c formatting --- src/lib_ccx/ccx_decoders_xds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index bc9b88323..0c3b88269 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -250,7 +250,7 @@ void clear_xds_buffer(struct ccx_decoders_xds_context *ctx, int num) void process_xds_bytes(struct ccx_decoders_xds_context *ctx, const unsigned char hi, int lo) { - ccxr_process_xds_bytes(ctx, hi, lo); + ccxr_process_xds_bytes(ctx, hi, lo); return; // use the rust implementation int is_new; @@ -906,7 +906,7 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) void do_end_of_xds(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned char expected_checksum) { ccxr_set_ts_start_of_xds(ts_start_of_xds); - ccxr_do_end_of_xds(sub, ctx, expected_checksum); + ccxr_do_end_of_xds(sub, ctx, expected_checksum); return; // use the rust implementation int cs = 0; From 4f6fb200e7c7aafcc2e14e2259022143c6908d5e Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sat, 28 Mar 2026 00:32:11 +0530 Subject: [PATCH 25/34] run cargo fmt --- src/rust/src/xds/constants.rs | 4 +-- src/rust/src/xds/handlers.rs | 6 +--- src/rust/src/xds/mod.rs | 2 +- src/rust/src/xds/types.rs | 52 ++++++++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs index 9e024ca47..ce26f956c 100644 --- a/src/rust/src/xds/constants.rs +++ b/src/rust/src/xds/constants.rs @@ -14,8 +14,8 @@ //! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | use std::os::raw::c_int; -use std::sync::Mutex; use std::sync::atomic::AtomicI64; +use std::sync::Mutex; pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe @@ -512,4 +512,4 @@ pub(crate) const APS_TEXT: [&str; 4] = [ "PSP On; Split Burst Off", "PSP On; 2 line Split Burst On", "PSP On; 4 line Split Burst On", -]; \ No newline at end of file +]; diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 6643efb5f..3c3be8d1f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -158,7 +158,6 @@ pub unsafe fn xdsprint( write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } - /// Utility methods for XDS buffer management and process_xds_bytes (function). impl CcxDecodersXdsContext<'_> { /// Count how many XDS buffers are currently in use @@ -358,7 +357,6 @@ pub unsafe fn xds_do_copy_generation_management_system( debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.rcd); } - /// Handles content advisory/rating information (US TV, MPA, Canadian ratings) /// /// # Safety @@ -596,7 +594,7 @@ pub unsafe fn xds_do_current_and_future( } // XDS_TYPE_LENGH_AND_CURRENT_TIME = 2 - Some(XdsPacketType::LengthAndCurrentTime) => { + Some(XdsPacketType::LengthAndCurrentTime) => { was_proc = 1; if ctx.cur_xds_payload_length < 5 { // We need 2 data bytes @@ -909,8 +907,6 @@ pub unsafe fn xds_do_current_and_future( was_proc } - - /// Processes channel-related XDS data (network name, call letters, TSID) /// /// # Safety diff --git a/src/rust/src/xds/mod.rs b/src/rust/src/xds/mod.rs index 811fe82fa..37c4de0ec 100644 --- a/src/rust/src/xds/mod.rs +++ b/src/rust/src/xds/mod.rs @@ -13,9 +13,9 @@ //! For detailed function-level mappings, see the [`handlers`] module documentation //! For type definitions, see the [`types`] module +pub mod constants; pub mod handlers; pub mod types; -pub mod constants; use crate::bindings::*; use crate::ctorust::FromCType; diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 523ae51d5..4c9f59045 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -24,8 +24,8 @@ use crate::bindings::*; use crate::common::CType; use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; -use lib_ccxr::time::TimingContext; pub use crate::xds::constants::*; +use lib_ccxr::time::TimingContext; use std::os::raw::c_int; #[repr(C)] @@ -327,8 +327,9 @@ mod tests { #[test] fn test_xds_type_future_class_same_as_current() { - for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] - { + for type_val in [ + 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ] { assert_eq!( XdsType::from_c_int(Some(XdsClass::Current), type_val), XdsType::from_c_int(Some(XdsClass::Future), type_val) @@ -394,8 +395,9 @@ mod tests { #[test] fn test_xds_type_roundtrip_current_future() { let class = Some(XdsClass::Current); - for type_val in [1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17] - { + for type_val in [ + 1, 2, 3, 4, 5, 6, 8, 9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ] { let xds_type = XdsType::from_c_int(class, type_val).unwrap(); assert_eq!(xds_type.to_c_int(), type_val); } @@ -506,17 +508,33 @@ mod tests { #[test] fn test_xds_buffer_roundtrip_all_classes() { let cases: &[(i32, i32)] = &[ - (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 8), (0, 9), - (0, 0x10), (0, 0x17), - (1, 3), // future + programName - (2, 1), (2, 2), (2, 4), // channel - (3, 1), (3, 4), (3, 0x40), // mics + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 8), + (0, 9), + (0, 0x10), + (0, 0x17), + (1, 3), // future + programName + (2, 1), + (2, 2), + (2, 4), // channel + (3, 1), + (3, 4), + (3, 0x40), // mics ]; for &(class, typ) in cases { let c_buf = make_c_buf(1, class, typ, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); let back = unsafe { rust_buf.to_ctype() }; - assert_eq!(back.xds_class, class, "class mismatch for ({}, {})", class, typ); + assert_eq!( + back.xds_class, class, + "class mismatch for ({}, {})", + class, typ + ); assert_eq!(back.xds_type, typ, "type mismatch for ({}, {})", class, typ); } } @@ -526,8 +544,16 @@ mod tests { for class in [4i32, 5, 6, 7, 0x40] { let c_buf = make_c_buf(1, class, 1, 2); let rust_buf = unsafe { XdsBuffer::from_ctype(c_buf) }.unwrap(); - assert!(rust_buf.xds_class.is_some(), "expected class Some for {}", class); - assert_eq!(rust_buf.xds_type, None, "expected type None for class {}", class); + assert!( + rust_buf.xds_class.is_some(), + "expected class Some for {}", + class + ); + assert_eq!( + rust_buf.xds_type, None, + "expected type None for class {}", + class + ); let back = unsafe { rust_buf.to_ctype() }; assert_eq!(back.xds_type, -1, "expected -1 in C for class {}", class); } From 8f5bb19d5594139c62248bcf455b649568fb8ee0 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sat, 28 Mar 2026 01:24:50 +0530 Subject: [PATCH 26/34] remove usage of free,malloc --- src/rust/src/xds/handlers.rs | 60 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 3c3be8d1f..8079d233f 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -26,12 +26,14 @@ pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } +use std::alloc::{self, Layout}; use std::ffi::CString; +use std::mem::{align_of, size_of}; use std::os::raw::c_void; use std::ptr::null_mut; use std::sync::atomic::Ordering; -use crate::bindings::{cc_subtitle, eia608_screen, realloc}; +use crate::bindings::{cc_subtitle, eia608_screen}; use lib_ccxr::debug; use lib_ccxr::info; @@ -42,31 +44,8 @@ use lib_ccxr::util::log::{hex_dump, send_gui, DebugMessageFlag, GuiXdsMessage}; use crate::xds::constants::*; use crate::xds::types::*; -extern "C" { - fn malloc(size: usize) -> *mut c_void; - fn free(ptr: *mut c_void); -} - // --- helper func --- -/// Frees a pointer and sets it to null. -/// -/// Converts the raw pointer back into a `Box` to deallocate the memory, -/// then sets the original pointer to null to prevent use-after-free. -/// -/// # Safety -/// - The pointer must have been originally allocated by Rust's `Box::into_raw` -/// or equivalent. Using this with C-allocated memory will cause undefined behavior. -/// - The pointer must not be used after this call. -pub fn freep(ptr: &mut *mut T) { - unsafe { - if !ptr.is_null() { - free(*ptr as *mut c_void); - *ptr = null_mut(); - } - } -} - /// Helper function to copy String into i8 array fn string_to_i8_array(s: &str, arr: &mut [i8]) { let bytes = s.as_bytes(); @@ -94,7 +73,6 @@ fn i8_array_to_string(arr: &[i8]) -> String { /// Writes an XDS string to the subtitle output buffer. /// /// # Safety -/// - `sub.data` must be a valid pointer previously allocated by C's malloc/realloc, or null. /// - The caller must ensure `sub` and `ctx` remain valid for the duration of the call. /// - The returned `xds_str` pointer in the screen data must be freed with `CString::from_raw`. pub unsafe fn write_xds_string( @@ -103,11 +81,29 @@ pub unsafe fn write_xds_string( p: String, ts_start_of_xds: i64, ) -> Result<(), ()> { + let c_str = CString::new(p).map_err(|_| ())?; + let len = c_str.as_bytes().len(); // length w/o null terminator - matching C's vsnprintf return + let alloc_len = len + 1; // allocate space for null terminator + let new_size = (sub.nb_data + 1) as usize * size_of::(); - let new_data = - unsafe { realloc(sub.data as *mut c_void, new_size as u64) as *mut eia608_screen }; + let screen_align = align_of::(); + let new_data = unsafe { + if sub.data.is_null() { + let layout = Layout::from_size_align_unchecked(new_size, screen_align); + alloc::alloc(layout) as *mut eia608_screen + } else { + let old_size = sub.nb_data as usize * size_of::(); + let old_layout = Layout::from_size_align_unchecked(old_size, screen_align); + alloc::realloc(sub.data as *mut u8, old_layout, new_size) as *mut eia608_screen + } + }; if new_data.is_null() { - freep(&mut sub.data); + if !sub.data.is_null() && sub.nb_data > 0 { + let old_size = sub.nb_data as usize * size_of::(); + let old_layout = Layout::from_size_align_unchecked(old_size, screen_align); + alloc::dealloc(sub.data as *mut u8, old_layout); + sub.data = null_mut(); + } sub.nb_data = 0; info!("No Memory left"); return Err(()); @@ -115,16 +111,14 @@ pub unsafe fn write_xds_string( sub.data = new_data as *mut c_void; sub.datatype = 0; let data_element = &mut *new_data.add(sub.nb_data as usize); - let c_str = CString::new(p).map_err(|_| ())?; - let len = c_str.as_bytes().len(); // length w/o null terminator - matching C's vsnprintf return - let alloc_len = len + 1; // allocate space for null terminator - let ptr = unsafe { malloc(alloc_len) as *mut i8 }; // fixing c/rust mem mismatch + let str_layout = Layout::from_size_align(alloc_len, 1).map_err(|_| ())?; + let ptr = unsafe { alloc::alloc(str_layout) as *mut i8 }; if ptr.is_null() { return Err(()); } unsafe { - std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, alloc_len); // copy including null terminator + std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, alloc_len); } data_element.format = 2; From c0f44dc3101e363125c08ab960807a6b0a9743ed Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sat, 28 Mar 2026 04:48:47 +0530 Subject: [PATCH 27/34] add XdsError enum, replace Result<(), ()> with Result<(), XdsError> --- src/rust/src/xds/constants.rs | 19 +++++++++++++++++++ src/rust/src/xds/handlers.rs | 12 ++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/rust/src/xds/constants.rs b/src/rust/src/xds/constants.rs index ce26f956c..0b71d54a3 100644 --- a/src/rust/src/xds/constants.rs +++ b/src/rust/src/xds/constants.rs @@ -13,10 +13,29 @@ //! | `ts_start_of_xds` global | [`TS_START_OF_XDS`] | //! | `cur_xds_packet_type` (int match) | [`XdsPacketType`] enum | +use std::fmt; use std::os::raw::c_int; use std::sync::atomic::AtomicI64; use std::sync::Mutex; +/// XDS write errors +#[derive(Debug)] +pub enum XdsError { + /// string contains a null byte + InvalidString, + /// alloc/realloc failed + MemoryAllocation, +} + +impl fmt::Display for XdsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + XdsError::InvalidString => write!(f, "XDS string contains interior null byte"), + XdsError::MemoryAllocation => write!(f, "XDS memory allocation failed"), + } + } +} + pub const NUM_BYTES_PER_PACKET: usize = 35; // Class + type (repeated for convenience) + data + zero pub const NUM_XDS_BUFFERS: usize = 9; // CEA recommends no more than one level of interleaving. Play it safe diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 8079d233f..a0185d271 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -80,8 +80,8 @@ pub unsafe fn write_xds_string( ctx: &mut CcxDecodersXdsContext, p: String, ts_start_of_xds: i64, -) -> Result<(), ()> { - let c_str = CString::new(p).map_err(|_| ())?; +) -> Result<(), XdsError> { + let c_str = CString::new(p).map_err(|_| XdsError::InvalidString)?; let len = c_str.as_bytes().len(); // length w/o null terminator - matching C's vsnprintf return let alloc_len = len + 1; // allocate space for null terminator @@ -106,16 +106,16 @@ pub unsafe fn write_xds_string( } sub.nb_data = 0; info!("No Memory left"); - return Err(()); + return Err(XdsError::MemoryAllocation); } sub.data = new_data as *mut c_void; sub.datatype = 0; let data_element = &mut *new_data.add(sub.nb_data as usize); - let str_layout = Layout::from_size_align(alloc_len, 1).map_err(|_| ())?; + let str_layout = Layout::from_size_align(alloc_len, 1).map_err(|_| XdsError::MemoryAllocation)?; let ptr = unsafe { alloc::alloc(str_layout) as *mut i8 }; if ptr.is_null() { - return Err(()); + return Err(XdsError::MemoryAllocation); } unsafe { std::ptr::copy_nonoverlapping(c_str.as_ptr(), ptr, alloc_len); @@ -144,7 +144,7 @@ pub unsafe fn xdsprint( sub: &mut cc_subtitle, ctx: &mut CcxDecodersXdsContext, message: String, -) -> Result<(), ()> { +) -> Result<(), XdsError> { if !ctx.xds_write_to_file { return Ok(()); } From 81082a6f35cd0f1d430d89f0b4382925c06da8c6 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sat, 28 Mar 2026 04:54:26 +0530 Subject: [PATCH 28/34] run cargo fmt --- src/rust/src/xds/handlers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index a0185d271..e2284038e 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -112,7 +112,8 @@ pub unsafe fn write_xds_string( sub.datatype = 0; let data_element = &mut *new_data.add(sub.nb_data as usize); - let str_layout = Layout::from_size_align(alloc_len, 1).map_err(|_| XdsError::MemoryAllocation)?; + let str_layout = + Layout::from_size_align(alloc_len, 1).map_err(|_| XdsError::MemoryAllocation)?; let ptr = unsafe { alloc::alloc(str_layout) as *mut i8 }; if ptr.is_null() { return Err(XdsError::MemoryAllocation); From 19578946a30a6832fa61c94e70e3c5873bb06ae9 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Mon, 30 Mar 2026 17:51:45 +0530 Subject: [PATCH 29/34] fix : update handlers.rs to match c code after commit 50b51e4234a86e1c5e8020c6d80af63e93528150 --- src/rust/src/xds/handlers.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index e2284038e..223207315 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -480,11 +480,15 @@ pub unsafe fn xds_do_content_advisory( if changed { info!("\rXDS: {}\n ", state.age); - info!("\rXDS: {}\n ", state.content); + if !state.content.is_empty() { + info!("\rXDS: {}\n ", state.content); + } } debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.age); - debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.content); + if !state.content.is_empty() { + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "\rXDS: {}\n", state.content); + } } // MPA, Canadian English, or Canadian French From 8f20d6c0f912c589212ad8e72052f0048d0cf882 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Mon, 30 Mar 2026 20:17:51 +0530 Subject: [PATCH 30/34] fix : remove payload_length < 3 guard --- src/rust/src/xds/handlers.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 223207315..1065aa1b3 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -521,7 +521,7 @@ pub unsafe fn xds_do_current_and_future( let mut was_proc = 0; // Safety check for payload - if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + if ctx.cur_xds_payload.is_null() { return -1; // CCX_EINVAL equivalent } @@ -920,7 +920,7 @@ pub unsafe fn xds_do_channel( let mut was_proc = 0; // Safety check for payload - if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + if ctx.cur_xds_payload.is_null() { return -1; // CCX_EINVAL equivalent } @@ -1047,7 +1047,7 @@ pub unsafe fn xds_do_private_data( ctx: &mut CcxDecodersXdsContext, ) -> i32 { // Safety check for payload - if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + if ctx.cur_xds_payload.is_null() { return -1; // CCX_EINVAL equivalent } @@ -1075,7 +1075,7 @@ pub unsafe fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> i32 { let was_proc; // Safety check for payload - if ctx.cur_xds_payload.is_null() || ctx.cur_xds_payload_length < 3 { + if ctx.cur_xds_payload.is_null() { return -1; // CCX_EINVAL equivalent } @@ -1210,7 +1210,7 @@ pub unsafe fn do_end_of_xds( ); // Validate checksum and minimum length - if cs != expected_checksum as i32 || ctx.cur_xds_payload_length < 3 { + if cs != expected_checksum as i32 { debug!( msg_type = DebugMessageFlag::DECODER_XDS; "Expected checksum: {:02X} Calculated: {:02X}\n", expected_checksum, cs From d4b1e56a1dd2870d228c02aa3e3ef554ce31bb2c Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Mon, 30 Mar 2026 20:29:21 +0530 Subject: [PATCH 31/34] fix : check DECODER_XDS flag --- src/rust/lib_ccxr/src/util/log.rs | 5 +++++ src/rust/src/xds/handlers.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rust/lib_ccxr/src/util/log.rs b/src/rust/lib_ccxr/src/util/log.rs index 155541e49..d853bb46c 100644 --- a/src/rust/lib_ccxr/src/util/log.rs +++ b/src/rust/lib_ccxr/src/util/log.rs @@ -264,6 +264,11 @@ impl<'a> CCExtractorLogger { self.debug_mask.is_debug_mode() } + /// Check if a specific debug flag is set in the current debug mask + pub fn has_debug_flag(&self, flag: DebugMessageFlag) -> bool { + self.debug_mask.mask().intersects(flag) + } + /// Returns the currently set target for logging messages. pub fn target(&self) -> OutputTarget { self.target diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 1065aa1b3..df22a1c07 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -1242,7 +1242,7 @@ pub unsafe fn do_end_of_xds( // Info on future program // Check if debug mask includes DECODER_XDS if let Some(logger) = lib_ccxr::util::log::logger() { - if !logger.is_debug_mode() { + if !logger.has_debug_flag(DebugMessageFlag::DECODER_XDS) { // Don't bother processing something we don't need was_proc = 1; } else { From ed3cc1f9293b63bed0d445125e8a09aaf2710b9b Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 2 Apr 2026 12:17:14 +0530 Subject: [PATCH 32/34] remove libc from src/rust/Cargo.toml --- src/rust/Cargo.lock | 1 - src/rust/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 415a36e0f..88edb6003 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -184,7 +184,6 @@ dependencies = [ "env_logger", "leptonica-sys", "lib_ccxr", - "libc", "log", "num-integer", "palette", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index de40de590..34b704122 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -25,7 +25,6 @@ lib_ccxr = { path = "lib_ccxr" } url = "2.5.4" serial_test = "3.2.0" encoding_rs = "0.8.35" -libc = "0.2.178" # Use CCExtractor's forked rsmpeg with FFmpeg 7 # All platforms use ffmpeg7 feature with prebuilt bindings for API consistency From 43bc0199b29e1a9b3f9f1842fc296be637533c72 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 2 Apr 2026 12:22:15 +0530 Subject: [PATCH 33/34] fix clippy warning --- src/rust/src/xds/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rust/src/xds/types.rs b/src/rust/src/xds/types.rs index 4c9f59045..1ee561707 100644 --- a/src/rust/src/xds/types.rs +++ b/src/rust/src/xds/types.rs @@ -26,7 +26,6 @@ use crate::ctorust::FromCType; use crate::libccxr_exports::time::write_back_to_common_timing_ctx; pub use crate::xds::constants::*; use lib_ccxr::time::TimingContext; -use std::os::raw::c_int; #[repr(C)] #[derive(Copy, Clone)] From 717822c9493225630863dd5f8a7389a867a76378 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 2 Apr 2026 12:26:01 +0530 Subject: [PATCH 34/34] run cargo fmt --- src/rust/lib_ccxr/src/util/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/lib_ccxr/src/util/log.rs b/src/rust/lib_ccxr/src/util/log.rs index d853bb46c..8830d39cb 100644 --- a/src/rust/lib_ccxr/src/util/log.rs +++ b/src/rust/lib_ccxr/src/util/log.rs @@ -266,7 +266,7 @@ impl<'a> CCExtractorLogger { /// Check if a specific debug flag is set in the current debug mask pub fn has_debug_flag(&self, flag: DebugMessageFlag) -> bool { - self.debug_mask.mask().intersects(flag) + self.debug_mask.mask().intersects(flag) } /// Returns the currently set target for logging messages.