From e7e30747456358642014d0801311ca6b50ff7ab1 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 12:56:40 +0530 Subject: [PATCH 01/12] 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 b38519ad3..d8d5d3bc6 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 dcfde8661..48433c9c0 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 9f267c7b3..9b9919c2c 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 9cd688bdc0eda102dbe064b9a6246850e5cb6c9f 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/12] 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 cb9f397054aebbe7a60fb63342077be6fb368c81 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/12] 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 82e65f4378d654997cfd95bf1888a884e6118685 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 20:32:46 +0530 Subject: [PATCH 04/12] 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 cadd3131c5582a277705fd84f24cac2f28fb86ef Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Wed, 24 Dec 2025 20:45:03 +0530 Subject: [PATCH 05/12] 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 c7100329bebfdc5294793f84bf727b265905dbca Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 11:43:17 +0530 Subject: [PATCH 06/12] 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 a91553fb1..96772b27a 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -178,6 +178,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 1869f2740..a30a42534 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 0c573969961f081349b82767a726c040bbfbfa8e Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 12:13:17 +0530 Subject: [PATCH 07/12] xds module : fix clippy warnings --- src/rust/src/xds/handlers.rs | 57 +++++++++++++++++------------------- src/rust/src/xds/types.rs | 45 +++++++--------------------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index ad594244d..87b75f8bd 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::(); @@ -126,13 +125,7 @@ pub unsafe fn xdsprint( } 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 +160,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 +594,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 +800,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 +831,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 +844,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 +852,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 +1192,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 49d4f2b0d442b1838c1db994cc7fcf1e4ab0aaf3 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 12:36:25 +0530 Subject: [PATCH 08/12] xds module : fix clippy warnings --- src/rust/src/xds/handlers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rust/src/xds/handlers.rs b/src/rust/src/xds/handlers.rs index 87b75f8bd..0ad895144 100644 --- a/src/rust/src/xds/handlers.rs +++ b/src/rust/src/xds/handlers.rs @@ -124,7 +124,6 @@ pub unsafe fn xdsprint( return Ok(()); } - let len = message.len(); write_xds_string(sub, ctx, message, TS_START_OF_XDS.load(Ordering::SeqCst)) } From e53f88f659df5fc70d5a026adf89ccfa1086aa33 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 17:36:22 +0530 Subject: [PATCH 09/12] xds module : running regression tests again with C variants in charge --- src/lib_ccx/ccx_decoders_xds.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 8cc5fefe8..7194f48cb 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -250,10 +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 +// #ifndef DISABLE_RUST +// ccxr_process_xds_bytes(ctx, hi, lo); // use the rust implementation +// return; +// #endif int is_new; if (!ctx) return; @@ -906,10 +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 +// #ifndef DISABLE_RUST +// ccxr_do_end_of_xds(sub, ctx, expected_checksum); // use the rust implementation +// return; +// #endif int cs = 0; int i; From a1d587ff3c20d476a1cc33778954ee85c1f6f014 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Thu, 25 Dec 2025 18:23:08 +0530 Subject: [PATCH 10/12] xds module : running regression tests again with Rust variants in charge --- src/lib_ccx/ccx_decoders_xds.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 7194f48cb..8cc5fefe8 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -250,10 +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 +#ifndef DISABLE_RUST + ccxr_process_xds_bytes(ctx, hi, lo); // use the rust implementation + return; +#endif int is_new; if (!ctx) return; @@ -906,10 +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 +#ifndef DISABLE_RUST + ccxr_do_end_of_xds(sub, ctx, expected_checksum); // use the rust implementation + return; +#endif int cs = 0; int i; From d95708f3ba28961015406e3d54a644d8108c3230 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Fri, 26 Dec 2025 06:52:49 +0530 Subject: [PATCH 11/12] xds module : running regression tests again after applying fix 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 715ca28d00e43b852e1ef68340667a6952918446 Mon Sep 17 00:00:00 2001 From: vatsalkeshav <=> Date: Sun, 28 Dec 2025 19:56:11 +0530 Subject: [PATCH 12/12] 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); +}