From 4618772378f1783206bb929fb892e02905d3327c Mon Sep 17 00:00:00 2001 From: vats004 Date: Wed, 26 Mar 2025 16:39:18 +0530 Subject: [PATCH 1/2] feat : added module decoder_xds in lib_ccxr --- src/lib_ccx/ccx_decoders_xds.c | 138 ++ src/rust/lib_ccxr/src/common/options.rs | 1 + .../lib_ccxr/src/decoder_xds/functions_xds.rs | 1666 +++++++++++++++++ src/rust/lib_ccxr/src/decoder_xds/mod.rs | 2 + .../lib_ccxr/src/decoder_xds/structs_xds.rs | 433 +++++ src/rust/lib_ccxr/src/lib.rs | 1 + src/rust/lib_ccxr/src/time/timing.rs | 4 +- src/rust/lib_ccxr/src/time/units.rs | 3 + src/rust/src/libccxr_exports/mod.rs | 1 + src/rust/src/libccxr_exports/xds_exports.rs | 127 ++ src/rust/wrapper.h | 2 +- 11 files changed, 2376 insertions(+), 2 deletions(-) create mode 100644 src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs create mode 100644 src/rust/lib_ccxr/src/decoder_xds/mod.rs create mode 100644 src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs create mode 100644 src/rust/src/libccxr_exports/xds_exports.rs diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 14a6b23d0..682f57347 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -4,6 +4,78 @@ #include "ccx_common_common.h" #include "utility.h" +#ifndef DISABLE_RUST + +extern struct ccx_decoders_xds_context *ccxr_ccx_decoders_xds_init_library( + struct ccx_common_timing_ctx timing, + int xds_write_to_file); + +extern int ccxr_write_xds_string( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + const char *p, + size_t len); + +extern void ccxr_xdsprint( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + const char *_fmt, + va_list args); + +extern void ccxr_clear_xds_buffer( + struct ccx_decoders_xds_context *ctx, + int64_t num); + +extern int64_t ccxr_how_many_used( + const struct ccx_decoders_xds_context *ctx); + +extern void ccxr_process_xds_bytes( + struct ccx_decoders_xds_context *ctx, + uint8_t hi, + int64_t lo); + +extern void ccxr_xds_do_copy_generation_management_system( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + uint8_t c1, + uint8_t c2); + +extern void ccxr_xds_do_content_advisory( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + uint8_t c1, + uint8_t c2); + +extern int64_t ccxr_xds_do_private_data( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx); + +extern int64_t ccxr_xds_do_misc( + const struct ccx_decoders_xds_context *ctx); + +extern int64_t ccxr_xds_do_current_and_future( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx); + +extern void ccxr_do_end_of_xds( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx, + int64_t expected_checksum); + +extern int64_t ccxr_xds_do_channel( + struct cc_subtitle *sub, + struct ccx_decoders_xds_context *ctx); + +extern void ccxr_xds_debug_test( + struct ccx_decoders_xds_context *ctx, + struct cc_subtitle *sub); + +extern void ccxr_xds_cea608_test( + struct ccx_decoders_xds_context *ctx, + struct cc_subtitle *sub); + +#endif + LLONG ts_start_of_xds = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet static const char *XDSclasses[] = @@ -80,6 +152,9 @@ static const char *XDSProgramTypes[] = struct ccx_decoders_xds_context *ccx_decoders_xds_init_library(struct ccx_common_timing_ctx *timing, int xds_write_to_file) { +#ifndef DISABLE_RUST + return ccxr_ccx_decoders_xds_init_library(*timing, xds_write_to_file); +#else int i; struct ccx_decoders_xds_context *ctx = NULL; @@ -121,10 +196,14 @@ struct ccx_decoders_xds_context *ccx_decoders_xds_init_library(struct ccx_common ctx->xds_write_to_file = xds_write_to_file; return ctx; +#endif } int write_xds_string(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, char *p, size_t len) { +#ifndef DISABLE_RUST + return ccxr_write_xds_string(sub, ctx, p, len); +#else struct eia608_screen *data = NULL; data = (struct eia608_screen *)realloc(sub->data, (sub->nb_data + 1) * sizeof(*data)); if (!data) @@ -151,10 +230,18 @@ int write_xds_string(struct cc_subtitle *sub, struct ccx_decoders_xds_context *c } return 0; +#endif } void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, const char *fmt, ...) { +#ifndef DISABLE_RUST + va_list ap; + va_start(ap, fmt); + ccxr_xdsprint(sub, ctx, fmt, ap); + va_end(ap); +#else + if (!ctx->xds_write_to_file) return; /* Guess we need no more than 100 bytes. */ @@ -192,17 +279,25 @@ void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, con p = np; } } +#endif } void xds_debug_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + ccxr_xds_debug_test(ctx, sub); +#else process_xds_bytes(ctx, 0x05, 0x02); process_xds_bytes(ctx, 0x20, 0x20); do_end_of_xds(sub, ctx, 0x2a); +#endif } void xds_cea608_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + ccxr_xds_cea608_test(ctx, sub); +#else /* This test is the sample data that comes in CEA-608. It sets the program name to be "Star Trek". The checksum is 0x1d and the validation must succeed. */ process_xds_bytes(ctx, 0x01, 0x03); @@ -214,28 +309,40 @@ void xds_cea608_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *s process_xds_bytes(ctx, 0x02, 0x03); process_xds_bytes(ctx, 0x6b, 0x00); do_end_of_xds(sub, ctx, 0x1d); +#endif } int how_many_used(struct ccx_decoders_xds_context *ctx) { +#ifndef DISABLE_RUST + ccxr_how_many_used(ctx); +#else int c = 0; for (int i = 0; i < NUM_XDS_BUFFERS; i++) if (ctx->xds_buffers[i].in_use) c++; return c; +#endif } void clear_xds_buffer(struct ccx_decoders_xds_context *ctx, int num) { +#ifndef DISABLE_RUST + ccxr_clear_xds_buffer(ctx, num); +#else ctx->xds_buffers[num].in_use = 0; ctx->xds_buffers[num].xds_class = -1; ctx->xds_buffers[num].xds_type = -1; ctx->xds_buffers[num].used_bytes = 0; memset(ctx->xds_buffers[num].bytes, 0, NUM_BYTES_PER_PACKET); +#endif } 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); +#else int is_new; if (!ctx) return; @@ -305,12 +412,17 @@ void process_xds_bytes(struct ccx_decoders_xds_context *ctx, const unsigned char ctx->xds_buffers[ctx->cur_xds_buffer_idx].bytes[ctx->xds_buffers[ctx->cur_xds_buffer_idx].used_bytes++] = lo; ctx->xds_buffers[ctx->cur_xds_buffer_idx].bytes[ctx->xds_buffers[ctx->cur_xds_buffer_idx].used_bytes] = 0; } +#endif } + /** * ctx XDS context can be NULL, if user don't want to write xds in transcript */ void xds_do_copy_generation_management_system(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned c1, unsigned c2) { +#ifndef DISABLE_RUST + ccxr_xds_do_copy_generation_management_system(sub, ctx, c1, c2); +#else static unsigned last_c1 = -1, last_c2 = -1; static char copy_permited[256]; static char aps[256]; @@ -364,10 +476,14 @@ void xds_do_copy_generation_management_system(struct cc_subtitle *sub, struct cc ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", copy_permited); ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", aps); ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", rcd); +#endif } void xds_do_content_advisory(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned c1, unsigned c2) { +#ifndef DISABLE_RUST + ccxr_xds_do_content_advisory(sub, ctx, c1, c2); +#else static unsigned last_c1 = -1, last_c2 = -1; static char age[256]; static char content[256]; @@ -477,10 +593,14 @@ void xds_do_content_advisory(struct cc_subtitle *sub, struct ccx_decoders_xds_co if (changed && !supported) ccx_common_logging.log_ftn("XDS: Unsupported ContentAdvisory encoding, please submit sample.\n"); +#endif } int xds_do_current_and_future(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { +#ifndef DISABLE_RUST + ccxr_xds_do_current_and_future(sub, ctx); +#else int was_proc = 0; char *str = malloc(1024); @@ -727,10 +847,14 @@ int xds_do_current_and_future(struct cc_subtitle *sub, struct ccx_decoders_xds_c free(str); return was_proc; +#endif } int xds_do_channel(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { +#ifndef DISABLE_RUST + ccxr_xds_do_channel(sub, ctx); +#else int was_proc = 0; if (!ctx) return CCX_EINVAL; @@ -790,10 +914,14 @@ int xds_do_channel(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx break; } return was_proc; +#endif } int xds_do_private_data(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { +#ifndef DISABLE_RUST + ccxr_xds_do_private_data(sub, ctx); +#else char *str; int i; @@ -810,10 +938,14 @@ int xds_do_private_data(struct cc_subtitle *sub, struct ccx_decoders_xds_context xdsprint(sub, ctx, str); free(str); return 1; +#endif } int xds_do_misc(struct ccx_decoders_xds_context *ctx) { +#ifndef DISABLE_RUST + ccxr_xds_do_misc(ctx); +#else int was_proc = 0; if (!ctx) return CCX_EINVAL; @@ -853,10 +985,15 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) break; } return was_proc; +#endif } 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); +#else + int cs = 0; int i; @@ -935,4 +1072,5 @@ void do_end_of_xds(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx dump(CCX_DMT_DECODER_XDS, ctx->cur_xds_payload, ctx->cur_xds_payload_length, 0, 0); } clear_xds_buffer(ctx, ctx->cur_xds_buffer_idx); +#endif } diff --git a/src/rust/lib_ccxr/src/common/options.rs b/src/rust/lib_ccxr/src/common/options.rs index 489605c44..ee0a108bd 100644 --- a/src/rust/lib_ccxr/src/common/options.rs +++ b/src/rust/lib_ccxr/src/common/options.rs @@ -38,6 +38,7 @@ impl Default for EncodersTranscriptFormat { } } +#[repr(C)] #[derive(Debug, Default, Copy, Clone, PartialEq)] pub enum FrameType { #[default] diff --git a/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs b/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs new file mode 100644 index 000000000..7e023e809 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs @@ -0,0 +1,1666 @@ +use crate::decoder_xds::structs_xds::*; + +use crate::time::c_functions::*; +use crate::time::timing::*; + +use crate::util::log::*; + +use std::alloc::{realloc, Layout}; +use std::ffi::CString; +use std::fmt::Write; +use std::ptr; + +/// Rust equivalent for `ccx_decoders_xds_init_library` function in C. Initializes the XDS decoder context. +/// +/// # Safety +/// +/// The `timing` parameter must be valid and properly initialized. Ensure that all memory allocations succeed. +pub fn ccx_decoders_xds_init_library( + timing: TimingContext, + xds_write_to_file: i64, +) -> CcxDecodersXdsContext { + let mut ctx = CcxDecodersXdsContext { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing, + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file, + }; + + // Initialize xds_buffers + for buffer in ctx.xds_buffers.iter_mut() { + buffer.in_use = 0; + buffer.xds_class = -1; + buffer.xds_type = -1; + buffer.used_bytes = 0; + unsafe { + ptr::write_bytes(buffer.bytes.as_mut_ptr(), 0, NUM_BYTES_PER_PACKET as usize); + } + } + + // Initialize xds_program_description + for description in ctx.xds_program_description.iter_mut() { + unsafe { + ptr::write_bytes(description.as_mut_ptr(), 0, 33); + } + } + + // Initialize other fields + unsafe { + ptr::write_bytes(ctx.current_xds_network_name.as_mut_ptr(), 0, 33); + ptr::write_bytes(ctx.current_xds_program_name.as_mut_ptr(), 0, 33); + ptr::write_bytes(ctx.current_xds_call_letters.as_mut_ptr(), 0, 7); + ptr::write_bytes(ctx.current_xds_program_type.as_mut_ptr(), 0, 33); + } + + ctx +} + +/// Rust equivalent for `write_xds_string` function in C. Writes XDS string data into the subtitle structure. +/// +/// # Safety +/// +/// The `sub` and `ctx` pointers must be valid and non-null. Ensure that memory allocations succeed and that `p` points to valid memory. +pub fn write_xds_string( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + p: *const u8, + len: usize, +) -> i32 { + // Allocate or reallocate memory for `sub.data` + let new_size = (sub.nb_data as usize + 1) * std::mem::size_of::(); + let layout = Layout::array::(sub.nb_data as usize + 1).unwrap(); + + let data = if sub.data.is_null() { + unsafe { std::alloc::alloc(layout) as *mut Eia608Screen } + } else { + unsafe { realloc(sub.data as *mut u8, layout, new_size) as *mut Eia608Screen } + }; + + if data.is_null() { + sub.data = ptr::null_mut(); + sub.nb_data = 0; + info!("No Memory left"); + return -1; + } else { + sub.data = data as *mut std::ffi::c_void; // the as *mut std::ffi::c_void is necessary to convert from *mut Eia608Screen to *mut std::ffi::c_void + // couldn't find a way to do this without the as *mut std::ffi::c_void + sub.datatype = SubDataType::Generic; + unsafe { + let data_ptr = (sub.data as *mut Eia608Screen).add(sub.nb_data as usize); + (*data_ptr).format = CcxEia608Format::SformatXds; + (*data_ptr).end_time = get_fts(&mut ctx.timing, CaptionField::Field2).millis(); // read millis from Timestamp + // (*data_ptr).end_time = get_fts(&ctx.timing, 2); + (*data_ptr).xds_str = p; + (*data_ptr).xds_len = len; + (*data_ptr).cur_xds_packet_class = ctx.cur_xds_packet_class; + } + sub.nb_data += 1; + sub.subtype = SubType::Cc608; + sub.got_output = true; + } + + 0 +} + +/// Rust equivalent for `xdsprint` function in C. Formats and writes XDS data to the subtitle structure. +/// +/// # Safety +/// +/// The `sub` and `ctx` pointers must be valid and non-null. Ensure that memory allocations succeed and that the formatted string is valid. +pub fn xdsprint( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + _fmt: &str, // coz fmt is unused in the current implementation + args: std::fmt::Arguments, +) { + if ctx.xds_write_to_file == 0 { + return; + } + + // Initial buffer size + let mut size: usize = 100; + let mut buffer = String::with_capacity(size); + + loop { + // Try to write the formatted string into the buffer + match write!(&mut buffer, "{}", args) { + Ok(_) => { + // If successful, write the XDS string + let c_string = CString::new(buffer.as_bytes()).unwrap(); + write_xds_string(sub, ctx, c_string.as_ptr() as *const u8, buffer.len()); + return; + } + Err(_) => { + // If the buffer is too small, double its size + size *= 2; + buffer.reserve(size); + } + } + } +} + +/// Rust equivalent for `clear_xds_buffer` function in C. Clears the specified XDS buffer. +/// +/// # Safety +/// +/// The `num` parameter must be within the valid range of the `xds_buffers` array, and the `ctx` pointer must be valid and non-null. +pub fn clear_xds_buffer(ctx: &mut CcxDecodersXdsContext, num: i64) { + ctx.xds_buffers[num as usize].in_use = 0; + ctx.xds_buffers[num as usize].xds_class = -1; + ctx.xds_buffers[num as usize].xds_type = -1; + ctx.xds_buffers[num as usize].used_bytes = 0; + + unsafe { + ptr::write_bytes( + ctx.xds_buffers[num as usize].bytes.as_mut_ptr(), + 0, + NUM_BYTES_PER_PACKET as usize, + ); + } +} + +/// Rust equivalent for `how_many_used` function in C. Counts the number of used XDS buffers. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. +pub fn how_many_used(ctx: &CcxDecodersXdsContext) -> i64 { + let mut count: i64 = 0; + for buffer in ctx.xds_buffers.iter() { + if buffer.in_use != 0 { + count += 1; + } + } + count +} + +/// Rust equivalent for `process_xds_bytes` function in C. Processes XDS bytes and updates the context. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `hi` and `lo` values are within valid ranges. +pub fn process_xds_bytes(ctx: &mut CcxDecodersXdsContext, hi: u8, lo: i64) { + if (0x01..=0x0f).contains(&hi) { + let xds_class = ((hi - 1) / 2) as i64; // 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 + info!( + "XDS Start: {}.{} Is new: {} | Class: {} ({:?}), Used buffers: {}", + hi, + lo, + is_new, + xds_class, + XDS_CLASSES[xds_class as usize], + how_many_used(ctx) + ); + + let mut first_free_buf: i64 = -1; + let mut matching_buf: i64 = -1; + + for i in 0..NUM_XDS_BUFFERS { + let buffer = &ctx.xds_buffers[i as usize]; + if buffer.in_use != 0 && buffer.xds_class == xds_class && buffer.xds_type == lo { + matching_buf = i; + break; + } + if first_free_buf == -1 && buffer.in_use == 0 { + first_free_buf = i; + } + } + + // Handle the three possibilities + if matching_buf == -1 && first_free_buf == -1 { + info!( + "Note: All XDS buffers full (bug or suicidal stream). Ignoring this one ({}, {}).", + xds_class, lo + ); + ctx.cur_xds_buffer_idx = -1; + return; + } + + ctx.cur_xds_buffer_idx = if matching_buf != -1 { + matching_buf + } else { + first_free_buf + }; + + let cur_idx = ctx.cur_xds_buffer_idx as usize; + let cur_buffer = &mut ctx.xds_buffers[cur_idx]; + + if is_new || cur_buffer.in_use == 0 { + // Discard previous data + cur_buffer.xds_class = xds_class; + cur_buffer.xds_type = lo; + cur_buffer.used_bytes = 0; + cur_buffer.in_use = 1; + + unsafe { + ptr::write_bytes( + cur_buffer.bytes.as_mut_ptr(), + 0, + NUM_BYTES_PER_PACKET as usize, + ); + } + } + + if !is_new { + // Continue codes aren't added to the packet + return; + } + } else { + // Informational: 00, or 0x20-0x7F, so 01-0x1f forbidden + info!( + "XDS: {:02X}.{:02X} ({}, {})", + hi, lo, hi as char, lo as u8 as char + ); + if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { + info!("Note: Illegal XDS data"); + return; + } + } + + let cur_idx = ctx.cur_xds_buffer_idx as usize; + let cur_buffer = &mut ctx.xds_buffers[cur_idx]; + + if cur_buffer.used_bytes <= 32 { + // Should always happen + cur_buffer.bytes[cur_buffer.used_bytes as usize] = hi; + cur_buffer.used_bytes += 1; + cur_buffer.bytes[cur_buffer.used_bytes as usize] = lo as u8; + cur_buffer.used_bytes += 1; + cur_buffer.bytes[cur_buffer.used_bytes as usize] = 0; + } +} + +/// Rust equivalent for `xds_do_copy_generation_management_system` function in C. Processes CGMS (Copy Generation Management System) data. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `c1` and `c2` values are within valid ranges. +pub fn xds_do_copy_generation_management_system( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u8, + c2: u8, +) { + static mut LAST_C1: i64 = -1; + static mut LAST_C2: i64 = -1; + + let mut copy_permitted = String::new(); + let mut aps = String::new(); + let mut rcd = String::new(); + + let mut changed = false; + + let c1_6 = (c1 & 0x40) >> 6; + 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 c2_6 = (c2 & 0x40) >> 6; + let rcd0 = c2 & 0x01; + + // User doesn't need XDS + if ctx.xds_write_to_file == 0 { + return; + } + + // These must be high. If not, not following specs + if c1_6 == 0 || c2_6 == 0 { + return; + } + + unsafe { + if LAST_C1 != c1 as i64 || LAST_C2 != c2 as i64 { + changed = true; + LAST_C1 = c1 as i64; + LAST_C2 = c2 as i64; + + // Changed since last time, decode + let copytext = [ + "Copy permitted (no restrictions)", + "No more copies (one generation copy has been made)", + "One generation of copies can be made", + "No copying is permitted", + ]; + let apstext = [ + "No APS", + "PSP On; Split Burst Off", + "PSP On; 2 line Split Burst On", + "PSP On; 4 line Split Burst On", + ]; + + copy_permitted = format!("CGMS: {}", copytext[(cgms_a_b4 * 2 + cgms_a_b3) as usize]); + aps = format!("APS: {}", apstext[(aps_b2 * 2 + aps_b1) as usize]); + rcd = format!("Redistribution Control Descriptor: {}", rcd0); + } + } + + xdsprint(sub, ctx, ©_permitted, format_args!("")); + xdsprint(sub, ctx, &aps, format_args!("")); + xdsprint(sub, ctx, &rcd, format_args!("")); + + if changed { + info!("XDS: {}", copy_permitted); + info!("XDS: {}", aps); + info!("XDS: {}", rcd); + } + + info!("XDS: {}", copy_permitted); + info!("XDS: {}", aps); + info!("XDS: {}", rcd); +} + +/// Rust equivalent for `xds_do_content_advisory` function in C. Processes content advisory data. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory. +pub fn xds_do_content_advisory( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u8, + c2: u8, +) { + static mut LAST_C1: i64 = -1; + static mut LAST_C2: i64 = -1; + + let mut age = String::new(); + let mut content = String::new(); + let mut rating = String::new(); + let mut changed = false; + + 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; + 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; + let mut supported = false; + + // User doesn't need XDS + if ctx.xds_write_to_file == 0 { + return; + } + + // These must be high. If not, not following specs + if c1_6 == 0 || c2_6 == 0 { + return; + } + + unsafe { + if LAST_C1 != c1 as i64 || LAST_C2 != c2 as i64 { + changed = true; + LAST_C1 = c1 as i64; + LAST_C2 = c2 as i64; + + // Changed since last time, decode + if a1 == 0 && a0 == 1 { + // US TV parental guidelines + let agetext = [ + "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", + ]; + age = format!( + "ContentAdvisory: US TV Parental Guidelines. Age Rating: {}", + agetext[(g2 * 4 + g1 * 2 + g0) as usize] + ); + content.clear(); + if g2 == 0 && g1 == 1 && g0 == 0 { + // For TV-Y7 (Older children), the Violence bit is "fantasy violence" + if fv != 0 { + content.push_str("[Fantasy Violence] "); + } + } else { + // For all others, is real + if fv != 0 { + content.push_str("[Violence] "); + } + } + if s != 0 { + content.push_str("[Sexual Situations] "); + } + if la3 != 0 { + content.push_str("[Adult Language] "); + } + if da2 != 0 { + content.push_str("[Sexually Suggestive Dialog] "); + } + supported = true; + } + if a0 == 0 { + // MPA + let ratingtext = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; + rating = format!( + "ContentAdvisory: MPA Rating: {}", + ratingtext[(r2 * 4 + r1 * 2 + r0) as usize] + ); + supported = true; + } + if a0 == 1 && a1 == 1 && da2 == 0 && la3 == 0 { + // Canadian English Language Rating + let ratingtext = [ + "Exempt", + "Children", + "Children eight years and older", + "General programming suitable for all audiences", + "Parental Guidance", + "Viewers 14 years and older", + "Adult Programming", + "[undefined]", + ]; + rating = format!( + "ContentAdvisory: Canadian English Rating: {}", + ratingtext[(g2 * 4 + g1 * 2 + g0) as usize] + ); + supported = true; + } + if a0 == 1 && a1 == 1 && da2 == 1 && la3 == 0 { + // Canadian French Language Rating + let ratingtext = [ + "Exemptes", + "General", + "General - Not recommended for young children", + "This program may not be suitable for children under 13 years of age", + "This program is not suitable for children under 16 years of age", + "This program is reserved for adults", + "[invalid]", + "[invalid]", + ]; + rating = format!( + "ContentAdvisory: Canadian French Rating: {}", + ratingtext[(g2 * 4 + g1 * 2 + g0) as usize] + ); + supported = true; + } + } + } + + if a1 == 0 && a0 == 1 { + // US TV parental guidelines + xdsprint(sub, ctx, &age, format_args!("")); + xdsprint(sub, ctx, &content, format_args!("")); + if changed { + info!("XDS: {}", age); + info!("XDS: {}", content); + } + } + if a0 == 0 + || (a0 == 1 && a1 == 1 && da2 == 0 && la3 == 0) + || (a0 == 1 && a1 == 1 && da2 == 1 && la3 == 0) + { + // MPA or Canadian ratings + xdsprint(sub, ctx, &rating, format_args!("")); + if changed { + info!("XDS: {}", rating); + } + } + + if changed && !supported { + info!("XDS: Unsupported ContentAdvisory encoding, please submit sample."); + } +} + +/// Rust equivalent for `xds_do_private_data` function in C. Processes private data in the XDS context. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. +pub fn xds_do_private_data(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { + if ctx.xds_write_to_file == 0 { + return CCX_EINVAL; + } + + // Allocate a string to hold the formatted private data + let mut str = String::with_capacity((ctx.cur_xds_payload_length * 3) as usize + 1); + + // Only thing we can do with private data is dump it + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + write!(&mut str, "{:02X} ", byte).unwrap(); + } + + // Print the private data + xdsprint(sub, ctx, &str, format_args!("")); + + 1 +} + +/// Rust equivalent for `xds_do_misc` function in C. Processes miscellaneous XDS data. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. +pub fn xds_do_misc(ctx: &CcxDecodersXdsContext) -> i64 { + let was_proc: i64; + + if ctx.cur_xds_payload.is_null() { + return CCX_EINVAL; + } + + match ctx.cur_xds_packet_type { + x if x as u8 == XDS_TYPE_TIME_OF_DAY => { + was_proc = 1; + if ctx.cur_xds_payload_length < 9 { + // We need at least 6 data bytes + return was_proc; + } + + let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; // 6 bits + let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; // 5 bits + let date = unsafe { *ctx.cur_xds_payload.add(4) & 0x1f }; // 5 bits + let month = unsafe { *ctx.cur_xds_payload.add(5) & 0x0f }; // 4 bits + let reset_seconds = unsafe { (*ctx.cur_xds_payload.add(5) & 0x20) != 0 }; // Reset seconds + let day_of_week = unsafe { *ctx.cur_xds_payload.add(6) & 0x07 }; // 3 bits + let year = unsafe { (*ctx.cur_xds_payload.add(7) & 0x3f) as i64 + 1990 }; // Year offset + + info!( + "Time of day: (YYYY/MM/DD) {}/{:02}/{:02} (HH:MM) {:02}:{:02} DoW: {} Reset seconds: {}", + year, month, date, hour, min, day_of_week, reset_seconds + ); + } + x if x as u8 == XDS_TYPE_LOCAL_TIME_ZONE => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + // We need at least 2 data bytes + return was_proc; + } + + let dst = unsafe { (*ctx.cur_xds_payload.add(2) & 0x20) >> 5 }; // Daylight Saving Time + let hour = unsafe { *ctx.cur_xds_payload.add(2) & 0x1f }; // 5 bits + + info!("Local Time Zone: {:02} DST: {}", hour, dst); + } + _ => { + was_proc = 0; + } + } + + was_proc +} + +/// Rust equivalent for `xds_do_current_and_future` function in C. Processes XDS data for current and future programs. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. +pub fn xds_do_current_and_future(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { + let mut was_proc: i64 = 0; + + if ctx.cur_xds_payload.is_null() { + return CCX_EINVAL; + } + + match ctx.cur_xds_packet_type as u8 { + XDS_TYPE_PIN_START_TIME => { + was_proc = 1; + if ctx.cur_xds_payload_length < 7 { + return was_proc; + } + + let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; + let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; + let date = unsafe { *ctx.cur_xds_payload.add(4) & 0x1f }; + let month = unsafe { *ctx.cur_xds_payload.add(5) & 0x0f }; + + if ctx.current_xds_min != min as i64 + || ctx.current_xds_hour != hour as i64 + || ctx.current_xds_date != date as i64 + || ctx.current_xds_month != month as i64 + { + ctx.xds_start_time_shown = 0; + ctx.current_xds_min = min as i64; + ctx.current_xds_hour = hour as i64; + ctx.current_xds_date = date as i64; + ctx.current_xds_month = month as i64; + } + + info!( + "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}", + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 { + "Current" + } else { + "Future" + }, + date, + month, + hour, + min + ); + + xdsprint( + sub, + ctx, + &format!( + "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}", + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 { + "Current" + } else { + "Future" + }, + date, + month, + hour, + min + ), + format_args!(""), + ); + + if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 + { + info!("XDS: Program changed."); + info!( + "XDS program start time (DD/MM HH:MM) {:02}-{:02} {:02}:{:02}", + date, month, hour, min + ); + ctx.xds_start_time_shown = 1; + } + } + XDS_TYPE_LENGTH_AND_CURRENT_TIME => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + return was_proc; + } + + let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; + let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; + + info!("XDS: Program length (HH:MM): {:02}:{:02}", hour, min); + xdsprint( + sub, + ctx, + &format!("Program length (HH:MM): {:02}:{:02}", hour, min), + format_args!(""), + ); + + if ctx.cur_xds_payload_length > 6 { + let el_min = unsafe { *ctx.cur_xds_payload.add(4) & 0x3f }; + let el_hour = unsafe { *ctx.cur_xds_payload.add(5) & 0x1f }; + info!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min); + xdsprint( + sub, + ctx, + &format!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min), + format_args!(""), + ); + } + + if ctx.cur_xds_payload_length > 8 { + let el_sec = unsafe { *ctx.cur_xds_payload.add(6) & 0x3f }; + info!("Elapsed (SS): {:02}", el_sec); + xdsprint( + sub, + ctx, + &format!("Elapsed (SS): {:02}", el_sec), + format_args!(""), + ); + } + } + XDS_TYPE_PROGRAM_NAME => { + was_proc = 1; + let mut xds_program_name = String::new(); + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + xds_program_name.push(byte as char); + } + + info!("XDS Program name: {}", xds_program_name); + xdsprint( + sub, + ctx, + &format!("Program name: {}", xds_program_name), + format_args!(""), + ); + + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 + && xds_program_name != String::from_utf8_lossy(&ctx.current_xds_program_name) + { + info!("XDS Notice: Program is now {}", xds_program_name); + unsafe { + ptr::write_bytes( + ctx.current_xds_program_name.as_mut_ptr(), + 0, + ctx.current_xds_program_name.len(), + ); + } + ctx.current_xds_program_name[..xds_program_name.len()] + .copy_from_slice(xds_program_name.as_bytes()); + } + } + XDS_TYPE_PROGRAM_TYPE => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + return was_proc; + } + + if ctx.current_program_type_reported != 0 { + for i in 0..ctx.cur_xds_payload_length as usize { + if unsafe { *ctx.cur_xds_payload.add(i) } != ctx.current_xds_program_type[i] { + ctx.current_program_type_reported = 0; + break; + } + } + } + + unsafe { + ptr::write_bytes( + ctx.current_xds_program_type.as_mut_ptr(), + 0, + ctx.current_xds_program_type.len(), + ); + } + for i in 0..ctx.cur_xds_payload_length as usize { + ctx.current_xds_program_type[i] = unsafe { *ctx.cur_xds_payload.add(i) }; + } + + let mut program_type = String::new(); + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + if (0x20..0x7f).contains(&byte) { + program_type.push_str(XDS_PROGRAM_TYPES[(byte - 0x20) as usize]); + } + } + + info!("XDS Program Type: {}", program_type); + xdsprint( + sub, + ctx, + &format!("Program type {}", program_type), + format_args!(""), + ); + + ctx.current_program_type_reported = 1; + } + XDS_TYPE_CONTENT_ADVISORY => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + return was_proc; + } + xds_do_content_advisory(sub, ctx, unsafe { *ctx.cur_xds_payload.add(2) }, unsafe { + *ctx.cur_xds_payload.add(3) + }); + } + XDS_TYPE_CGMS => { + was_proc = 1; + xds_do_copy_generation_management_system( + sub, + ctx, + unsafe { *ctx.cur_xds_payload.add(2) }, + unsafe { *ctx.cur_xds_payload.add(3) }, + ); + } + XDS_TYPE_ASPECT_RATIO_INFO => { + was_proc = 1; + if ctx.cur_xds_payload_length < 5 { + return was_proc; + } + + let ar_start = (unsafe { *ctx.cur_xds_payload.add(2) } & 0x1f) as i64 + 22; + let ar_end = 262 - (unsafe { *ctx.cur_xds_payload.add(3) } & 0x1f) as i64; + let active_picture_height = ar_end - ar_start; + let aspect_ratio = 320.0 / active_picture_height as f64; + + if ar_start != ctx.current_ar_start { + ctx.current_ar_start = ar_start; + ctx.current_ar_end = ar_end; + info!( + "XDS Notice: Aspect ratio info, start line={}, end line={}", + ar_start, ar_end + ); + info!( + "XDS Notice: Aspect ratio info, active picture height={}, ratio={}", + active_picture_height, aspect_ratio + ); + } + } + XDS_TYPE_PROGRAM_DESC_1..=XDS_TYPE_PROGRAM_DESC_8 => { + was_proc = 1; + let mut xds_desc = String::new(); + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + xds_desc.push(byte as char); + } + + if !xds_desc.is_empty() { + let line_num = ctx.cur_xds_packet_type as usize - XDS_TYPE_PROGRAM_DESC_1 as usize; + if xds_desc != String::from_utf8_lossy(&ctx.xds_program_description[line_num]) { + info!("XDS description line {}: {}", line_num, xds_desc); + unsafe { + ptr::write_bytes( + ctx.xds_program_description[line_num].as_mut_ptr(), + 0, + ctx.xds_program_description[line_num].len(), + ); + } + ctx.xds_program_description[line_num][..xds_desc.len()] + .copy_from_slice(xds_desc.as_bytes()); + } + xdsprint( + sub, + ctx, + &format!("XDS description line {}: {}", line_num, xds_desc), + format_args!(""), + ); + } + } + _ => {} + } + + was_proc +} + +/// Rust equivalent for `do_end_of_xds` function in C. Processes the end of an XDS packet and handles its contents. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. +pub fn do_end_of_xds( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + expected_checksum: i64, +) { + let mut cs: i64 = 0; + + if ctx.cur_xds_buffer_idx == -1 || ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].in_use == 0 + { + return; + } + + ctx.cur_xds_packet_class = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].xds_class; + ctx.cur_xds_payload = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize] + .bytes + .as_mut_ptr(); + ctx.cur_xds_payload_length = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].used_bytes; + ctx.cur_xds_packet_type = unsafe { *ctx.cur_xds_payload.add(1) as i64 }; + unsafe { + *ctx.cur_xds_payload.add(ctx.cur_xds_payload_length as usize) = 0x0F; + } + ctx.cur_xds_payload_length += 1; + + for i in 0..ctx.cur_xds_payload_length as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + cs = (cs + byte as i64) & 0x7F; + let c = byte & 0x7F; + info!( + "{:02X} - {} cs: {:02X}", + c, + if c >= 0x20 { c as char } else { '?' }, + cs + ); + } + cs = (128 - cs) & 0x7F; + + info!( + "End of XDS. Class={} ({}), size={} Checksum OK: {} Used buffers: {}", + ctx.cur_xds_packet_class, + XDS_CLASSES[ctx.cur_xds_packet_class as usize], + ctx.cur_xds_payload_length, + cs == expected_checksum, + how_many_used(ctx) + ); + + if cs != expected_checksum || ctx.cur_xds_payload_length < 3 { + info!( + "Expected checksum: {:02X} Calculated: {:02X}", + expected_checksum, cs + ); + clear_xds_buffer(ctx, ctx.cur_xds_buffer_idx); + return; + } + + let mut was_proc: i64 = 0; + if ctx.cur_xds_packet_type & 0x40 != 0 { + ctx.cur_xds_packet_class = XDS_CLASS_OUT_OF_BAND as i64; + } + + match ctx.cur_xds_packet_class { + x if x == XDS_CLASS_FUTURE as i64 => { + was_proc = 1; + } + x if x == XDS_CLASS_CURRENT as i64 => { + was_proc = xds_do_current_and_future(sub, ctx); + } + x if x == XDS_CLASS_CHANNEL as i64 => { + was_proc = xds_do_channel(sub, ctx); + } + x if x == XDS_CLASS_MISC as i64 => { + was_proc = xds_do_misc(ctx); + } + x if x == XDS_CLASS_PRIVATE as i64 => { + was_proc = xds_do_private_data(sub, ctx); + } + x if x == XDS_CLASS_OUT_OF_BAND as i64 => { + info!("Out-of-band data, ignored."); + was_proc = 1; + } + _ => {} + } + + if was_proc == 0 { + info!("Note: We found a currently unsupported XDS packet."); + hex_dump_with_start_idx( + DebugMessageFlag::DECODER_XDS, + unsafe { + std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize) + }, + false, + 0, + ); + } + + clear_xds_buffer(ctx, ctx.cur_xds_buffer_idx); +} + +/// Rust equivalent for `xds_do_channel` function in C. Processes XDS data for channel-related information. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. +pub fn xds_do_channel(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { + let mut was_proc: i64 = 0; + + if ctx.cur_xds_payload.is_null() { + return CCX_EINVAL; + } + + match ctx.cur_xds_packet_type as u8 { + XDS_TYPE_NETWORK_NAME => { + was_proc = 1; + let mut xds_network_name = String::new(); + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + xds_network_name.push(byte as char); + } + + info!("XDS Network name: {}", xds_network_name); + xdsprint( + sub, + ctx, + &format!("Network: {}", xds_network_name), + format_args!(""), + ); + + if xds_network_name != String::from_utf8_lossy(&ctx.current_xds_network_name) { + info!("XDS Notice: Network is now {}", xds_network_name); + unsafe { + ptr::write_bytes( + ctx.current_xds_network_name.as_mut_ptr(), + 0, + ctx.current_xds_network_name.len(), + ); + } + ctx.current_xds_network_name[..xds_network_name.len()] + .copy_from_slice(xds_network_name.as_bytes()); + } + } + XDS_TYPE_CALL_LETTERS_AND_CHANNEL => { + was_proc = 1; + if ctx.cur_xds_payload_length != 7 && ctx.cur_xds_payload_length != 9 { + return was_proc; + } + + let mut xds_call_letters = String::new(); + for i in 2..(ctx.cur_xds_payload_length - 1) as usize { + let byte = unsafe { *ctx.cur_xds_payload.add(i) }; + xds_call_letters.push(byte as char); + } + + info!("XDS Network call letters: {}", xds_call_letters); + xdsprint( + sub, + ctx, + &format!("Call Letters: {}", xds_call_letters), + format_args!(""), + ); + + if xds_call_letters != String::from_utf8_lossy(&ctx.current_xds_call_letters) { + info!("XDS Notice: Network call letters now {}", xds_call_letters); + unsafe { + ptr::write_bytes( + ctx.current_xds_call_letters.as_mut_ptr(), + 0, + ctx.current_xds_call_letters.len(), + ); + } + ctx.current_xds_call_letters[..xds_call_letters.len()] + .copy_from_slice(xds_call_letters.as_bytes()); + } + } + XDS_TYPE_TSID => { + was_proc = 1; + if ctx.cur_xds_payload_length < 7 { + return was_proc; + } + + let b1 = (unsafe { *ctx.cur_xds_payload.add(2) } & 0x10) as i64; + let b2 = (unsafe { *ctx.cur_xds_payload.add(3) } & 0x10) as i64; + let b3 = (unsafe { *ctx.cur_xds_payload.add(4) } & 0x10) as i64; + let b4 = (unsafe { *ctx.cur_xds_payload.add(5) } & 0x10) as i64; + let tsid = (b4 << 12) | (b3 << 8) | (b2 << 4) | b1; + + if tsid != 0 { + xdsprint(sub, ctx, &format!("TSID: {}", tsid), format_args!("")); + } + } + _ => {} + } + + was_proc +} + +/// Rust equivalent for `xds_debug_test` function in C. Tests the XDS processing logic with sample data. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory +pub fn xds_debug_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + process_xds_bytes(ctx, 0x05, 0x02); + process_xds_bytes(ctx, 0x20, 0x20); + do_end_of_xds(sub, ctx, 0x2a); +} + +/// Rust equivalent for `xds_cea608_test` function in C. Tests the XDS processing logic with CEA-608 sample data. +/// +/// # Safety +/// +/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory. +pub fn xds_cea608_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + // This test is the sample data that comes in CEA-608. It sets the program name + // to be "Star Trek". The checksum is 0x1d and the validation must succeed. + process_xds_bytes(ctx, 0x01, 0x03); + process_xds_bytes(ctx, 0x53, 0x74); + process_xds_bytes(ctx, 0x61, 0x72); + process_xds_bytes(ctx, 0x20, 0x54); + process_xds_bytes(ctx, 0x72, 0x65); + process_xds_bytes(ctx, 0x02, 0x03); + process_xds_bytes(ctx, 0x02, 0x03); + process_xds_bytes(ctx, 0x6b, 0x00); + do_end_of_xds(sub, ctx, 0x1d); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CStr; + + #[test] + fn test_ccx_decoders_xds_init_library_initialization() { + let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context + let xds_write_to_file = 1; + + let ctx = ccx_decoders_xds_init_library(timing, xds_write_to_file); + + // Check initial values + assert_eq!(ctx.current_xds_min, -1); + assert_eq!(ctx.current_xds_hour, -1); + assert_eq!(ctx.current_xds_date, -1); + assert_eq!(ctx.current_xds_month, -1); + assert_eq!(ctx.current_program_type_reported, 0); + assert_eq!(ctx.xds_start_time_shown, 0); + assert_eq!(ctx.xds_program_length_shown, 0); + assert_eq!(ctx.cur_xds_buffer_idx, -1); + assert_eq!(ctx.cur_xds_packet_class, -1); + assert_eq!(ctx.cur_xds_payload, std::ptr::null_mut()); + assert_eq!(ctx.cur_xds_payload_length, 0); + assert_eq!(ctx.cur_xds_packet_type, 0); + assert_eq!(ctx.xds_write_to_file, xds_write_to_file); + + // Check that all buffers are initialized correctly + for buffer in ctx.xds_buffers.iter() { + assert_eq!(buffer.in_use, 0); + assert_eq!(buffer.xds_class, -1); + assert_eq!(buffer.xds_type, -1); + assert_eq!(buffer.used_bytes, 0); + assert!(buffer.bytes.iter().all(|&b| b == 0)); + } + + // Check that all program descriptions are initialized to zero + for description in ctx.xds_program_description.iter() { + assert!(description.iter().all(|&b| b == 0)); + } + + // Check that network name, program name, call letters, and program type are initialized to zero + assert!(ctx.current_xds_network_name.iter().all(|&b| b == 0)); + assert!(ctx.current_xds_program_name.iter().all(|&b| b == 0)); + assert!(ctx.current_xds_call_letters.iter().all(|&b| b == 0)); + assert!(ctx.current_xds_program_type.iter().all(|&b| b == 0)); + } + + #[test] + fn test_ccx_decoders_xds_init_library_with_different_write_flag() { + let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context + let xds_write_to_file = 0; + + let ctx = ccx_decoders_xds_init_library(timing, xds_write_to_file); + + // Check that the `xds_write_to_file` flag is set correctly + assert_eq!(ctx.xds_write_to_file, xds_write_to_file); + } + + #[test] + fn test_ccx_decoders_xds_init_library_timing_context() { + let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context + let xds_write_to_file = 1; + + let ctx = ccx_decoders_xds_init_library(timing.clone(), xds_write_to_file); + + // Check that the timing context is correctly assigned + assert_eq!(ctx.timing, timing); + } + + #[test] + fn test_write_xds_string_success() { + let mut sub = CcSubtitle { + data: std::ptr::null_mut(), + datatype: SubDataType::Generic, + nb_data: 0, + subtype: SubType::Cc608, + enc_type: CcxEncodingType::Utf8, + start_time: 0, + end_time: 0, + flags: 0, + lang_index: 0, + got_output: false, + mode: [0; 5], + info: [0; 4], + time_out: 0, + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + }; + + let mut ctx = CcxDecodersXdsContext { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::new(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 1, + }; + + let xds_string = b"Test XDS String"; + let result = write_xds_string(&mut sub, &mut ctx, xds_string.as_ptr(), xds_string.len()); + + assert_eq!(result, 0); + assert_eq!(sub.nb_data, 1); + assert!(sub.got_output); + + unsafe { + let data_ptr = sub.data as *mut Eia608Screen; + let first_screen = &*data_ptr; + assert_eq!(first_screen.xds_len, xds_string.len()); + assert_eq!( + std::slice::from_raw_parts(first_screen.xds_str, xds_string.len()), + xds_string + ); + } + } + + #[test] + fn test_xdsprint_no_write_flag() { + let mut sub = CcSubtitle { + data: std::ptr::null_mut(), + datatype: SubDataType::Generic, + nb_data: 0, + subtype: SubType::Cc608, + enc_type: CcxEncodingType::Utf8, + start_time: 0, + end_time: 0, + flags: 0, + lang_index: 0, + got_output: false, + mode: [0; 5], + info: [0; 4], + time_out: 0, + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + }; + + let mut ctx = CcxDecodersXdsContext { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::new(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 0, // Writing is disabled + }; + + let message = "This message should not be written"; + xdsprint(&mut sub, &mut ctx, "", format_args!("{}", message)); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_clear_xds_buffer_success() { + let mut ctx = CcxDecodersXdsContext { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 1, + xds_class: 2, + xds_type: 3, + bytes: [0xFF; NUM_BYTES_PER_PACKET as usize], + used_bytes: 10, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::new(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 1, + }; + + let buffer_index = 0; + + // Call the function to clear the buffer + clear_xds_buffer(&mut ctx, buffer_index); + + // Verify that the buffer was cleared + let buffer = &ctx.xds_buffers[buffer_index as usize]; + assert_eq!(buffer.in_use, 0); + assert_eq!(buffer.xds_class, -1); + assert_eq!(buffer.xds_type, -1); + assert_eq!(buffer.used_bytes, 0); + assert!(buffer.bytes.iter().all(|&b| b == 0)); + } + + #[test] + fn test_clear_xds_buffer_partial_clear() { + let mut ctx = CcxDecodersXdsContext { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 1, + xds_class: 2, + xds_type: 3, + bytes: [0xFF; NUM_BYTES_PER_PACKET as usize], + used_bytes: 10, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::new(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 1, + }; + + let buffer_index = 1; + + // Call the function to clear the buffer + clear_xds_buffer(&mut ctx, buffer_index); + + // Verify that only the specified buffer was cleared + let cleared_buffer = &ctx.xds_buffers[buffer_index as usize]; + assert_eq!(cleared_buffer.in_use, 0); + assert_eq!(cleared_buffer.xds_class, -1); + assert_eq!(cleared_buffer.xds_type, -1); + assert_eq!(cleared_buffer.used_bytes, 0); + assert!(cleared_buffer.bytes.iter().all(|&b| b == 0)); + + // Verify that other buffers remain unchanged + let untouched_buffer = &ctx.xds_buffers[0]; + assert_eq!(untouched_buffer.in_use, 1); + assert_eq!(untouched_buffer.xds_class, 2); + assert_eq!(untouched_buffer.xds_type, 3); + assert_eq!(untouched_buffer.used_bytes, 10); + assert!(untouched_buffer.bytes.iter().all(|&b| b == 0xFF)); + } + + #[test] + fn test_how_many_used_all_unused() { + let ctx = CcxDecodersXdsContext { + xds_buffers: [XdsBuffer { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + ..Default::default() + }; + + let used_count = how_many_used(&ctx); + assert_eq!(used_count, 0, "Expected no buffers to be in use"); + } + + #[test] + fn test_how_many_used_some_used() { + let mut ctx = CcxDecodersXdsContext { + xds_buffers: [XdsBuffer { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + ..Default::default() + }; + + // Mark some buffers as in use + ctx.xds_buffers[0].in_use = 1; + ctx.xds_buffers[2].in_use = 1; + ctx.xds_buffers[4].in_use = 1; + + let used_count = how_many_used(&ctx); + assert_eq!(used_count, 3, "Expected 3 buffers to be in use"); + } + + #[test] + fn test_how_many_used_all_used() { + let ctx = CcxDecodersXdsContext { + xds_buffers: [XdsBuffer { + in_use: 1, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + ..Default::default() + }; + + let used_count = how_many_used(&ctx); + assert_eq!( + used_count, NUM_XDS_BUFFERS as i64, + "Expected all buffers to be in use" + ); + } + + #[test] + fn test_xds_do_copy_generation_management_system_no_write_flag() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.xds_write_to_file = 0; // Disable writing XDS data + + let c1 = 0x50; // CGMS: Copy permitted (no restrictions) + let c2 = 0x40; // APS: No APS, RCD: 0 + + xds_do_copy_generation_management_system(&mut sub, &mut ctx, c1, c2); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_do_copy_generation_management_system_invalid_data() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.xds_write_to_file = 1; // Enable writing XDS data + + let c1 = 0x10; // Invalid CGMS data (c1_6 is 0) + let c2 = 0x40; // APS: No APS, RCD: 0 + + xds_do_copy_generation_management_system(&mut sub, &mut ctx, c1, c2); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_do_content_advisory_no_write_flag() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.xds_write_to_file = 0; // Disable writing XDS data + + let c1 = 0x58; // US TV parental guidelines: TV-14 (Parents Strongly Cautioned) + let c2 = 0x10; // Content: Violence + + xds_do_content_advisory(&mut sub, &mut ctx, c1, c2); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_do_content_advisory_invalid_data() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.xds_write_to_file = 1; // Enable writing XDS data + + let c1 = 0x10; // Invalid data (c1_6 is 0) + let c2 = 0x40; // No additional content + + xds_do_content_advisory(&mut sub, &mut ctx, c1, c2); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_do_misc_invalid_payload() { + let mut ctx = CcxDecodersXdsContext::default(); + ctx.cur_xds_packet_type = XDS_TYPE_TIME_OF_DAY as i64; + ctx.cur_xds_payload_length = 4; // Invalid length (less than required) + + // Simulate an invalid payload + let payload: [u8; 4] = [0x00, 0x00, 0x3C, 0x12]; + ctx.cur_xds_payload = payload.as_ptr() as *mut u8; + + let result = xds_do_misc(&ctx); + + // Verify the function did not process the payload + assert_eq!(result, 1); // Still returns 1 but does not process + } + + #[test] + fn test_xds_do_misc_null_payload() { + let mut ctx = CcxDecodersXdsContext::default(); + ctx.cur_xds_packet_type = XDS_TYPE_TIME_OF_DAY as i64; + ctx.cur_xds_payload = std::ptr::null_mut(); // Null payload + + let result = xds_do_misc(&ctx); + + // Verify the function returned an error + assert_eq!(result, CCX_EINVAL); + } + + #[test] + fn test_xds_do_misc_unsupported_packet_type() { + let mut ctx = CcxDecodersXdsContext::default(); + ctx.cur_xds_packet_type = 0x99; // Unsupported packet type + ctx.cur_xds_payload_length = 5; + + // Simulate a valid payload + let payload: [u8; 5] = [0x00, 0x00, 0x25, 0x00, 0x00]; + ctx.cur_xds_payload = payload.as_ptr() as *mut u8; + + let result = xds_do_misc(&ctx); + + // Verify the function did not process the payload + assert_eq!(result, 0); + } + + #[test] + fn test_xds_do_current_and_future_invalid_payload() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.cur_xds_packet_type = XDS_TYPE_PIN_START_TIME as i64; + ctx.cur_xds_payload_length = 4; // Invalid length (less than required) + + // Simulate an invalid payload + let payload: [u8; 4] = [0x00, 0x00, 0x15, 0x10]; + ctx.cur_xds_payload = payload.as_ptr() as *mut u8; + + let result = xds_do_current_and_future(&mut sub, &mut ctx); + + // Verify the function did not process the payload + assert_eq!(result, 1); // Still returns 1 but does not process + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_do_end_of_xds_empty_buffer() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + + // Simulate an empty XDS buffer + ctx.cur_xds_buffer_idx = 0; + ctx.xds_buffers[0].in_use = 0; + + let expected_checksum = 0x1D; // Example checksum + do_end_of_xds(&mut sub, &mut ctx, expected_checksum); + + // Verify that nothing was processed + assert_eq!(ctx.xds_buffers[0].in_use, 0); + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_do_channel_invalid_payload() { + let mut sub = CcSubtitle::default(); + let mut ctx = CcxDecodersXdsContext::default(); + ctx.cur_xds_packet_type = XDS_TYPE_CALL_LETTERS_AND_CHANNEL as i64; + ctx.cur_xds_payload_length = 4; // Invalid length (less than required) + + // Simulate an invalid payload + let payload: [u8; 4] = [0x00, 0x00, b'W', b'A']; + ctx.cur_xds_payload = payload.as_ptr() as *mut u8; + + let result = xds_do_channel(&mut sub, &mut ctx); + + // Verify the function did not process the payload + assert_eq!(result, 1); // Still returns 1 but does not process + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_debug_test_empty_context() { + let mut ctx = CcxDecodersXdsContext::default(); + let mut sub = CcSubtitle::default(); + + // Call the debug test function without any valid data + do_end_of_xds(&mut sub, &mut ctx, 0x2a); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } + + #[test] + fn test_xds_cea608_test_empty_context() { + let mut ctx = CcxDecodersXdsContext::default(); + let mut sub = CcSubtitle::default(); + + // Call the CEA-608 test function without any valid data + do_end_of_xds(&mut sub, &mut ctx, 0x1d); + + // Verify that the subtitle structure was not updated + assert_eq!(sub.nb_data, 0); + assert!(!sub.got_output); + } +} diff --git a/src/rust/lib_ccxr/src/decoder_xds/mod.rs b/src/rust/lib_ccxr/src/decoder_xds/mod.rs new file mode 100644 index 000000000..1d240449a --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_xds/mod.rs @@ -0,0 +1,2 @@ +pub mod functions_xds; +pub mod structs_xds; diff --git a/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs b/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs new file mode 100644 index 000000000..b8aba215f --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs @@ -0,0 +1,433 @@ +use crate::time::timing::*; + +// Time at which we switched to XDS mode, -1 means it hasn't happened yet +pub const TS_START_OF_XDS: i64 = -1; + +// Exit codes +pub const EXIT_OK: i64 = 0; +pub const EXIT_NO_INPUT_FILES: i64 = 2; +pub const EXIT_TOO_MANY_INPUT_FILES: i64 = 3; +pub const EXIT_INCOMPATIBLE_PARAMETERS: i64 = 4; +pub const EXIT_UNABLE_TO_DETERMINE_FILE_SIZE: i64 = 6; +pub const EXIT_MALFORMED_PARAMETER: i64 = 7; +pub const EXIT_READ_ERROR: i64 = 8; +pub const EXIT_NO_CAPTIONS: i64 = 10; +pub const EXIT_WITH_HELP: i64 = 11; +pub const EXIT_NOT_CLASSIFIED: i64 = 300; +pub const EXIT_ERROR_IN_CAPITALIZATION_FILE: i64 = 501; +pub const EXIT_BUFFER_FULL: i64 = 502; +pub const EXIT_MISSING_ASF_HEADER: i64 = 1001; +pub const EXIT_MISSING_RCWT_HEADER: i64 = 1002; + +// Common exit codes +pub const CCX_COMMON_EXIT_FILE_CREATION_FAILED: i64 = 5; +pub const CCX_COMMON_EXIT_UNSUPPORTED: i64 = 9; +pub const EXIT_NOT_ENOUGH_MEMORY: i64 = 500; +pub const CCX_COMMON_EXIT_BUG_BUG: i64 = 1000; + +// Status codes +pub const CCX_OK: i64 = 0; +pub const CCX_FALSE: i64 = 0; +pub const CCX_TRUE: i64 = 1; +pub const CCX_EAGAIN: i64 = -100; +pub const CCX_EOF: i64 = -101; +pub const CCX_EINVAL: i64 = -102; +pub const CCX_ENOSUPP: i64 = -103; +pub const CCX_ENOMEM: i64 = -104; + +// Define max width in characters/columns on the screen +pub const CCX_DECODER_608_SCREEN_ROWS: usize = 15; +pub const CCX_DECODER_608_SCREEN_WIDTH: usize = 32; + +pub const NUM_BYTES_PER_PACKET: i64 = 35; // Class + type (repeated for convenience) + data + zero +pub const NUM_XDS_BUFFERS: i64 = 35; // CEA recommends no more than one level of interleaving. Play it safe + +// Enums for format, color codes, font bits, and modes +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxEia608Format { + SformatCcScreen, + SformatCcLine, + SformatXds, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxDecoder608ColorCode { + ColWhite = 0, + ColGreen = 1, + ColBlue = 2, + ColCyan = 3, + ColRed = 4, + ColYellow = 5, + ColMagenta = 6, + ColUserDefined = 7, + ColBlack = 8, + ColTransparent = 9, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FontBits { + FontRegular = 0, + FontItalics = 1, + FontUnderlined = 2, + FontUnderlinedItalics = 3, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcModes { + ModePopOn = 0, + ModeRollUp2 = 1, + ModeRollUp3 = 2, + ModeRollUp4 = 3, + ModeText = 4, + ModePaintOn = 5, + ModeFakeRollUp1 = 100, // Fake modes to emulate stuff +} + +// The `Eia608Screen` structure +#[derive(Debug)] +pub struct Eia608Screen { + pub format: CcxEia608Format, // Format of data inside this structure + pub characters: [[u8; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Characters + pub colors: + [[CcxDecoder608ColorCode; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Colors + pub fonts: [[FontBits; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Fonts + pub row_used: [i64; CCX_DECODER_608_SCREEN_ROWS], // Any data in row? + pub empty: i64, // Buffer completely empty? + pub start_time: i64, // Start time of this CC buffer + pub end_time: i64, // End time of this CC buffer + pub mode: CcModes, // Mode + pub channel: i64, // Currently selected channel + pub my_field: i64, // Used for sanity checks + pub xds_str: *const u8, // Pointer to XDS string + pub xds_len: usize, // Length of XDS string + pub cur_xds_packet_class: i64, // Class of XDS string +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubDataType { + #[default] + Generic = 0, + Dvb = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubType { + #[default] + Bitmap, + Cc608, + Text, + Raw, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum CcxEncodingType { + #[default] + Unicode = 0, + Latin1 = 1, + Utf8 = 2, + Ascii = 3, +} + +#[derive(Debug)] +pub struct CcSubtitle { + /** + * A generic data which contains data according to the decoder. + * @warn Decoder can't output multiple types of data. + */ + pub data: *mut std::ffi::c_void, // Pointer to generic data + pub datatype: SubDataType, // Data type (e.g., Generic, DVB) + + /** Number of data */ + pub nb_data: u32, + + /** Type of subtitle */ + pub subtype: SubType, + + /** Encoding type of text, ignored for bitmap or cc_screen subtypes */ + pub enc_type: CcxEncodingType, + + /* Set only when all the data is to be displayed at the same time. + * Unit of time is milliseconds. + */ + pub start_time: i64, + pub end_time: i64, + + /* Flags */ + pub flags: i32, + + /* Index of language table */ + pub lang_index: i32, + + /** Flag to tell that the decoder has given output */ + pub got_output: bool, + + pub mode: [u8; 5], // Mode as a fixed-size array of 5 bytes + pub info: [u8; 4], // Info as a fixed-size array of 4 bytes + + /** Used for DVB end time in milliseconds */ + pub time_out: i32, + + pub next: *mut CcSubtitle, // Pointer to the next subtitle + pub prev: *mut CcSubtitle, // Pointer to the previous subtitle +} + +// XDS classes +pub const XDS_CLASSES: [&str; 8] = [ + "Current", + "Future", + "Channel", + "Miscellaneous", + "Public service", + "Reserved", + "Private data", + "End", +]; + +// XDS program types +pub const 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", +]; + +// XDS class constants +pub const XDS_CLASS_CURRENT: u8 = 0; +pub const XDS_CLASS_FUTURE: u8 = 1; +pub const XDS_CLASS_CHANNEL: u8 = 2; +pub const XDS_CLASS_MISC: u8 = 3; +pub const XDS_CLASS_PUBLIC: u8 = 4; +pub const XDS_CLASS_RESERVED: u8 = 5; +pub const XDS_CLASS_PRIVATE: u8 = 6; +pub const XDS_CLASS_END: u8 = 7; +pub const XDS_CLASS_OUT_OF_BAND: u8 = 0x40; // Not a real class, a marker for packets for out-of-band data + +// Types for the classes current and future +pub const XDS_TYPE_PIN_START_TIME: u8 = 1; +pub const XDS_TYPE_LENGTH_AND_CURRENT_TIME: u8 = 2; +pub const XDS_TYPE_PROGRAM_NAME: u8 = 3; +pub const XDS_TYPE_PROGRAM_TYPE: u8 = 4; +pub const XDS_TYPE_CONTENT_ADVISORY: u8 = 5; +pub const XDS_TYPE_AUDIO_SERVICES: u8 = 6; +pub const XDS_TYPE_CGMS: u8 = 8; // Copy Generation Management System +pub const XDS_TYPE_ASPECT_RATIO_INFO: u8 = 9; // Appears in CEA-608-B but in E it's been removed as is "reserved" +pub const XDS_TYPE_PROGRAM_DESC_1: u8 = 0x10; +pub const XDS_TYPE_PROGRAM_DESC_2: u8 = 0x11; +pub const XDS_TYPE_PROGRAM_DESC_3: u8 = 0x12; +pub const XDS_TYPE_PROGRAM_DESC_4: u8 = 0x13; +pub const XDS_TYPE_PROGRAM_DESC_5: u8 = 0x14; +pub const XDS_TYPE_PROGRAM_DESC_6: u8 = 0x15; +pub const XDS_TYPE_PROGRAM_DESC_7: u8 = 0x16; +pub const XDS_TYPE_PROGRAM_DESC_8: u8 = 0x17; + +// Types for the class channel +pub const XDS_TYPE_NETWORK_NAME: u8 = 1; +pub const XDS_TYPE_CALL_LETTERS_AND_CHANNEL: u8 = 2; +pub const XDS_TYPE_TSID: u8 = 4; // Transmission Signal Identifier + +// Types for miscellaneous packets +pub const XDS_TYPE_TIME_OF_DAY: u8 = 1; +pub const XDS_TYPE_LOCAL_TIME_ZONE: u8 = 4; +pub const XDS_TYPE_OUT_OF_BAND_CHANNEL_NUMBER: u8 = 0x40; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct XdsBuffer { + pub in_use: i64, // Whether the buffer is in use + pub xds_class: i64, // XDS class (e.g., XDS_CLASS_CURRENT, etc.) + pub xds_type: i64, // XDS type (e.g., XDS_TYPE_PROGRAM_NAME, etc.) + pub bytes: [u8; NUM_BYTES_PER_PACKET as usize], // Data bytes (size defined by NUM_BYTES_PER_PACKET) + pub used_bytes: i64, // Number of bytes used in the buffer +} + +#[repr(C)] +pub struct CcxDecodersXdsContext { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i64, + pub current_xds_hour: i64, + pub current_xds_date: i64, + pub current_xds_month: i64, + pub current_program_type_reported: i64, // No. + pub xds_start_time_shown: i64, + pub xds_program_length_shown: i64, + pub xds_program_description: [[u8; 33]; 8], // 8 strings of 33 bytes each + + pub current_xds_network_name: [u8; 33], // String of 33 bytes + pub current_xds_program_name: [u8; 33], // String of 33 bytes + pub current_xds_call_letters: [u8; 7], // String of 7 bytes + pub current_xds_program_type: [u8; 33], // String of 33 bytes + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XdsBuffer + pub cur_xds_buffer_idx: i64, + pub cur_xds_packet_class: i64, + pub cur_xds_payload: *mut u8, // Pointer to payload + pub cur_xds_payload_length: i64, + pub cur_xds_packet_type: i64, + pub timing: TimingContext, // Replacing ccx_common_timing_ctx with TimingContext + + pub current_ar_start: i64, + pub current_ar_end: i64, + + pub xds_write_to_file: i64, // Set to 1 if XDS data is to be written to file +} + +impl Default for CcxDecodersXdsContext { + fn default() -> Self { + Self { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + 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 { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::default(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 0, + } + } +} + +impl Default for XdsBuffer { + fn default() -> Self { + Self { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + } + } +} + +impl Default for CcSubtitle { + fn default() -> Self { + Self { + data: std::ptr::null_mut(), + datatype: SubDataType::Generic, + nb_data: 0, + subtype: SubType::Cc608, + enc_type: CcxEncodingType::Utf8, + start_time: 0, + end_time: 0, + flags: 0, + lang_index: 0, + got_output: false, + mode: [0; 5], + info: [0; 4], + time_out: 0, + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + } + } +} diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index 9f32678db..b7fda833f 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -5,3 +5,4 @@ pub mod subtitle; pub mod teletext; pub mod time; pub mod util; +pub mod decoder_xds; diff --git a/src/rust/lib_ccxr/src/time/timing.rs b/src/rust/lib_ccxr/src/time/timing.rs index 564b61634..8250c457c 100644 --- a/src/rust/lib_ccxr/src/time/timing.rs +++ b/src/rust/lib_ccxr/src/time/timing.rs @@ -14,6 +14,7 @@ pub static GLOBAL_TIMING_INFO: RwLock = RwLock::new(GlobalTimi pub const DEFAULT_FRAME_RATE: f64 = 30000.0 / 1001.0; /// Represents the status of [`TimingContext::current_pts`] and [`TimingContext::min_pts`] +#[repr(C)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum PtsSet { No = 0, @@ -33,7 +34,8 @@ pub enum CaptionField { /// /// [`GlobalTimingInfo`] serves a similar purpose. The only difference is that its lifetime is /// global. -#[derive(Debug)] +#[repr(C)] +#[derive(Debug, PartialEq, Clone)] pub struct TimingContext { pub pts_set: PtsSet, /// if true then don't adjust again. diff --git a/src/rust/lib_ccxr/src/time/units.rs b/src/rust/lib_ccxr/src/time/units.rs index 0bc546c6a..5385cbe11 100644 --- a/src/rust/lib_ccxr/src/time/units.rs +++ b/src/rust/lib_ccxr/src/time/units.rs @@ -19,6 +19,7 @@ extern "C" { /// Represents a timestamp in milliseconds. /// /// The number can be negetive. +#[repr(C)] #[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub, Neg)] pub struct Timestamp { millis: i64, @@ -478,6 +479,7 @@ impl Timestamp { /// Represent the number of clock ticks as defined in Mpeg standard. /// /// This number can never be negetive. +#[repr(C)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub)] pub struct MpegClockTick(i64); @@ -508,6 +510,7 @@ impl MpegClockTick { /// Represents the number of frames. /// /// This number can never be negetive. +#[repr(C)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub)] pub struct FrameCount(u64); diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index c2f64a258..8d496a39a 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,6 +1,7 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. pub mod time; +pub mod xds_exports; use crate::ccx_options; use lib_ccxr::util::log::*; use lib_ccxr::util::{bits::*, levenshtein::*}; diff --git a/src/rust/src/libccxr_exports/xds_exports.rs b/src/rust/src/libccxr_exports/xds_exports.rs new file mode 100644 index 000000000..b81bc421f --- /dev/null +++ b/src/rust/src/libccxr_exports/xds_exports.rs @@ -0,0 +1,127 @@ +use lib_ccxr::decoder_xds::functions_xds::*; +use lib_ccxr::decoder_xds::structs_xds::*; +use lib_ccxr::time::timing::*; + +#[no_mangle] +pub extern "C" fn ccxr_ccx_decoders_xds_init_library( + timing: TimingContext, + xds_write_to_file: i64, +) -> CcxDecodersXdsContext { + ccx_decoders_xds_init_library(timing, xds_write_to_file) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_write_xds_string( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + p: *const u8, + len: usize, +) -> i32 { + write_xds_string(sub, ctx, p, len) // Call the pure Rust function +} + +/// # Safety +/// +/// The `fmt` pointer must be a valid null-terminated C string, +/// and `args` must contain valid formatting arguments. Ensure that +/// `fmt` is not null and points to a properly allocated memory region. +#[no_mangle] +pub unsafe extern "C" fn ccxr_xdsprint( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + fmt: *const std::os::raw::c_char, + args: *const std::os::raw::c_char, +) { + // Call the pure Rust function + xdsprint( + sub, + ctx, + unsafe { std::ffi::CStr::from_ptr(fmt).to_str().unwrap_or("") }, + format_args!("{}", unsafe { + std::ffi::CStr::from_ptr(args).to_str().unwrap_or("") + }), + ); +} + +#[no_mangle] +pub extern "C" fn ccxr_clear_xds_buffer(ctx: &mut CcxDecodersXdsContext, num: i64) { + clear_xds_buffer(ctx, num); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_how_many_used(ctx: &CcxDecodersXdsContext) -> i64 { + how_many_used(ctx) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_process_xds_bytes(ctx: &mut CcxDecodersXdsContext, hi: u8, lo: i64) { + process_xds_bytes(ctx, hi, lo); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_copy_generation_management_system( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u8, + c2: u8, +) { + xds_do_copy_generation_management_system(sub, ctx, c1, c2); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_content_advisory( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + c1: u8, + c2: u8, +) { + xds_do_content_advisory(sub, ctx, c1, c2); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_private_data( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i64 { + xds_do_private_data(sub, ctx) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_misc(ctx: &CcxDecodersXdsContext) -> i64 { + xds_do_misc(ctx) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_current_and_future( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i64 { + xds_do_current_and_future(sub, ctx) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_do_end_of_xds( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, + expected_checksum: i64, +) { + do_end_of_xds(sub, ctx, expected_checksum); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_do_channel( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, +) -> i64 { + xds_do_channel(sub, ctx) // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_debug_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + xds_debug_test(ctx, sub); // Call the pure Rust function +} + +#[no_mangle] +pub extern "C" fn ccxr_xds_cea608_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + xds_cea608_test(ctx, sub); // Call the pure Rust function +} diff --git a/src/rust/wrapper.h b/src/rust/wrapper.h index a7297478f..18b62075d 100644 --- a/src/rust/wrapper.h +++ b/src/rust/wrapper.h @@ -10,4 +10,4 @@ #include "../lib_ccx/lib_ccx.h" #include "../lib_ccx/hardsubx.h" #include "../lib_ccx/utility.h" -#include "../lib_ccx/ccx_encoders_helpers.h" +#include "../lib_ccx/ccx_encoders_helpers.h" \ No newline at end of file From 9f03ab44335bacf19b9d3805825184f84b9caebe Mon Sep 17 00:00:00 2001 From: vats004 <=> Date: Sat, 29 Mar 2025 19:14:02 +0530 Subject: [PATCH 2/2] some serious code refactoring --- src/lib_ccx/ccx_decoders_xds.c | 122 +- src/lib_ccx/ccx_decoders_xds.h | 2 +- .../lib_ccxr/src/decoder_xds/exit_codes.rs | 43 + .../lib_ccxr/src/decoder_xds/functions_xds.rs | 2127 ++++++----------- src/rust/lib_ccxr/src/decoder_xds/mod.rs | 1 + .../lib_ccxr/src/decoder_xds/structs_xds.rs | 613 ++--- src/rust/lib_ccxr/src/lib.rs | 2 +- src/rust/src/libccxr_exports/xds_exports.rs | 162 +- 8 files changed, 1128 insertions(+), 1944 deletions(-) create mode 100644 src/rust/lib_ccxr/src/decoder_xds/exit_codes.rs diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index 682f57347..8234f7210 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -5,75 +5,23 @@ #include "utility.h" #ifndef DISABLE_RUST - -extern struct ccx_decoders_xds_context *ccxr_ccx_decoders_xds_init_library( - struct ccx_common_timing_ctx timing, - int xds_write_to_file); - -extern int ccxr_write_xds_string( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx, - const char *p, - size_t len); - -extern void ccxr_xdsprint( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx, - const char *_fmt, - va_list args); - -extern void ccxr_clear_xds_buffer( - struct ccx_decoders_xds_context *ctx, - int64_t num); - -extern int64_t ccxr_how_many_used( - const struct ccx_decoders_xds_context *ctx); - extern void ccxr_process_xds_bytes( struct ccx_decoders_xds_context *ctx, - uint8_t hi, - int64_t lo); - -extern void ccxr_xds_do_copy_generation_management_system( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx, - uint8_t c1, - uint8_t c2); - -extern void ccxr_xds_do_content_advisory( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx, - uint8_t c1, - uint8_t c2); - -extern int64_t ccxr_xds_do_private_data( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx); - -extern int64_t ccxr_xds_do_misc( - const struct ccx_decoders_xds_context *ctx); - -extern int64_t ccxr_xds_do_current_and_future( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx); + unsigned char hi, + unsigned char lo); extern void ccxr_do_end_of_xds( struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, - int64_t expected_checksum); - -extern int64_t ccxr_xds_do_channel( - struct cc_subtitle *sub, - struct ccx_decoders_xds_context *ctx); + unsigned char expected_checksum); -extern void ccxr_xds_debug_test( - struct ccx_decoders_xds_context *ctx, - struct cc_subtitle *sub); +extern struct ccx_decoders_xds_context *ccxr_ccx_decoders_xds_init_library( + struct ccx_common_timing_ctx *timing, + int xds_write_to_file); extern void ccxr_xds_cea608_test( struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub); - #endif LLONG ts_start_of_xds = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet @@ -153,7 +101,7 @@ static const char *XDSProgramTypes[] = struct ccx_decoders_xds_context *ccx_decoders_xds_init_library(struct ccx_common_timing_ctx *timing, int xds_write_to_file) { #ifndef DISABLE_RUST - return ccxr_ccx_decoders_xds_init_library(*timing, xds_write_to_file); + return ccxr_ccx_decoders_xds_init_library(timing, xds_write_to_file); // Use the Rust implementation #else int i; struct ccx_decoders_xds_context *ctx = NULL; @@ -201,9 +149,6 @@ struct ccx_decoders_xds_context *ccx_decoders_xds_init_library(struct ccx_common int write_xds_string(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, char *p, size_t len) { -#ifndef DISABLE_RUST - return ccxr_write_xds_string(sub, ctx, p, len); -#else struct eia608_screen *data = NULL; data = (struct eia608_screen *)realloc(sub->data, (sub->nb_data + 1) * sizeof(*data)); if (!data) @@ -217,6 +162,7 @@ int write_xds_string(struct cc_subtitle *sub, struct ccx_decoders_xds_context *c { sub->data = data; sub->datatype = CC_DATATYPE_GENERIC; + data = (struct eia608_screen *)sub->data + sub->nb_data; data->format = SFORMAT_XDS; data->start_time = ts_start_of_xds; @@ -224,24 +170,17 @@ int write_xds_string(struct cc_subtitle *sub, struct ccx_decoders_xds_context *c data->xds_str = p; data->xds_len = len; data->cur_xds_packet_class = ctx->cur_xds_packet_class; + sub->nb_data++; sub->type = CC_608; sub->got_output = 1; } return 0; -#endif } void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, const char *fmt, ...) { -#ifndef DISABLE_RUST - va_list ap; - va_start(ap, fmt); - ccxr_xdsprint(sub, ctx, fmt, ap); - va_end(ap); -#else - if (!ctx->xds_write_to_file) return; /* Guess we need no more than 100 bytes. */ @@ -279,24 +218,19 @@ void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, con p = np; } } -#endif } void xds_debug_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub) { -#ifndef DISABLE_RUST - ccxr_xds_debug_test(ctx, sub); -#else process_xds_bytes(ctx, 0x05, 0x02); process_xds_bytes(ctx, 0x20, 0x20); do_end_of_xds(sub, ctx, 0x2a); -#endif } void xds_cea608_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub) { #ifndef DISABLE_RUST - ccxr_xds_cea608_test(ctx, sub); + return ccxr_xds_cea608_test(ctx, sub); #else /* This test is the sample data that comes in CEA-608. It sets the program name to be "Star Trek". The checksum is 0x1d and the validation must succeed. */ @@ -314,34 +248,26 @@ void xds_cea608_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *s int how_many_used(struct ccx_decoders_xds_context *ctx) { -#ifndef DISABLE_RUST - ccxr_how_many_used(ctx); -#else int c = 0; for (int i = 0; i < NUM_XDS_BUFFERS; i++) if (ctx->xds_buffers[i].in_use) c++; return c; -#endif } void clear_xds_buffer(struct ccx_decoders_xds_context *ctx, int num) { -#ifndef DISABLE_RUST - ccxr_clear_xds_buffer(ctx, num); -#else ctx->xds_buffers[num].in_use = 0; ctx->xds_buffers[num].xds_class = -1; ctx->xds_buffers[num].xds_type = -1; ctx->xds_buffers[num].used_bytes = 0; memset(ctx->xds_buffers[num].bytes, 0, NUM_BYTES_PER_PACKET); -#endif } 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); + return ccxr_process_xds_bytes(ctx, hi, lo); // Use the Rust implementation #else int is_new; if (!ctx) @@ -420,9 +346,6 @@ void process_xds_bytes(struct ccx_decoders_xds_context *ctx, const unsigned char */ void xds_do_copy_generation_management_system(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned c1, unsigned c2) { -#ifndef DISABLE_RUST - ccxr_xds_do_copy_generation_management_system(sub, ctx, c1, c2); -#else static unsigned last_c1 = -1, last_c2 = -1; static char copy_permited[256]; static char aps[256]; @@ -476,14 +399,10 @@ void xds_do_copy_generation_management_system(struct cc_subtitle *sub, struct cc ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", copy_permited); ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", aps); ccx_common_logging.debug_ftn(CCX_DMT_DECODER_XDS, "\rXDS: %s\n", rcd); -#endif } void xds_do_content_advisory(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, unsigned c1, unsigned c2) { -#ifndef DISABLE_RUST - ccxr_xds_do_content_advisory(sub, ctx, c1, c2); -#else static unsigned last_c1 = -1, last_c2 = -1; static char age[256]; static char content[256]; @@ -593,14 +512,10 @@ void xds_do_content_advisory(struct cc_subtitle *sub, struct ccx_decoders_xds_co if (changed && !supported) ccx_common_logging.log_ftn("XDS: Unsupported ContentAdvisory encoding, please submit sample.\n"); -#endif } int xds_do_current_and_future(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { -#ifndef DISABLE_RUST - ccxr_xds_do_current_and_future(sub, ctx); -#else int was_proc = 0; char *str = malloc(1024); @@ -847,14 +762,10 @@ int xds_do_current_and_future(struct cc_subtitle *sub, struct ccx_decoders_xds_c free(str); return was_proc; -#endif } int xds_do_channel(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { -#ifndef DISABLE_RUST - ccxr_xds_do_channel(sub, ctx); -#else int was_proc = 0; if (!ctx) return CCX_EINVAL; @@ -914,14 +825,10 @@ int xds_do_channel(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx break; } return was_proc; -#endif } int xds_do_private_data(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx) { -#ifndef DISABLE_RUST - ccxr_xds_do_private_data(sub, ctx); -#else char *str; int i; @@ -938,14 +845,10 @@ int xds_do_private_data(struct cc_subtitle *sub, struct ccx_decoders_xds_context xdsprint(sub, ctx, str); free(str); return 1; -#endif } int xds_do_misc(struct ccx_decoders_xds_context *ctx) { -#ifndef DISABLE_RUST - ccxr_xds_do_misc(ctx); -#else int was_proc = 0; if (!ctx) return CCX_EINVAL; @@ -985,13 +888,12 @@ int xds_do_misc(struct ccx_decoders_xds_context *ctx) break; } return was_proc; -#endif } 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); + return ccxr_do_end_of_xds(sub, ctx, expected_checksum); #else int cs = 0; diff --git a/src/lib_ccx/ccx_decoders_xds.h b/src/lib_ccx/ccx_decoders_xds.h index 8f894ed75..46161d09e 100644 --- a/src/lib_ccx/ccx_decoders_xds.h +++ b/src/lib_ccx/ccx_decoders_xds.h @@ -12,7 +12,7 @@ void do_end_of_xds (struct cc_subtitle *sub, struct ccx_decoders_xds_context *ct struct ccx_decoders_xds_context *ccx_decoders_xds_init_library(struct ccx_common_timing_ctx *timing, int xds_write_to_file); -void xds_cea608_test(); +void xds_cea608_test(struct ccx_decoders_xds_context *ctx, struct cc_subtitle *sub); struct xds_buffer { diff --git a/src/rust/lib_ccxr/src/decoder_xds/exit_codes.rs b/src/rust/lib_ccxr/src/decoder_xds/exit_codes.rs new file mode 100644 index 000000000..db8b1f89d --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_xds/exit_codes.rs @@ -0,0 +1,43 @@ +// codes for do_end_of_xds +pub const XDS_CLASS_FUTURE: i64 = 1; +pub const XDS_CLASS_CHANNEL: i64 = 2; +pub const XDS_CLASS_MISC: i64 = 3; +pub const XDS_CLASS_PRIVATE: i64 = 6; +pub const XDS_CLASS_OUT_OF_BAND: i64 = 0x40; + +// codes for xds_do_misc +pub const XDS_TYPE_TIME_OF_DAY: i64 = 1; +pub const XDS_TYPE_LOCAL_TIME_ZONE: i64 = 4; + +// codes for xds_do_channel +pub const XDS_TYPE_NETWORK_NAME: i64 = 1; +pub const XDS_TYPE_CALL_LETTERS_AND_CHANNEL: i64 = 2; +pub const XDS_TYPE_TSID: i64 = 4; + +// codes for xds_do_current_and_future +pub const XDS_CLASS_CURRENT: i64 = 0; + +pub const XDS_TYPE_PIN_START_TIME: i64 = 1; +pub const XDS_TYPE_LENGTH_AND_CURRENT_TIME: i64 = 2; +pub const XDS_TYPE_PROGRAM_NAME: i64 = 3; +pub const XDS_TYPE_PROGRAM_TYPE: i64 = 4; +pub const XDS_TYPE_CONTENT_ADVISORY: i64 = 5; +pub const XDS_TYPE_AUDIO_SERVICES: i64 = 6; +pub const XDS_TYPE_CGMS: i64 = 8; +pub const XDS_TYPE_ASPECT_RATIO_INFO: i64 = 9; + +pub const XDS_TYPE_PROGRAM_DESC_1: i64 = 0x10; +pub const XDS_TYPE_PROGRAM_DESC_8: i64 = 0x17; + +// codes for write_xds_string +pub const TS_START_OF_XDS: i64 = -1; // Time at which we switched to XDS mode, =-1 hasn't happened yet + +// codes for Eia608Screen::default +pub const CCX_DECODER_608_SCREEN_ROWS: usize = 15; +pub const CCX_DECODER_608_SCREEN_WIDTH: usize = 32; + +// codes for CcxDecodersXdsContext::default +pub const NUM_XDS_BUFFERS: usize = 9; + +// codes for XdsBuffer::default +pub const NUM_BYTES_PER_PACKET: usize = 35; diff --git a/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs b/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs index 7e023e809..1da2dba67 100644 --- a/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs +++ b/src/rust/lib_ccxr/src/decoder_xds/functions_xds.rs @@ -1,660 +1,434 @@ -use crate::decoder_xds::structs_xds::*; - -use crate::time::c_functions::*; -use crate::time::timing::*; +use crate::time::c_functions::get_fts; +use crate::time::timing::CaptionField; +use crate::util::log::{debug, info, DebugMessageFlag}; -use crate::util::log::*; - -use std::alloc::{realloc, Layout}; -use std::ffi::CString; -use std::fmt::Write; -use std::ptr; - -/// Rust equivalent for `ccx_decoders_xds_init_library` function in C. Initializes the XDS decoder context. -/// -/// # Safety -/// -/// The `timing` parameter must be valid and properly initialized. Ensure that all memory allocations succeed. -pub fn ccx_decoders_xds_init_library( - timing: TimingContext, - xds_write_to_file: i64, -) -> CcxDecodersXdsContext { - let mut ctx = CcxDecodersXdsContext { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - 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 { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: ptr::null_mut(), - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing, - current_ar_start: 0, - current_ar_end: 0, - xds_write_to_file, - }; - - // Initialize xds_buffers - for buffer in ctx.xds_buffers.iter_mut() { - buffer.in_use = 0; - buffer.xds_class = -1; - buffer.xds_type = -1; - buffer.used_bytes = 0; - unsafe { - ptr::write_bytes(buffer.bytes.as_mut_ptr(), 0, NUM_BYTES_PER_PACKET as usize); - } - } - - // Initialize xds_program_description - for description in ctx.xds_program_description.iter_mut() { - unsafe { - ptr::write_bytes(description.as_mut_ptr(), 0, 33); - } - } +use crate::decoder_xds::exit_codes::*; +use crate::decoder_xds::structs_xds::*; - // Initialize other fields - unsafe { - ptr::write_bytes(ctx.current_xds_network_name.as_mut_ptr(), 0, 33); - ptr::write_bytes(ctx.current_xds_program_name.as_mut_ptr(), 0, 33); - ptr::write_bytes(ctx.current_xds_call_letters.as_mut_ptr(), 0, 7); - ptr::write_bytes(ctx.current_xds_program_type.as_mut_ptr(), 0, 33); - } +//---------------------------------------------------------------- - ctx +#[derive(Debug)] +pub enum XdsError { + WriteError, + OtherError(String), } - -/// Rust equivalent for `write_xds_string` function in C. Writes XDS string data into the subtitle structure. -/// -/// # Safety -/// -/// The `sub` and `ctx` pointers must be valid and non-null. Ensure that memory allocations succeed and that `p` points to valid memory. pub fn write_xds_string( sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext, - p: *const u8, - len: usize, -) -> i32 { - // Allocate or reallocate memory for `sub.data` - let new_size = (sub.nb_data as usize + 1) * std::mem::size_of::(); - let layout = Layout::array::(sub.nb_data as usize + 1).unwrap(); - - let data = if sub.data.is_null() { - unsafe { std::alloc::alloc(layout) as *mut Eia608Screen } - } else { - unsafe { realloc(sub.data as *mut u8, layout, new_size) as *mut Eia608Screen } + p: String, + len: i64, +) -> Result<(), XdsError> { + let new_screen = Eia608Screen { + format: CcxEia608Format::SformatXds, + start_time: TS_START_OF_XDS, + end_time: (get_fts(&mut ctx.timing, CaptionField::Field2)).millis(), + xds_str: p, + xds_len: len, + cur_xds_packet_class: ctx.cur_xds_packet_class, + ..Default::default() // Use default values for the remaining fields }; - if data.is_null() { - sub.data = ptr::null_mut(); - sub.nb_data = 0; - info!("No Memory left"); - return -1; - } else { - sub.data = data as *mut std::ffi::c_void; // the as *mut std::ffi::c_void is necessary to convert from *mut Eia608Screen to *mut std::ffi::c_void - // couldn't find a way to do this without the as *mut std::ffi::c_void - sub.datatype = SubDataType::Generic; - unsafe { - let data_ptr = (sub.data as *mut Eia608Screen).add(sub.nb_data as usize); - (*data_ptr).format = CcxEia608Format::SformatXds; - (*data_ptr).end_time = get_fts(&mut ctx.timing, CaptionField::Field2).millis(); // read millis from Timestamp - // (*data_ptr).end_time = get_fts(&ctx.timing, 2); - (*data_ptr).xds_str = p; - (*data_ptr).xds_len = len; - (*data_ptr).cur_xds_packet_class = ctx.cur_xds_packet_class; - } - sub.nb_data += 1; - sub.subtype = SubType::Cc608; - sub.got_output = true; - } + sub.data.push(new_screen); + sub.datatype = SubDataType::Generic; + sub.nb_data = (sub.data.len() + 1) as i64; + sub.subtype = SubType::Cc608; + sub.got_output = true; - 0 + Ok(()) } +//---------------------------------------------------------------- -/// Rust equivalent for `xdsprint` function in C. Formats and writes XDS data to the subtitle structure. -/// -/// # Safety -/// -/// The `sub` and `ctx` pointers must be valid and non-null. Ensure that memory allocations succeed and that the formatted string is valid. -pub fn xdsprint( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - _fmt: &str, // coz fmt is unused in the current implementation - args: std::fmt::Arguments, -) { - if ctx.xds_write_to_file == 0 { +// macro could be used, bad coz nothing is returned, could be an impl +pub fn xdsprint(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext, args: std::fmt::Arguments) { + if !ctx.xds_write_to_file { return; } - // Initial buffer size - let mut size: usize = 100; - let mut buffer = String::with_capacity(size); - - loop { - // Try to write the formatted string into the buffer - match write!(&mut buffer, "{}", args) { - Ok(_) => { - // If successful, write the XDS string - let c_string = CString::new(buffer.as_bytes()).unwrap(); - write_xds_string(sub, ctx, c_string.as_ptr() as *const u8, buffer.len()); - return; - } - Err(_) => { - // If the buffer is too small, double its size - size *= 2; - buffer.reserve(size); - } - } - } -} - -/// Rust equivalent for `clear_xds_buffer` function in C. Clears the specified XDS buffer. -/// -/// # Safety -/// -/// The `num` parameter must be within the valid range of the `xds_buffers` array, and the `ctx` pointer must be valid and non-null. -pub fn clear_xds_buffer(ctx: &mut CcxDecodersXdsContext, num: i64) { - ctx.xds_buffers[num as usize].in_use = 0; - ctx.xds_buffers[num as usize].xds_class = -1; - ctx.xds_buffers[num as usize].xds_type = -1; - ctx.xds_buffers[num as usize].used_bytes = 0; - - unsafe { - ptr::write_bytes( - ctx.xds_buffers[num as usize].bytes.as_mut_ptr(), - 0, - NUM_BYTES_PER_PACKET as usize, - ); - } + let formatted = format!("{}", args); + let formatted_len = formatted.len() as i64; + write_xds_string(sub, ctx, formatted, formatted_len).unwrap(); // unhandled err variant } -/// Rust equivalent for `how_many_used` function in C. Counts the number of used XDS buffers. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. -pub fn how_many_used(ctx: &CcxDecodersXdsContext) -> i64 { - let mut count: i64 = 0; - for buffer in ctx.xds_buffers.iter() { - if buffer.in_use != 0 { - count += 1; - } - } - count -} +//---------------------------------------------------------------- -/// Rust equivalent for `process_xds_bytes` function in C. Processes XDS bytes and updates the context. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `hi` and `lo` values are within valid ranges. -pub fn process_xds_bytes(ctx: &mut CcxDecodersXdsContext, hi: u8, lo: i64) { +pub fn process_xds_bytes(ctx: &mut CcxDecodersXdsContext, hi: i64, lo: i64) { if (0x01..=0x0f).contains(&hi) { - let xds_class = ((hi - 1) / 2) as i64; // 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 - info!( - "XDS Start: {}.{} Is new: {} | Class: {} ({:?}), Used buffers: {}", - hi, - lo, - is_new, - xds_class, - XDS_CLASSES[xds_class as usize], - how_many_used(ctx) - ); + let xds_class = (hi - 1) / 2; + let is_new = hi % 2 != 0; - let mut first_free_buf: i64 = -1; - let mut matching_buf: i64 = -1; + let mut first_free_buf: Option = None; + let mut matching_buf: Option = None; - for i in 0..NUM_XDS_BUFFERS { - let buffer = &ctx.xds_buffers[i as usize]; - if buffer.in_use != 0 && buffer.xds_class == xds_class && buffer.xds_type == lo { - matching_buf = i; + for (i, buf) in ctx.xds_buffers.iter_mut().enumerate() { + if buf.in_use && buf.xds_class == xds_class && buf.xds_type == lo { + matching_buf = Some(i); break; } - if first_free_buf == -1 && buffer.in_use == 0 { - first_free_buf = i; + if first_free_buf.is_none() && !buf.in_use { + first_free_buf = Some(i); } } - // Handle the three possibilities - if matching_buf == -1 && first_free_buf == -1 { - info!( - "Note: All XDS buffers full (bug or suicidal stream). Ignoring this one ({}, {}).", - xds_class, lo - ); - ctx.cur_xds_buffer_idx = -1; - return; - } - - ctx.cur_xds_buffer_idx = if matching_buf != -1 { - matching_buf - } else { - first_free_buf + let buf_idx = match matching_buf.or(first_free_buf) { + Some(idx) => idx, + None => { + println!("All XDS buffers full, ignoring."); + ctx.cur_xds_buffer_idx = None; + return; + } }; - let cur_idx = ctx.cur_xds_buffer_idx as usize; - let cur_buffer = &mut ctx.xds_buffers[cur_idx]; - - if is_new || cur_buffer.in_use == 0 { - // Discard previous data - cur_buffer.xds_class = xds_class; - cur_buffer.xds_type = lo; - cur_buffer.used_bytes = 0; - cur_buffer.in_use = 1; - - unsafe { - ptr::write_bytes( - cur_buffer.bytes.as_mut_ptr(), - 0, - NUM_BYTES_PER_PACKET as usize, - ); - } + ctx.cur_xds_buffer_idx = Some(buf_idx as i64); + + let buf = &mut ctx.xds_buffers[buf_idx]; + + if is_new || !buf.in_use { + buf.xds_class = xds_class; + buf.xds_type = lo; + buf.used_bytes = 0; + buf.in_use = true; + buf.bytes = vec![0; NUM_BYTES_PER_PACKET]; } if !is_new { - // Continue codes aren't added to the packet return; } - } else { - // Informational: 00, or 0x20-0x7F, so 01-0x1f forbidden - info!( - "XDS: {:02X}.{:02X} ({}, {})", - hi, lo, hi as char, lo as u8 as char - ); - if (hi > 0 && hi <= 0x1f) || (lo > 0 && lo <= 0x1f) { - info!("Note: Illegal XDS data"); - return; + } else if (0x01..=0x1f).contains(&hi) || (0x01..=0x1f).contains(&lo) { + println!("Illegal XDS data"); + return; + } + + if let Some(buf_idx) = ctx.cur_xds_buffer_idx { + let buf_idx = buf_idx as usize; + let buf = &mut ctx.xds_buffers[buf_idx]; + if buf.used_bytes <= 32 && buf.used_bytes < ((buf.bytes.len() as i64) - 1) { + buf.bytes[buf.used_bytes as usize] = hi; + buf.used_bytes += 1; + buf.bytes[buf.used_bytes as usize] = lo; + buf.used_bytes += 1; + buf.bytes[buf.used_bytes as usize] = 0; } } +} + +//---------------------------------------------------------------- - let cur_idx = ctx.cur_xds_buffer_idx as usize; - let cur_buffer = &mut ctx.xds_buffers[cur_idx]; +pub struct XdsCopyState { + last_c1: Option, + last_c2: Option, + copy_permitted: String, + aps: String, + rcd: String, +} +impl XdsCopyState { + fn new() -> Self { + Self { + last_c1: None, + last_c2: None, + copy_permitted: String::new(), + aps: String::new(), + rcd: String::new(), + } + } +} - if cur_buffer.used_bytes <= 32 { - // Should always happen - cur_buffer.bytes[cur_buffer.used_bytes as usize] = hi; - cur_buffer.used_bytes += 1; - cur_buffer.bytes[cur_buffer.used_bytes as usize] = lo as u8; - cur_buffer.used_bytes += 1; - cur_buffer.bytes[cur_buffer.used_bytes as usize] = 0; +impl Default for XdsCopyState { + fn default() -> Self { + Self::new() } } -/// Rust equivalent for `xds_do_copy_generation_management_system` function in C. Processes CGMS (Copy Generation Management System) data. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `c1` and `c2` values are within valid ranges. pub fn xds_do_copy_generation_management_system( sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext, c1: u8, c2: u8, + state: &mut XdsCopyState, ) { - static mut LAST_C1: i64 = -1; - static mut LAST_C2: i64 = -1; - - let mut copy_permitted = String::new(); - let mut aps = String::new(); - let mut rcd = String::new(); - - let mut changed = false; - let c1_6 = (c1 & 0x40) >> 6; - 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 c2_6 = (c2 & 0x40) >> 6; - let rcd0 = c2 & 0x01; - // User doesn't need XDS - if ctx.xds_write_to_file == 0 { - return; - } - - // These must be high. If not, not following specs if c1_6 == 0 || c2_6 == 0 { return; } - unsafe { - if LAST_C1 != c1 as i64 || LAST_C2 != c2 as i64 { - changed = true; - LAST_C1 = c1 as i64; - LAST_C2 = c2 as i64; - - // Changed since last time, decode - let copytext = [ - "Copy permitted (no restrictions)", - "No more copies (one generation copy has been made)", - "One generation of copies can be made", - "No copying is permitted", - ]; - let apstext = [ - "No APS", - "PSP On; Split Burst Off", - "PSP On; 2 line Split Burst On", - "PSP On; 4 line Split Burst On", - ]; - - copy_permitted = format!("CGMS: {}", copytext[(cgms_a_b4 * 2 + cgms_a_b3) as usize]); - aps = format!("APS: {}", apstext[(aps_b2 * 2 + aps_b1) as usize]); - rcd = format!("Redistribution Control Descriptor: {}", rcd0); - } + let changed = state.last_c1 != Some(c1) || state.last_c2 != Some(c2); + + if changed { + state.last_c1 = Some(c1); + state.last_c2 = Some(c2); + + let copytext = [ + "Copy permitted (no restrictions)", + "No more copies (one generation copy has been made)", + "One generation of copies can be made", + "No copying is permitted", + ]; + + let apstext = [ + "No APS", + "PSP On; Split Burst Off", + "PSP On; 2 line Split Burst On", + "PSP On; 4 line Split Burst On", + ]; + + let cgms_index = ((c1 & 0x10) >> 3) | ((c1 & 0x08) >> 3); + let aps_index = ((c1 & 0x04) >> 1) | ((c1 & 0x02) >> 1); + let rcd0 = c2 & 0x01; + + state.copy_permitted = format!("CGMS: {}", copytext[cgms_index as usize]); + state.aps = format!("APS: {}", apstext[aps_index as usize]); + state.rcd = format!("Redistribution Control Descriptor: {}", rcd0); } - xdsprint(sub, ctx, ©_permitted, format_args!("")); - xdsprint(sub, ctx, &aps, format_args!("")); - xdsprint(sub, ctx, &rcd, format_args!("")); + xdsprint(sub, ctx, format_args!("{}", state.copy_permitted)); + xdsprint(sub, ctx, format_args!("{}", state.aps)); + xdsprint(sub, ctx, format_args!("{}", state.rcd)); if changed { - info!("XDS: {}", copy_permitted); - info!("XDS: {}", aps); - info!("XDS: {}", rcd); + info!("{}", state.copy_permitted); + info!("{}", state.aps); + info!("{}", state.rcd); } - info!("XDS: {}", copy_permitted); - info!("XDS: {}", aps); - info!("XDS: {}", rcd); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", state.copy_permitted); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", state.aps); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", state.rcd); +} + +//---------------------------------------------------------------- + +struct Bits { + // c1_6: bool, + da2: bool, + a1: bool, + a0: bool, + r2: bool, + r1: bool, + r0: bool, + // c2_6: bool, + fv: bool, + s: bool, + la3: bool, + g2: bool, + g1: bool, + g0: bool, +} + +fn decode_bits(byte: u8) -> Bits { + Bits { + // c1_6: byte & 0x40 != 0, + da2: byte & 0x20 != 0, + a1: byte & 0x10 != 0, + a0: byte & 0x08 != 0, + r2: byte & 0x04 != 0, + r1: byte & 0x02 != 0, + r0: byte & 0x01 != 0, + // c2_6: byte & 0x40 != 0, + fv: byte & 0x20 != 0, + s: byte & 0x10 != 0, + la3: byte & 0x08 != 0, + g2: byte & 0x04 != 0, + g1: byte & 0x02 != 0, + g0: byte & 0x01 != 0, + } +} + +fn bits_to_number(b2: bool, b1: bool, b0: bool) -> usize { + (b2 as usize) * 4 + (b1 as usize) * 2 + (b0 as usize) } -/// Rust equivalent for `xds_do_content_advisory` function in C. Processes content advisory data. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory. pub fn xds_do_content_advisory( sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, + ctx: Option<&mut CcxDecodersXdsContext>, c1: u8, c2: u8, + state: &mut XdsCopyState, ) { - static mut LAST_C1: i64 = -1; - static mut LAST_C2: i64 = -1; - - let mut age = String::new(); - let mut content = String::new(); - let mut rating = String::new(); - let mut changed = false; - - 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; - 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; - let mut supported = false; - - // User doesn't need XDS - if ctx.xds_write_to_file == 0 { + if ctx.is_none() { return; } + let ctx = ctx.unwrap(); - // These must be high. If not, not following specs - if c1_6 == 0 || c2_6 == 0 { + if (c1 & 0x40) == 0 || (c2 & 0x40) == 0 { return; } - unsafe { - if LAST_C1 != c1 as i64 || LAST_C2 != c2 as i64 { - changed = true; - LAST_C1 = c1 as i64; - LAST_C2 = c2 as i64; - - // Changed since last time, decode - if a1 == 0 && a0 == 1 { - // US TV parental guidelines - let agetext = [ - "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", - ]; - age = format!( - "ContentAdvisory: US TV Parental Guidelines. Age Rating: {}", - agetext[(g2 * 4 + g1 * 2 + g0) as usize] - ); - content.clear(); - if g2 == 0 && g1 == 1 && g0 == 0 { - // For TV-Y7 (Older children), the Violence bit is "fantasy violence" - if fv != 0 { - content.push_str("[Fantasy Violence] "); - } - } else { - // For all others, is real - if fv != 0 { - content.push_str("[Violence] "); - } - } - if s != 0 { - content.push_str("[Sexual Situations] "); - } - if la3 != 0 { - content.push_str("[Adult Language] "); - } - if da2 != 0 { - content.push_str("[Sexually Suggestive Dialog] "); - } - supported = true; - } - if a0 == 0 { - // MPA - let ratingtext = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; - rating = format!( - "ContentAdvisory: MPA Rating: {}", - ratingtext[(r2 * 4 + r1 * 2 + r0) as usize] - ); - supported = true; - } - if a0 == 1 && a1 == 1 && da2 == 0 && la3 == 0 { - // Canadian English Language Rating - let ratingtext = [ - "Exempt", - "Children", - "Children eight years and older", - "General programming suitable for all audiences", - "Parental Guidance", - "Viewers 14 years and older", - "Adult Programming", - "[undefined]", - ]; - rating = format!( - "ContentAdvisory: Canadian English Rating: {}", - ratingtext[(g2 * 4 + g1 * 2 + g0) as usize] - ); - supported = true; - } - if a0 == 1 && a1 == 1 && da2 == 1 && la3 == 0 { - // Canadian French Language Rating - let ratingtext = [ - "Exemptes", - "General", - "General - Not recommended for young children", - "This program may not be suitable for children under 13 years of age", - "This program is not suitable for children under 16 years of age", - "This program is reserved for adults", - "[invalid]", - "[invalid]", - ]; - rating = format!( - "ContentAdvisory: Canadian French Rating: {}", - ratingtext[(g2 * 4 + g1 * 2 + g0) as usize] - ); - supported = true; - } - } + let changed = state.last_c1 != Some(c1) || state.last_c2 != Some(c2); + + if changed { + state.last_c1 = Some(c1); + state.last_c2 = Some(c2); } - if a1 == 0 && a0 == 1 { + let c1_bits = decode_bits(c1); + let c2_bits = decode_bits(c2); + + let mut supported = false; + let mut age_info = String::new(); + let mut content_info = String::new(); + let mut rating_info = String::new(); + + if !c1_bits.a1 && c1_bits.a0 { // US TV parental guidelines - xdsprint(sub, ctx, &age, format_args!("")); - xdsprint(sub, ctx, &content, format_args!("")); - if changed { - info!("XDS: {}", age); - info!("XDS: {}", content); + let age_labels = [ + "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", + ]; + let age_idx = bits_to_number(c2_bits.g2, c2_bits.g1, c2_bits.g0); + age_info = format!( + "ContentAdvisory: US TV Parental Guidelines. Age Rating: {}", + age_labels.get(age_idx).unwrap_or(&"Unknown") + ); + + if !c2_bits.g2 && c2_bits.g1 && !c2_bits.g0 { + if c2_bits.fv { + content_info.push_str("[Fantasy Violence] "); + } + } else if c2_bits.fv { + content_info.push_str("[Violence] "); } - } - if a0 == 0 - || (a0 == 1 && a1 == 1 && da2 == 0 && la3 == 0) - || (a0 == 1 && a1 == 1 && da2 == 1 && la3 == 0) - { - // MPA or Canadian ratings - xdsprint(sub, ctx, &rating, format_args!("")); - if changed { - info!("XDS: {}", rating); + + if c2_bits.s { + content_info.push_str("[Sexual Situations] "); } - } - if changed && !supported { - info!("XDS: Unsupported ContentAdvisory encoding, please submit sample."); - } -} + if c2_bits.la3 { + content_info.push_str("[Adult Language] "); + } -/// Rust equivalent for `xds_do_private_data` function in C. Processes private data in the XDS context. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. -pub fn xds_do_private_data(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { - if ctx.xds_write_to_file == 0 { - return CCX_EINVAL; + if c1_bits.da2 { + content_info.push_str("[Sexually Suggestive Dialog] "); + } + + supported = true; } - // Allocate a string to hold the formatted private data - let mut str = String::with_capacity((ctx.cur_xds_payload_length * 3) as usize + 1); + if !c1_bits.a0 { + // MPA rating + let rating_labels = ["N/A", "G", "PG", "PG-13", "R", "NC-17", "X", "Not Rated"]; + let rating_idx = bits_to_number(c1_bits.r2, c1_bits.r1, c1_bits.r0); + rating_info = format!( + "ContentAdvisory: MPA Rating: {}", + rating_labels.get(rating_idx).unwrap_or(&"Unknown") + ); - // Only thing we can do with private data is dump it - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - write!(&mut str, "{:02X} ", byte).unwrap(); + supported = true; } - // Print the private data - xdsprint(sub, ctx, &str, format_args!("")); + if c1_bits.a0 && c1_bits.a1 && !c1_bits.da2 && !c2_bits.la3 { + // Canadian English rating + let rating_labels = [ + "Exempt", + "Children", + "Children eight years and older", + "General programming suitable for all audiences", + "Parental Guidance", + "Viewers 14 years and older", + "Adult Programming", + "[undefined]", + ]; + let rating_idx = bits_to_number(c2_bits.g2, c2_bits.g1, c2_bits.g0); + rating_info = format!( + "ContentAdvisory: Canadian English Rating: {}", + rating_labels.get(rating_idx).unwrap_or(&"Unknown") + ); - 1 -} + supported = true; + } -/// Rust equivalent for `xds_do_misc` function in C. Processes miscellaneous XDS data. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. -pub fn xds_do_misc(ctx: &CcxDecodersXdsContext) -> i64 { - let was_proc: i64; + if c1_bits.a0 && c1_bits.a1 && c1_bits.da2 && !c2_bits.la3 { + // Canadian French rating + let rating_labels = [ + "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]", + ]; + let rating_idx = bits_to_number(c2_bits.g2, c2_bits.g1, c2_bits.g0); + rating_info = format!( + "ContentAdvisory: Canadian French Rating: {}", + rating_labels.get(rating_idx).unwrap_or(&"Unknown") + ); - if ctx.cur_xds_payload.is_null() { - return CCX_EINVAL; + supported = true; } - match ctx.cur_xds_packet_type { - x if x as u8 == XDS_TYPE_TIME_OF_DAY => { - was_proc = 1; - if ctx.cur_xds_payload_length < 9 { - // We need at least 6 data bytes - return was_proc; - } + if !c1_bits.a1 && c1_bits.a0 { + // US TV - let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; // 6 bits - let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; // 5 bits - let date = unsafe { *ctx.cur_xds_payload.add(4) & 0x1f }; // 5 bits - let month = unsafe { *ctx.cur_xds_payload.add(5) & 0x0f }; // 4 bits - let reset_seconds = unsafe { (*ctx.cur_xds_payload.add(5) & 0x20) != 0 }; // Reset seconds - let day_of_week = unsafe { *ctx.cur_xds_payload.add(6) & 0x07 }; // 3 bits - let year = unsafe { (*ctx.cur_xds_payload.add(7) & 0x3f) as i64 + 1990 }; // Year offset - - info!( - "Time of day: (YYYY/MM/DD) {}/{:02}/{:02} (HH:MM) {:02}:{:02} DoW: {} Reset seconds: {}", - year, month, date, hour, min, day_of_week, reset_seconds - ); + xdsprint(sub, ctx, format_args!("{}", age_info)); + xdsprint(sub, ctx, format_args!("{}", content_info)); + if changed { + info!("{}", age_info); + info!("{}", content_info); } - x if x as u8 == XDS_TYPE_LOCAL_TIME_ZONE => { - was_proc = 1; - if ctx.cur_xds_payload_length < 5 { - // We need at least 2 data bytes - return was_proc; - } - - let dst = unsafe { (*ctx.cur_xds_payload.add(2) & 0x20) >> 5 }; // Daylight Saving Time - let hour = unsafe { *ctx.cur_xds_payload.add(2) & 0x1f }; // 5 bits + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", age_info); + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", content_info); + } - info!("Local Time Zone: {:02} DST: {}", hour, dst); - } - _ => { - was_proc = 0; + if !c1_bits.a0 || (c1_bits.a1 && !c1_bits.da2 && !c2_bits.la3) { + xdsprint(sub, ctx, format_args!("{}", rating_info)); + if changed { + info!("{}", rating_info); } + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "{}", rating_info); } - was_proc + if changed && !supported { + info!("XDS: Unsupported ContentAdvisory encoding, please submit sample."); + } } -/// Rust equivalent for `xds_do_current_and_future` function in C. Processes XDS data for current and future programs. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. -pub fn xds_do_current_and_future(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { - let mut was_proc: i64 = 0; +//---------------------------------------------------------------- - if ctx.cur_xds_payload.is_null() { - return CCX_EINVAL; - } +use std::fmt::Write; - match ctx.cur_xds_packet_type as u8 { +pub fn xds_do_current_and_future( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, +) -> Result { + let mut was_proc = false; + let mut str_buf = String::with_capacity(1024); + + match ctx.cur_xds_packet_type { XDS_TYPE_PIN_START_TIME => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 7 { - return was_proc; + return Ok(was_proc); } - let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; - let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; - let date = unsafe { *ctx.cur_xds_payload.add(4) & 0x1f }; - let month = unsafe { *ctx.cur_xds_payload.add(5) & 0x0f }; + let min = ctx.cur_xds_payload[2] & 0x3f; + let hour = ctx.cur_xds_payload[3] & 0x1f; + let date = ctx.cur_xds_payload[4] & 0x1f; + let month = ctx.cur_xds_payload[5] & 0x0f; - if ctx.current_xds_min != min as i64 - || ctx.current_xds_hour != hour as i64 - || ctx.current_xds_date != date as i64 - || ctx.current_xds_month != month as i64 + if ctx.current_xds_min != min.into() + || ctx.current_xds_hour != hour.into() + || ctx.current_xds_date != date.into() + || ctx.current_xds_month != month.into() { ctx.xds_start_time_shown = 0; - ctx.current_xds_min = min as i64; - ctx.current_xds_hour = hour as i64; - ctx.current_xds_date = date as i64; - ctx.current_xds_month = month as i64; + ctx.current_xds_min = min.into(); + ctx.current_xds_hour = hour.into(); + ctx.current_xds_date = date.into(); + ctx.current_xds_month = month.into(); } - info!( + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}", - if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 { + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT { "Current" } else { "Future" @@ -668,9 +442,9 @@ pub fn xds_do_current_and_future(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsC xdsprint( sub, ctx, - &format!( + format_args!( "PIN (Start Time): {} {:02}-{:02} {:02}:{:02}", - if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 { + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT { "Current" } else { "Future" @@ -680,166 +454,213 @@ pub fn xds_do_current_and_future(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsC hour, min ), - format_args!(""), ); - if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 - { + if ctx.xds_start_time_shown == 0 && ctx.cur_xds_packet_class == XDS_CLASS_CURRENT { info!("XDS: Program changed."); info!( "XDS program start time (DD/MM HH:MM) {:02}-{:02} {:02}:{:02}", date, month, hour, min ); + // GUI update would go here ctx.xds_start_time_shown = 1; } } XDS_TYPE_LENGTH_AND_CURRENT_TIME => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 5 { - return was_proc; + return Ok(was_proc); } - let min = unsafe { *ctx.cur_xds_payload.add(2) & 0x3f }; - let hour = unsafe { *ctx.cur_xds_payload.add(3) & 0x1f }; + let min = ctx.cur_xds_payload[2] & 0x3f; + let hour = ctx.cur_xds_payload[3] & 0x1f; + + if ctx.xds_program_length_shown == 0 { + info!("XDS: Program length (HH:MM): {:02}:{:02} ", hour, min); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS: Program length (HH:MM): {:02}:{:02} ", + hour, + min + ); + } - info!("XDS: Program length (HH:MM): {:02}:{:02}", hour, min); xdsprint( sub, ctx, - &format!("Program length (HH:MM): {:02}:{:02}", hour, min), - format_args!(""), + format_args!("Program length (HH:MM): {:02}:{:02} ", hour, min), ); if ctx.cur_xds_payload_length > 6 { - let el_min = unsafe { *ctx.cur_xds_payload.add(4) & 0x3f }; - let el_hour = unsafe { *ctx.cur_xds_payload.add(5) & 0x1f }; - info!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min); + let el_min = ctx.cur_xds_payload[4] & 0x3f; + let el_hour = ctx.cur_xds_payload[5] & 0x1f; + + 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 + ); + } xdsprint( sub, ctx, - &format!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min), - format_args!(""), + format_args!("Elapsed (HH:MM): {:02}:{:02}", el_hour, el_min), ); } if ctx.cur_xds_payload_length > 8 { - let el_sec = unsafe { *ctx.cur_xds_payload.add(6) & 0x3f }; - info!("Elapsed (SS): {:02}", el_sec); - xdsprint( - sub, - ctx, - &format!("Elapsed (SS): {:02}", el_sec), - format_args!(""), - ); + let el_sec = ctx.cur_xds_payload[6] & 0x3f; + if ctx.xds_program_length_shown == 0 { + debug!(msg_type = DebugMessageFlag::DECODER_XDS; ":{:02}", el_sec); + } + xdsprint(sub, ctx, format_args!("Elapsed (SS) :{:02}", el_sec)); } + + if ctx.xds_program_length_shown == 0 { + info!(""); + } else { + debug!(msg_type = DebugMessageFlag::DECODER_XDS; ""); + } + ctx.xds_program_length_shown = 1; } XDS_TYPE_PROGRAM_NAME => { - was_proc = 1; + was_proc = true; let mut xds_program_name = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - xds_program_name.push(byte as char); + for i in 2..ctx.cur_xds_payload_length - 1 { + xds_program_name.push(ctx.cur_xds_payload[i as usize] as char); } - info!("XDS Program name: {}", xds_program_name); - xdsprint( - sub, - ctx, - &format!("Program name: {}", xds_program_name), - format_args!(""), + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Program name: {}", + xds_program_name ); + xdsprint(sub, ctx, format_args!("Program name: {}", xds_program_name)); - if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT as i64 - && xds_program_name != String::from_utf8_lossy(&ctx.current_xds_program_name) + if ctx.cur_xds_packet_class == XDS_CLASS_CURRENT + && ctx.current_xds_program_name != xds_program_name { info!("XDS Notice: Program is now {}", xds_program_name); - unsafe { - ptr::write_bytes( - ctx.current_xds_program_name.as_mut_ptr(), - 0, - ctx.current_xds_program_name.len(), - ); - } - ctx.current_xds_program_name[..xds_program_name.len()] - .copy_from_slice(xds_program_name.as_bytes()); + ctx.current_xds_program_name = xds_program_name; + // GUI update would go here } } XDS_TYPE_PROGRAM_TYPE => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 5 { - return was_proc; + return Ok(was_proc); } if ctx.current_program_type_reported != 0 { - for i in 0..ctx.cur_xds_payload_length as usize { - if unsafe { *ctx.cur_xds_payload.add(i) } != ctx.current_xds_program_type[i] { + for i in 0..ctx.cur_xds_payload_length { + if ctx.cur_xds_payload[i as usize] + != ctx.current_xds_program_type.as_bytes()[i as usize] + { ctx.current_program_type_reported = 0; break; } } } - unsafe { - ptr::write_bytes( - ctx.current_xds_program_type.as_mut_ptr(), - 0, - ctx.current_xds_program_type.len(), - ); - } - for i in 0..ctx.cur_xds_payload_length as usize { - ctx.current_xds_program_type[i] = unsafe { *ctx.cur_xds_payload.add(i) }; + ctx.current_xds_program_type = String::from_utf8_lossy( + &ctx.cur_xds_payload[..ctx.cur_xds_payload_length as usize], + ) + .into_owned(); + + if ctx.current_program_type_reported == 0 { + info!("XDS Program Type: "); } - let mut program_type = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - if (0x20..0x7f).contains(&byte) { - program_type.push_str(XDS_PROGRAM_TYPES[(byte - 0x20) as usize]); + str_buf.clear(); + for i in 2..ctx.cur_xds_payload_length - 1 { + if ctx.cur_xds_payload[i as usize] == 0 { + continue; } - } - info!("XDS Program Type: {}", program_type); - xdsprint( - sub, - ctx, - &format!("Program type {}", program_type), - format_args!(""), - ); + if ctx.current_program_type_reported == 0 { + info!("[{:02X}-", ctx.cur_xds_payload[i as usize]); + } + if ctx.cur_xds_payload[i as usize] >= 0x20 && ctx.cur_xds_payload[i as usize] < 0x7F + { + let type_str = + XDS_PROGRAM_TYPES[(ctx.cur_xds_payload[i as usize] - 0x20) as usize]; + write!(str_buf, "[{}] ", type_str).unwrap(); + } + + if ctx.current_program_type_reported == 0 { + if ctx.cur_xds_payload[i as usize] >= 0x20 + && ctx.cur_xds_payload[i as usize] < 0x7F + { + info!( + "{}", + XDS_PROGRAM_TYPES[(ctx.cur_xds_payload[i as usize] - 0x20) as usize] + ); + } else { + info!("ILLEGAL VALUE"); + } + info!("] "); + } + } + + xdsprint(sub, ctx, format_args!("Program type {}", str_buf)); + if ctx.current_program_type_reported == 0 { + info!(""); + } ctx.current_program_type_reported = 1; } XDS_TYPE_CONTENT_ADVISORY => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 5 { - return was_proc; + return Ok(was_proc); } - xds_do_content_advisory(sub, ctx, unsafe { *ctx.cur_xds_payload.add(2) }, unsafe { - *ctx.cur_xds_payload.add(3) - }); + let xds_do_content_advisory_c1: u8 = ctx.cur_xds_payload[2]; + let xds_do_content_advisory_c2: u8 = ctx.cur_xds_payload[3]; + let mut state = XdsCopyState::new(); + xds_do_content_advisory( + sub, + Some(ctx), + xds_do_content_advisory_c1, + xds_do_content_advisory_c2, + &mut state, + ); + } + XDS_TYPE_AUDIO_SERVICES => { + was_proc = true; } XDS_TYPE_CGMS => { - was_proc = 1; + was_proc = true; + let mut state = XdsCopyState::new(); xds_do_copy_generation_management_system( sub, ctx, - unsafe { *ctx.cur_xds_payload.add(2) }, - unsafe { *ctx.cur_xds_payload.add(3) }, + ctx.cur_xds_payload[2], + ctx.cur_xds_payload[3], + &mut state, ); } XDS_TYPE_ASPECT_RATIO_INFO => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 5 { - return was_proc; + return Ok(was_proc); + } + if (ctx.cur_xds_payload[2] & 0x20) == 0 || (ctx.cur_xds_payload[3] & 0x20) == 0 { + return Ok(was_proc); } - let ar_start = (unsafe { *ctx.cur_xds_payload.add(2) } & 0x1f) as i64 + 22; - let ar_end = 262 - (unsafe { *ctx.cur_xds_payload.add(3) } & 0x1f) as i64; + let ar_start = (ctx.cur_xds_payload[2] & 0x1F) as u32 + 22; + let ar_end = 262 - (ctx.cur_xds_payload[3] & 0x1F) as u32; let active_picture_height = ar_end - ar_start; - let aspect_ratio = 320.0 / active_picture_height as f64; + 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; + if ar_start != ctx.current_ar_start as u32 { + ctx.current_ar_start = ar_start as i64; + ctx.current_ar_end = ar_end as i64; info!( "XDS Notice: Aspect ratio info, start line={}, end line={}", ar_start, ar_end @@ -848,819 +669,415 @@ pub fn xds_do_current_and_future(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsC "XDS Notice: Aspect ratio info, active picture height={}, ratio={}", active_picture_height, aspect_ratio ); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Notice: Aspect ratio info, start line={}, end line={}", + ar_start, + ar_end + ); + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Notice: Aspect ratio info, active picture height={}, ratio={}", + active_picture_height, + aspect_ratio + ); } } XDS_TYPE_PROGRAM_DESC_1..=XDS_TYPE_PROGRAM_DESC_8 => { - was_proc = 1; + was_proc = true; let mut xds_desc = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - xds_desc.push(byte as char); + for i in 2..ctx.cur_xds_payload_length - 1 { + xds_desc.push(ctx.cur_xds_payload[i as usize] as char); } if !xds_desc.is_empty() { - let line_num = ctx.cur_xds_packet_type as usize - XDS_TYPE_PROGRAM_DESC_1 as usize; - if xds_desc != String::from_utf8_lossy(&ctx.xds_program_description[line_num]) { + let line_num = (ctx.cur_xds_packet_type - XDS_TYPE_PROGRAM_DESC_1) as usize; + let changed = ctx.xds_program_description[line_num] != xds_desc; + + if changed { info!("XDS description line {}: {}", line_num, xds_desc); - unsafe { - ptr::write_bytes( - ctx.xds_program_description[line_num].as_mut_ptr(), - 0, - ctx.xds_program_description[line_num].len(), - ); - } - ctx.xds_program_description[line_num][..xds_desc.len()] - .copy_from_slice(xds_desc.as_bytes()); + ctx.xds_program_description[line_num] = xds_desc.clone(); + } else { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS description line {}: {}", + line_num, + xds_desc + ); } xdsprint( sub, ctx, - &format!("XDS description line {}: {}", line_num, xds_desc), - format_args!(""), + format_args!("XDS description line {}: {}", line_num, xds_desc), ); + // at end, to-do : GUI Update } } _ => {} } - was_proc + Ok(was_proc) } -/// Rust equivalent for `do_end_of_xds` function in C. Processes the end of an XDS packet and handles its contents. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. -pub fn do_end_of_xds( +pub const 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", +]; + +//---------------------------------------------------------------- + +pub fn xds_do_channel( sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext, - expected_checksum: i64, -) { - let mut cs: i64 = 0; - - if ctx.cur_xds_buffer_idx == -1 || ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].in_use == 0 - { - return; - } +) -> Result { + let mut was_proc = false; - ctx.cur_xds_packet_class = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].xds_class; - ctx.cur_xds_payload = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize] - .bytes - .as_mut_ptr(); - ctx.cur_xds_payload_length = ctx.xds_buffers[ctx.cur_xds_buffer_idx as usize].used_bytes; - ctx.cur_xds_packet_type = unsafe { *ctx.cur_xds_payload.add(1) as i64 }; - unsafe { - *ctx.cur_xds_payload.add(ctx.cur_xds_payload_length as usize) = 0x0F; - } - ctx.cur_xds_payload_length += 1; - - for i in 0..ctx.cur_xds_payload_length as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - cs = (cs + byte as i64) & 0x7F; - let c = byte & 0x7F; - info!( - "{:02X} - {} cs: {:02X}", - c, - if c >= 0x20 { c as char } else { '?' }, - cs - ); - } - cs = (128 - cs) & 0x7F; - - info!( - "End of XDS. Class={} ({}), size={} Checksum OK: {} Used buffers: {}", - ctx.cur_xds_packet_class, - XDS_CLASSES[ctx.cur_xds_packet_class as usize], - ctx.cur_xds_payload_length, - cs == expected_checksum, - how_many_used(ctx) - ); - - if cs != expected_checksum || ctx.cur_xds_payload_length < 3 { - info!( - "Expected checksum: {:02X} Calculated: {:02X}", - expected_checksum, cs - ); - clear_xds_buffer(ctx, ctx.cur_xds_buffer_idx); - return; - } - - let mut was_proc: i64 = 0; - if ctx.cur_xds_packet_type & 0x40 != 0 { - ctx.cur_xds_packet_class = XDS_CLASS_OUT_OF_BAND as i64; - } - - match ctx.cur_xds_packet_class { - x if x == XDS_CLASS_FUTURE as i64 => { - was_proc = 1; - } - x if x == XDS_CLASS_CURRENT as i64 => { - was_proc = xds_do_current_and_future(sub, ctx); - } - x if x == XDS_CLASS_CHANNEL as i64 => { - was_proc = xds_do_channel(sub, ctx); - } - x if x == XDS_CLASS_MISC as i64 => { - was_proc = xds_do_misc(ctx); - } - x if x == XDS_CLASS_PRIVATE as i64 => { - was_proc = xds_do_private_data(sub, ctx); - } - x if x == XDS_CLASS_OUT_OF_BAND as i64 => { - info!("Out-of-band data, ignored."); - was_proc = 1; - } - _ => {} - } - - if was_proc == 0 { - info!("Note: We found a currently unsupported XDS packet."); - hex_dump_with_start_idx( - DebugMessageFlag::DECODER_XDS, - unsafe { - std::slice::from_raw_parts(ctx.cur_xds_payload, ctx.cur_xds_payload_length as usize) - }, - false, - 0, - ); - } - - clear_xds_buffer(ctx, ctx.cur_xds_buffer_idx); -} - -/// Rust equivalent for `xds_do_channel` function in C. Processes XDS data for channel-related information. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `cur_xds_payload` is properly initialized and points to valid memory. -pub fn xds_do_channel(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext) -> i64 { - let mut was_proc: i64 = 0; - - if ctx.cur_xds_payload.is_null() { - return CCX_EINVAL; - } - - match ctx.cur_xds_packet_type as u8 { + match ctx.cur_xds_packet_type { XDS_TYPE_NETWORK_NAME => { - was_proc = 1; + was_proc = true; let mut xds_network_name = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - xds_network_name.push(byte as char); + for i in 2..ctx.cur_xds_payload_length - 1 { + xds_network_name.push(ctx.cur_xds_payload[i as usize] as char); } - info!("XDS Network name: {}", xds_network_name); - xdsprint( - sub, - ctx, - &format!("Network: {}", xds_network_name), - format_args!(""), + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Network name: {}", + xds_network_name ); + xdsprint(sub, ctx, format_args!("Network: {}", xds_network_name)); - if xds_network_name != String::from_utf8_lossy(&ctx.current_xds_network_name) { + if ctx.current_xds_network_name != xds_network_name { info!("XDS Notice: Network is now {}", xds_network_name); - unsafe { - ptr::write_bytes( - ctx.current_xds_network_name.as_mut_ptr(), - 0, - ctx.current_xds_network_name.len(), - ); - } - ctx.current_xds_network_name[..xds_network_name.len()] - .copy_from_slice(xds_network_name.as_bytes()); + ctx.current_xds_network_name = xds_network_name; } } XDS_TYPE_CALL_LETTERS_AND_CHANNEL => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length != 7 && ctx.cur_xds_payload_length != 9 { - return was_proc; + return Ok(was_proc); } let mut xds_call_letters = String::new(); - for i in 2..(ctx.cur_xds_payload_length - 1) as usize { - let byte = unsafe { *ctx.cur_xds_payload.add(i) }; - xds_call_letters.push(byte as char); + for i in 2..ctx.cur_xds_payload_length - 1 { + xds_call_letters.push(ctx.cur_xds_payload[i as usize] as char); } - info!("XDS Network call letters: {}", xds_call_letters); - xdsprint( - sub, - ctx, - &format!("Call Letters: {}", xds_call_letters), - format_args!(""), + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "XDS Network call letters: {}", + xds_call_letters ); + xdsprint(sub, ctx, format_args!("Call Letters: {}", xds_call_letters)); - if xds_call_letters != String::from_utf8_lossy(&ctx.current_xds_call_letters) { + if ctx.current_xds_call_letters != xds_call_letters { info!("XDS Notice: Network call letters now {}", xds_call_letters); - unsafe { - ptr::write_bytes( - ctx.current_xds_call_letters.as_mut_ptr(), - 0, - ctx.current_xds_call_letters.len(), - ); - } - ctx.current_xds_call_letters[..xds_call_letters.len()] - .copy_from_slice(xds_call_letters.as_bytes()); + ctx.current_xds_call_letters = xds_call_letters; + // GUI update would go here } } XDS_TYPE_TSID => { - was_proc = 1; + was_proc = true; if ctx.cur_xds_payload_length < 7 { - return was_proc; + return Ok(was_proc); } - let b1 = (unsafe { *ctx.cur_xds_payload.add(2) } & 0x10) as i64; - let b2 = (unsafe { *ctx.cur_xds_payload.add(3) } & 0x10) as i64; - let b3 = (unsafe { *ctx.cur_xds_payload.add(4) } & 0x10) as i64; - let b4 = (unsafe { *ctx.cur_xds_payload.add(5) } & 0x10) as i64; + let b1 = (ctx.cur_xds_payload[2] & 0x0F) as u32; + let b2 = (ctx.cur_xds_payload[3] & 0x0F) as u32; + let b3 = (ctx.cur_xds_payload[4] & 0x0F) as u32; + let b4 = (ctx.cur_xds_payload[5] & 0x0F) as u32; let tsid = (b4 << 12) | (b3 << 8) | (b2 << 4) | b1; if tsid != 0 { - xdsprint(sub, ctx, &format!("TSID: {}", tsid), format_args!("")); + xdsprint(sub, ctx, format_args!("TSID: {}", tsid)); } } _ => {} } - was_proc + Ok(was_proc) } -/// Rust equivalent for `xds_debug_test` function in C. Tests the XDS processing logic with sample data. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory -pub fn xds_debug_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { - process_xds_bytes(ctx, 0x05, 0x02); - process_xds_bytes(ctx, 0x20, 0x20); - do_end_of_xds(sub, ctx, 0x2a); -} - -/// Rust equivalent for `xds_cea608_test` function in C. Tests the XDS processing logic with CEA-608 sample data. -/// -/// # Safety -/// -/// The `ctx` pointer must be valid and non-null. Ensure that `sub` is properly initialized and points to valid memory. -pub fn xds_cea608_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { - // This test is the sample data that comes in CEA-608. It sets the program name - // to be "Star Trek". The checksum is 0x1d and the validation must succeed. - process_xds_bytes(ctx, 0x01, 0x03); - process_xds_bytes(ctx, 0x53, 0x74); - process_xds_bytes(ctx, 0x61, 0x72); - process_xds_bytes(ctx, 0x20, 0x54); - process_xds_bytes(ctx, 0x72, 0x65); - process_xds_bytes(ctx, 0x02, 0x03); - process_xds_bytes(ctx, 0x02, 0x03); - process_xds_bytes(ctx, 0x6b, 0x00); - do_end_of_xds(sub, ctx, 0x1d); -} - -#[cfg(test)] -mod tests { - use super::*; - use std::ffi::CStr; - - #[test] - fn test_ccx_decoders_xds_init_library_initialization() { - let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context - let xds_write_to_file = 1; - - let ctx = ccx_decoders_xds_init_library(timing, xds_write_to_file); - - // Check initial values - assert_eq!(ctx.current_xds_min, -1); - assert_eq!(ctx.current_xds_hour, -1); - assert_eq!(ctx.current_xds_date, -1); - assert_eq!(ctx.current_xds_month, -1); - assert_eq!(ctx.current_program_type_reported, 0); - assert_eq!(ctx.xds_start_time_shown, 0); - assert_eq!(ctx.xds_program_length_shown, 0); - assert_eq!(ctx.cur_xds_buffer_idx, -1); - assert_eq!(ctx.cur_xds_packet_class, -1); - assert_eq!(ctx.cur_xds_payload, std::ptr::null_mut()); - assert_eq!(ctx.cur_xds_payload_length, 0); - assert_eq!(ctx.cur_xds_packet_type, 0); - assert_eq!(ctx.xds_write_to_file, xds_write_to_file); - - // Check that all buffers are initialized correctly - for buffer in ctx.xds_buffers.iter() { - assert_eq!(buffer.in_use, 0); - assert_eq!(buffer.xds_class, -1); - assert_eq!(buffer.xds_type, -1); - assert_eq!(buffer.used_bytes, 0); - assert!(buffer.bytes.iter().all(|&b| b == 0)); - } - - // Check that all program descriptions are initialized to zero - for description in ctx.xds_program_description.iter() { - assert!(description.iter().all(|&b| b == 0)); - } +//---------------------------------------------------------------- - // Check that network name, program name, call letters, and program type are initialized to zero - assert!(ctx.current_xds_network_name.iter().all(|&b| b == 0)); - assert!(ctx.current_xds_program_name.iter().all(|&b| b == 0)); - assert!(ctx.current_xds_call_letters.iter().all(|&b| b == 0)); - assert!(ctx.current_xds_program_type.iter().all(|&b| b == 0)); - } - - #[test] - fn test_ccx_decoders_xds_init_library_with_different_write_flag() { - let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context - let xds_write_to_file = 0; - - let ctx = ccx_decoders_xds_init_library(timing, xds_write_to_file); - - // Check that the `xds_write_to_file` flag is set correctly - assert_eq!(ctx.xds_write_to_file, xds_write_to_file); +pub fn xds_do_private_data( + sub: &mut CcSubtitle, + ctx: &mut CcxDecodersXdsContext, +) -> Result { + if ctx.cur_xds_payload_length < 3 { + return Ok(false); } - #[test] - fn test_ccx_decoders_xds_init_library_timing_context() { - let timing = TimingContext::new(); // Assuming `TimingContext::new()` initializes a valid context - let xds_write_to_file = 1; + let mut hex_dump = String::with_capacity((ctx.cur_xds_payload_length as usize) * 3); - let ctx = ccx_decoders_xds_init_library(timing.clone(), xds_write_to_file); - - // Check that the timing context is correctly assigned - assert_eq!(ctx.timing, timing); + for i in 2..ctx.cur_xds_payload_length - 1 { + write!(hex_dump, "{:02X} ", ctx.cur_xds_payload[i as usize]) + .map_err(|_| "Failed to format hex string")?; } - #[test] - fn test_write_xds_string_success() { - let mut sub = CcSubtitle { - data: std::ptr::null_mut(), - datatype: SubDataType::Generic, - nb_data: 0, - subtype: SubType::Cc608, - enc_type: CcxEncodingType::Utf8, - start_time: 0, - end_time: 0, - flags: 0, - lang_index: 0, - got_output: false, - mode: [0; 5], - info: [0; 4], - time_out: 0, - next: std::ptr::null_mut(), - prev: std::ptr::null_mut(), - }; + xdsprint(sub, ctx, format_args!("{}", hex_dump)); + Ok(true) +} - let mut ctx = CcxDecodersXdsContext { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - 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 { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing: TimingContext::new(), - current_ar_start: 0, - current_ar_end: 0, - xds_write_to_file: 1, - }; +//---------------------------------------------------------------- - let xds_string = b"Test XDS String"; - let result = write_xds_string(&mut sub, &mut ctx, xds_string.as_ptr(), xds_string.len()); +pub fn xds_do_misc(ctx: &mut CcxDecodersXdsContext) -> Result { + let mut was_proc = false; - assert_eq!(result, 0); - assert_eq!(sub.nb_data, 1); - assert!(sub.got_output); + match ctx.cur_xds_packet_type { + XDS_TYPE_TIME_OF_DAY => { + was_proc = true; + if ctx.cur_xds_payload_length < 9 { + return Ok(was_proc); + } - unsafe { - let data_ptr = sub.data as *mut Eia608Screen; - let first_screen = &*data_ptr; - assert_eq!(first_screen.xds_len, xds_string.len()); - assert_eq!( - std::slice::from_raw_parts(first_screen.xds_str, xds_string.len()), - xds_string + let min = ctx.cur_xds_payload[2] & 0x3f; + let hour = ctx.cur_xds_payload[3] & 0x1f; + let date = ctx.cur_xds_payload[4] & 0x1f; + let month = ctx.cur_xds_payload[5] & 0x0f; + let reset_seconds = (ctx.cur_xds_payload[5] & 0x20) != 0; + let day_of_week = ctx.cur_xds_payload[6] & 0x07; + let year = ((ctx.cur_xds_payload[7] as u16) & 0x3f) + 1990; + + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Time of day: (YYYY/MM/DD) {:04}/{:02}/{:02} (HH:MM) {:02}:{:02} DoW: {} Reset seconds: {}", + year, month, date, hour, min, day_of_week, reset_seconds ); } - } - - #[test] - fn test_xdsprint_no_write_flag() { - let mut sub = CcSubtitle { - data: std::ptr::null_mut(), - datatype: SubDataType::Generic, - nb_data: 0, - subtype: SubType::Cc608, - enc_type: CcxEncodingType::Utf8, - start_time: 0, - end_time: 0, - flags: 0, - lang_index: 0, - got_output: false, - mode: [0; 5], - info: [0; 4], - time_out: 0, - next: std::ptr::null_mut(), - prev: std::ptr::null_mut(), - }; - - let mut ctx = CcxDecodersXdsContext { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - 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 { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing: TimingContext::new(), - current_ar_start: 0, - current_ar_end: 0, - xds_write_to_file: 0, // Writing is disabled - }; + XDS_TYPE_LOCAL_TIME_ZONE => { + was_proc = true; + if ctx.cur_xds_payload_length < 5 { + return Ok(was_proc); + } - let message = "This message should not be written"; - xdsprint(&mut sub, &mut ctx, "", format_args!("{}", message)); + let dst = (ctx.cur_xds_payload[2] & 0x20) != 0; + let hour = ctx.cur_xds_payload[2] & 0x1f; - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Local Time Zone: {:02} DST: {}", + hour, dst + ); + } + _ => {} } - #[test] - fn test_clear_xds_buffer_success() { - let mut ctx = CcxDecodersXdsContext { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - 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 { - in_use: 1, - xds_class: 2, - xds_type: 3, - bytes: [0xFF; NUM_BYTES_PER_PACKET as usize], - used_bytes: 10, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing: TimingContext::new(), - current_ar_start: 0, - current_ar_end: 0, - xds_write_to_file: 1, - }; - - let buffer_index = 0; - - // Call the function to clear the buffer - clear_xds_buffer(&mut ctx, buffer_index); - - // Verify that the buffer was cleared - let buffer = &ctx.xds_buffers[buffer_index as usize]; - assert_eq!(buffer.in_use, 0); - assert_eq!(buffer.xds_class, -1); - assert_eq!(buffer.xds_type, -1); - assert_eq!(buffer.used_bytes, 0); - assert!(buffer.bytes.iter().all(|&b| b == 0)); - } + Ok(was_proc) +} - #[test] - fn test_clear_xds_buffer_partial_clear() { - let mut ctx = CcxDecodersXdsContext { - current_xds_min: -1, - current_xds_hour: -1, - current_xds_date: -1, - current_xds_month: -1, - 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 { - in_use: 1, - xds_class: 2, - xds_type: 3, - bytes: [0xFF; NUM_BYTES_PER_PACKET as usize], - used_bytes: 10, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), - cur_xds_payload_length: 0, - cur_xds_packet_type: 0, - timing: TimingContext::new(), - current_ar_start: 0, - current_ar_end: 0, - xds_write_to_file: 1, - }; +//---------------------------------------------------------------- - let buffer_index = 1; - - // Call the function to clear the buffer - clear_xds_buffer(&mut ctx, buffer_index); - - // Verify that only the specified buffer was cleared - let cleared_buffer = &ctx.xds_buffers[buffer_index as usize]; - assert_eq!(cleared_buffer.in_use, 0); - assert_eq!(cleared_buffer.xds_class, -1); - assert_eq!(cleared_buffer.xds_type, -1); - assert_eq!(cleared_buffer.used_bytes, 0); - assert!(cleared_buffer.bytes.iter().all(|&b| b == 0)); - - // Verify that other buffers remain unchanged - let untouched_buffer = &ctx.xds_buffers[0]; - assert_eq!(untouched_buffer.in_use, 1); - assert_eq!(untouched_buffer.xds_class, 2); - assert_eq!(untouched_buffer.xds_type, 3); - assert_eq!(untouched_buffer.used_bytes, 10); - assert!(untouched_buffer.bytes.iter().all(|&b| b == 0xFF)); +pub fn do_end_of_xds(sub: &mut CcSubtitle, ctx: &mut CcxDecodersXdsContext, expected_checksum: u8) { + if ctx.cur_xds_buffer_idx.is_none() { + return; } + let buffer_idx = ctx.cur_xds_buffer_idx.unwrap(); - #[test] - fn test_how_many_used_all_unused() { - let ctx = CcxDecodersXdsContext { - xds_buffers: [XdsBuffer { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - ..Default::default() - }; - - let used_count = how_many_used(&ctx); - assert_eq!(used_count, 0, "Expected no buffers to be in use"); + if buffer_idx < 0 + || buffer_idx as usize >= ctx.xds_buffers.len() + || !ctx.xds_buffers[buffer_idx as usize].in_use + { + return; } - #[test] - fn test_how_many_used_some_used() { - let mut ctx = CcxDecodersXdsContext { - xds_buffers: [XdsBuffer { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - ..Default::default() - }; + let buffer = &ctx.xds_buffers[buffer_idx as usize]; + ctx.cur_xds_packet_class = buffer.xds_class; + ctx.cur_xds_payload = buffer.bytes.iter().map(|&x| x as u8).collect(); + ctx.cur_xds_payload_length = buffer.used_bytes; + ctx.cur_xds_packet_type = buffer.bytes[1]; - // Mark some buffers as in use - ctx.xds_buffers[0].in_use = 1; - ctx.xds_buffers[2].in_use = 1; - ctx.xds_buffers[4].in_use = 1; - - let used_count = how_many_used(&ctx); - assert_eq!(used_count, 3, "Expected 3 buffers to be in use"); - } - - #[test] - fn test_how_many_used_all_used() { - let ctx = CcxDecodersXdsContext { - xds_buffers: [XdsBuffer { - in_use: 1, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - ..Default::default() - }; + ctx.cur_xds_payload.push(0x0F); + ctx.cur_xds_payload_length += 1; - let used_count = how_many_used(&ctx); - assert_eq!( - used_count, NUM_XDS_BUFFERS as i64, - "Expected all buffers to be in use" + let mut cs = 0; + for i in 0..ctx.cur_xds_payload_length as usize { + let byte = ctx.cur_xds_payload[i]; + cs = (cs + byte) & 0x7f; + let c = byte & 0x7F; + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "{:02X} - {} cs: {:02X}", + c, + if c >= 0x20 { c as char } else { '?' }, + cs ); } + cs = (128 - cs) & 0x7F; - #[test] - fn test_xds_do_copy_generation_management_system_no_write_flag() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.xds_write_to_file = 0; // Disable writing XDS data - - let c1 = 0x50; // CGMS: Copy permitted (no restrictions) - let c2 = 0x40; // APS: No APS, RCD: 0 - - xds_do_copy_generation_management_system(&mut sub, &mut ctx, c1, c2); - - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } - - #[test] - fn test_xds_do_copy_generation_management_system_invalid_data() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.xds_write_to_file = 1; // Enable writing XDS data - - let c1 = 0x10; // Invalid CGMS data (c1_6 is 0) - let c2 = 0x40; // APS: No APS, RCD: 0 - - xds_do_copy_generation_management_system(&mut sub, &mut ctx, c1, c2); - - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } - - #[test] - fn test_xds_do_content_advisory_no_write_flag() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.xds_write_to_file = 0; // Disable writing XDS data - - let c1 = 0x58; // US TV parental guidelines: TV-14 (Parents Strongly Cautioned) - let c2 = 0x10; // Content: Violence - - xds_do_content_advisory(&mut sub, &mut ctx, c1, c2); - - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } - - #[test] - fn test_xds_do_content_advisory_invalid_data() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.xds_write_to_file = 1; // Enable writing XDS data - - let c1 = 0x10; // Invalid data (c1_6 is 0) - let c2 = 0x40; // No additional content - - xds_do_content_advisory(&mut sub, &mut ctx, c1, c2); - - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } - - #[test] - fn test_xds_do_misc_invalid_payload() { - let mut ctx = CcxDecodersXdsContext::default(); - ctx.cur_xds_packet_type = XDS_TYPE_TIME_OF_DAY as i64; - ctx.cur_xds_payload_length = 4; // Invalid length (less than required) - - // Simulate an invalid payload - let payload: [u8; 4] = [0x00, 0x00, 0x3C, 0x12]; - ctx.cur_xds_payload = payload.as_ptr() as *mut u8; - - let result = xds_do_misc(&ctx); - - // Verify the function did not process the payload - assert_eq!(result, 1); // Still returns 1 but does not process - } - - #[test] - fn test_xds_do_misc_null_payload() { - let mut ctx = CcxDecodersXdsContext::default(); - ctx.cur_xds_packet_type = XDS_TYPE_TIME_OF_DAY as i64; - ctx.cur_xds_payload = std::ptr::null_mut(); // Null payload - - let result = xds_do_misc(&ctx); - - // Verify the function returned an error - assert_eq!(result, CCX_EINVAL); - } - - #[test] - fn test_xds_do_misc_unsupported_packet_type() { - let mut ctx = CcxDecodersXdsContext::default(); - ctx.cur_xds_packet_type = 0x99; // Unsupported packet type - ctx.cur_xds_payload_length = 5; - - // Simulate a valid payload - let payload: [u8; 5] = [0x00, 0x00, 0x25, 0x00, 0x00]; - ctx.cur_xds_payload = payload.as_ptr() as *mut u8; - - let result = xds_do_misc(&ctx); - - // Verify the function did not process the payload - assert_eq!(result, 0); - } - - #[test] - fn test_xds_do_current_and_future_invalid_payload() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.cur_xds_packet_type = XDS_TYPE_PIN_START_TIME as i64; - ctx.cur_xds_payload_length = 4; // Invalid length (less than required) - - // Simulate an invalid payload - let payload: [u8; 4] = [0x00, 0x00, 0x15, 0x10]; - ctx.cur_xds_payload = payload.as_ptr() as *mut u8; - - let result = xds_do_current_and_future(&mut sub, &mut ctx); + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "End of XDS. Class={} ({}), size={} Checksum OK: {} Used buffers: {}", + ctx.cur_xds_packet_class, + match ctx.cur_xds_packet_class { + 0 => "Current", + 1 => "Future", + 2 => "Channel", + 3 => "Misc", + 4 => "Private", + 5 => "Out-of-band", + _ => "Unknown", + }, + ctx.cur_xds_payload_length, + cs == expected_checksum, + ctx.how_many_used() + ); - // Verify the function did not process the payload - assert_eq!(result, 1); // Still returns 1 but does not process - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); + if cs != expected_checksum || ctx.cur_xds_payload_length < 3 { + debug!( + msg_type = DebugMessageFlag::DECODER_XDS; + "Expected checksum: {:02X} Calculated: {:02X}", + expected_checksum, + cs + ); + ctx.clear_xds_buffer(buffer_idx as usize) + .unwrap_or_default(); + return; } - #[test] - fn test_do_end_of_xds_empty_buffer() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - - // Simulate an empty XDS buffer - ctx.cur_xds_buffer_idx = 0; - ctx.xds_buffers[0].in_use = 0; - - let expected_checksum = 0x1D; // Example checksum - do_end_of_xds(&mut sub, &mut ctx, expected_checksum); - - // Verify that nothing was processed - assert_eq!(ctx.xds_buffers[0].in_use, 0); - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); + if (ctx.cur_xds_packet_type & 0x40) != 0 { + ctx.cur_xds_packet_class = XDS_CLASS_OUT_OF_BAND; } - #[test] - fn test_xds_do_channel_invalid_payload() { - let mut sub = CcSubtitle::default(); - let mut ctx = CcxDecodersXdsContext::default(); - ctx.cur_xds_packet_type = XDS_TYPE_CALL_LETTERS_AND_CHANNEL as i64; - ctx.cur_xds_payload_length = 4; // Invalid length (less than required) - - // Simulate an invalid payload - let payload: [u8; 4] = [0x00, 0x00, b'W', b'A']; - ctx.cur_xds_payload = payload.as_ptr() as *mut u8; - - let result = xds_do_channel(&mut sub, &mut ctx); + let was_proc = match ctx.cur_xds_packet_class { + XDS_CLASS_FUTURE + if !matches!(DebugMessageFlag::DECODER_XDS, DebugMessageFlag::DECODER_XDS) => + { + true + } + XDS_CLASS_FUTURE | XDS_CLASS_CURRENT => { + xds_do_current_and_future(sub, ctx).unwrap_or(false) + } + XDS_CLASS_CHANNEL => xds_do_channel(sub, ctx).unwrap_or(false), + XDS_CLASS_MISC => xds_do_misc(ctx).unwrap_or(false), + XDS_CLASS_PRIVATE => xds_do_private_data(sub, ctx).unwrap_or(false), + XDS_CLASS_OUT_OF_BAND => { + debug!(msg_type = DebugMessageFlag::DECODER_XDS; "Out-of-band data, ignored."); + true + } + _ => false, + }; - // Verify the function did not process the payload - assert_eq!(result, 1); // Still returns 1 but does not process - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); + if !was_proc { + info!("Note: We found a currently unsupported XDS packet."); + //to-do : how to dump? } - #[test] - fn test_xds_debug_test_empty_context() { - let mut ctx = CcxDecodersXdsContext::default(); - let mut sub = CcSubtitle::default(); - - // Call the debug test function without any valid data - do_end_of_xds(&mut sub, &mut ctx, 0x2a); - - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } + ctx.clear_xds_buffer(buffer_idx as usize) + .unwrap_or_default(); +} - #[test] - fn test_xds_cea608_test_empty_context() { - let mut ctx = CcxDecodersXdsContext::default(); - let mut sub = CcSubtitle::default(); +//---------------------------------------------------------------- - // Call the CEA-608 test function without any valid data - do_end_of_xds(&mut sub, &mut ctx, 0x1d); +pub fn xds_debug_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + process_xds_bytes(ctx, 0x05, 0x02); + process_xds_bytes(ctx, 0x20, 0x20); + do_end_of_xds(sub, ctx, 0x2a); +} - // Verify that the subtitle structure was not updated - assert_eq!(sub.nb_data, 0); - assert!(!sub.got_output); - } +pub fn xds_cea608_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { + process_xds_bytes(ctx, 0x01, 0x03); + process_xds_bytes(ctx, 0x53, 0x74); + process_xds_bytes(ctx, 0x61, 0x72); + process_xds_bytes(ctx, 0x20, 0x54); + process_xds_bytes(ctx, 0x72, 0x65); + process_xds_bytes(ctx, 0x02, 0x03); + process_xds_bytes(ctx, 0x02, 0x03); + process_xds_bytes(ctx, 0x6b, 0x00); + do_end_of_xds(sub, ctx, 0x1d); } diff --git a/src/rust/lib_ccxr/src/decoder_xds/mod.rs b/src/rust/lib_ccxr/src/decoder_xds/mod.rs index 1d240449a..eae0cb96c 100644 --- a/src/rust/lib_ccxr/src/decoder_xds/mod.rs +++ b/src/rust/lib_ccxr/src/decoder_xds/mod.rs @@ -1,2 +1,3 @@ +pub mod exit_codes; pub mod functions_xds; pub mod structs_xds; diff --git a/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs b/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs index b8aba215f..162c52390 100644 --- a/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs +++ b/src/rust/lib_ccxr/src/decoder_xds/structs_xds.rs @@ -1,371 +1,102 @@ -use crate::time::timing::*; +use crate::time::timing::TimingContext; -// Time at which we switched to XDS mode, -1 means it hasn't happened yet -pub const TS_START_OF_XDS: i64 = -1; +use crate::decoder_xds::exit_codes::*; -// Exit codes -pub const EXIT_OK: i64 = 0; -pub const EXIT_NO_INPUT_FILES: i64 = 2; -pub const EXIT_TOO_MANY_INPUT_FILES: i64 = 3; -pub const EXIT_INCOMPATIBLE_PARAMETERS: i64 = 4; -pub const EXIT_UNABLE_TO_DETERMINE_FILE_SIZE: i64 = 6; -pub const EXIT_MALFORMED_PARAMETER: i64 = 7; -pub const EXIT_READ_ERROR: i64 = 8; -pub const EXIT_NO_CAPTIONS: i64 = 10; -pub const EXIT_WITH_HELP: i64 = 11; -pub const EXIT_NOT_CLASSIFIED: i64 = 300; -pub const EXIT_ERROR_IN_CAPITALIZATION_FILE: i64 = 501; -pub const EXIT_BUFFER_FULL: i64 = 502; -pub const EXIT_MISSING_ASF_HEADER: i64 = 1001; -pub const EXIT_MISSING_RCWT_HEADER: i64 = 1002; - -// Common exit codes -pub const CCX_COMMON_EXIT_FILE_CREATION_FAILED: i64 = 5; -pub const CCX_COMMON_EXIT_UNSUPPORTED: i64 = 9; -pub const EXIT_NOT_ENOUGH_MEMORY: i64 = 500; -pub const CCX_COMMON_EXIT_BUG_BUG: i64 = 1000; - -// Status codes -pub const CCX_OK: i64 = 0; -pub const CCX_FALSE: i64 = 0; -pub const CCX_TRUE: i64 = 1; -pub const CCX_EAGAIN: i64 = -100; -pub const CCX_EOF: i64 = -101; -pub const CCX_EINVAL: i64 = -102; -pub const CCX_ENOSUPP: i64 = -103; -pub const CCX_ENOMEM: i64 = -104; - -// Define max width in characters/columns on the screen -pub const CCX_DECODER_608_SCREEN_ROWS: usize = 15; -pub const CCX_DECODER_608_SCREEN_WIDTH: usize = 32; - -pub const NUM_BYTES_PER_PACKET: i64 = 35; // Class + type (repeated for convenience) + data + zero -pub const NUM_XDS_BUFFERS: i64 = 35; // CEA recommends no more than one level of interleaving. Play it safe - -// Enums for format, color codes, font bits, and modes -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum CcxEia608Format { - SformatCcScreen, - SformatCcLine, - SformatXds, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum CcxDecoder608ColorCode { - ColWhite = 0, - ColGreen = 1, - ColBlue = 2, - ColCyan = 3, - ColRed = 4, - ColYellow = 5, - ColMagenta = 6, - ColUserDefined = 7, - ColBlack = 8, - ColTransparent = 9, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum FontBits { - FontRegular = 0, - FontItalics = 1, - FontUnderlined = 2, - FontUnderlinedItalics = 3, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum CcModes { - ModePopOn = 0, - ModeRollUp2 = 1, - ModeRollUp3 = 2, - ModeRollUp4 = 3, - ModeText = 4, - ModePaintOn = 5, - ModeFakeRollUp1 = 100, // Fake modes to emulate stuff -} - -// The `Eia608Screen` structure -#[derive(Debug)] -pub struct Eia608Screen { - pub format: CcxEia608Format, // Format of data inside this structure - pub characters: [[u8; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Characters - pub colors: - [[CcxDecoder608ColorCode; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Colors - pub fonts: [[FontBits; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Fonts - pub row_used: [i64; CCX_DECODER_608_SCREEN_ROWS], // Any data in row? - pub empty: i64, // Buffer completely empty? - pub start_time: i64, // Start time of this CC buffer - pub end_time: i64, // End time of this CC buffer - pub mode: CcModes, // Mode - pub channel: i64, // Currently selected channel - pub my_field: i64, // Used for sanity checks - pub xds_str: *const u8, // Pointer to XDS string - pub xds_len: usize, // Length of XDS string - pub cur_xds_packet_class: i64, // Class of XDS string -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum SubDataType { - #[default] - Generic = 0, - Dvb = 1, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum SubType { - #[default] - Bitmap, - Cc608, - Text, - Raw, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum CcxEncodingType { - #[default] - Unicode = 0, - Latin1 = 1, - Utf8 = 2, - Ascii = 3, +#[derive(Clone)] +pub struct XdsBuffer { + pub in_use: bool, + pub xds_class: i64, + pub xds_type: i64, + pub bytes: Vec, // of size NUM_BYTES_PER_PACKET + pub used_bytes: i64, } -#[derive(Debug)] -pub struct CcSubtitle { - /** - * A generic data which contains data according to the decoder. - * @warn Decoder can't output multiple types of data. - */ - pub data: *mut std::ffi::c_void, // Pointer to generic data - pub datatype: SubDataType, // Data type (e.g., Generic, DVB) - - /** Number of data */ - pub nb_data: u32, - - /** Type of subtitle */ - pub subtype: SubType, - - /** Encoding type of text, ignored for bitmap or cc_screen subtypes */ - pub enc_type: CcxEncodingType, - - /* Set only when all the data is to be displayed at the same time. - * Unit of time is milliseconds. - */ - pub start_time: i64, - pub end_time: i64, - - /* Flags */ - pub flags: i32, - - /* Index of language table */ - pub lang_index: i32, - - /** Flag to tell that the decoder has given output */ - pub got_output: bool, - - pub mode: [u8; 5], // Mode as a fixed-size array of 5 bytes - pub info: [u8; 4], // Info as a fixed-size array of 4 bytes - - /** Used for DVB end time in milliseconds */ - pub time_out: i32, - - pub next: *mut CcSubtitle, // Pointer to the next subtitle - pub prev: *mut CcSubtitle, // Pointer to the previous subtitle +impl XdsBuffer { + pub fn clear(&mut self) { + self.in_use = false; + self.xds_class = -1; + self.xds_type = -1; + self.bytes = vec![0; NUM_BYTES_PER_PACKET]; + self.used_bytes = 0; + } } -// XDS classes -pub const XDS_CLASSES: [&str; 8] = [ - "Current", - "Future", - "Channel", - "Miscellaneous", - "Public service", - "Reserved", - "Private data", - "End", -]; - -// XDS program types -pub const 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", -]; - -// XDS class constants -pub const XDS_CLASS_CURRENT: u8 = 0; -pub const XDS_CLASS_FUTURE: u8 = 1; -pub const XDS_CLASS_CHANNEL: u8 = 2; -pub const XDS_CLASS_MISC: u8 = 3; -pub const XDS_CLASS_PUBLIC: u8 = 4; -pub const XDS_CLASS_RESERVED: u8 = 5; -pub const XDS_CLASS_PRIVATE: u8 = 6; -pub const XDS_CLASS_END: u8 = 7; -pub const XDS_CLASS_OUT_OF_BAND: u8 = 0x40; // Not a real class, a marker for packets for out-of-band data - -// Types for the classes current and future -pub const XDS_TYPE_PIN_START_TIME: u8 = 1; -pub const XDS_TYPE_LENGTH_AND_CURRENT_TIME: u8 = 2; -pub const XDS_TYPE_PROGRAM_NAME: u8 = 3; -pub const XDS_TYPE_PROGRAM_TYPE: u8 = 4; -pub const XDS_TYPE_CONTENT_ADVISORY: u8 = 5; -pub const XDS_TYPE_AUDIO_SERVICES: u8 = 6; -pub const XDS_TYPE_CGMS: u8 = 8; // Copy Generation Management System -pub const XDS_TYPE_ASPECT_RATIO_INFO: u8 = 9; // Appears in CEA-608-B but in E it's been removed as is "reserved" -pub const XDS_TYPE_PROGRAM_DESC_1: u8 = 0x10; -pub const XDS_TYPE_PROGRAM_DESC_2: u8 = 0x11; -pub const XDS_TYPE_PROGRAM_DESC_3: u8 = 0x12; -pub const XDS_TYPE_PROGRAM_DESC_4: u8 = 0x13; -pub const XDS_TYPE_PROGRAM_DESC_5: u8 = 0x14; -pub const XDS_TYPE_PROGRAM_DESC_6: u8 = 0x15; -pub const XDS_TYPE_PROGRAM_DESC_7: u8 = 0x16; -pub const XDS_TYPE_PROGRAM_DESC_8: u8 = 0x17; - -// Types for the class channel -pub const XDS_TYPE_NETWORK_NAME: u8 = 1; -pub const XDS_TYPE_CALL_LETTERS_AND_CHANNEL: u8 = 2; -pub const XDS_TYPE_TSID: u8 = 4; // Transmission Signal Identifier - -// Types for miscellaneous packets -pub const XDS_TYPE_TIME_OF_DAY: u8 = 1; -pub const XDS_TYPE_LOCAL_TIME_ZONE: u8 = 4; -pub const XDS_TYPE_OUT_OF_BAND_CHANNEL_NUMBER: u8 = 0x40; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct XdsBuffer { - pub in_use: i64, // Whether the buffer is in use - pub xds_class: i64, // XDS class (e.g., XDS_CLASS_CURRENT, etc.) - pub xds_type: i64, // XDS type (e.g., XDS_TYPE_PROGRAM_NAME, etc.) - pub bytes: [u8; NUM_BYTES_PER_PACKET as usize], // Data bytes (size defined by NUM_BYTES_PER_PACKET) - pub used_bytes: i64, // Number of bytes used in the buffer +impl Default for XdsBuffer { + fn default() -> Self { + XdsBuffer { + in_use: false, + xds_class: -1, + xds_type: -1, + bytes: vec![0; NUM_BYTES_PER_PACKET], + used_bytes: 0, + } + } } #[repr(C)] pub struct CcxDecodersXdsContext { - // Program Identification Number (Start Time) for current program pub current_xds_min: i64, pub current_xds_hour: i64, pub current_xds_date: i64, pub current_xds_month: i64, - pub current_program_type_reported: i64, // No. + pub current_program_type_reported: i64, pub xds_start_time_shown: i64, pub xds_program_length_shown: i64, - pub xds_program_description: [[u8; 33]; 8], // 8 strings of 33 bytes each + pub xds_program_description: Vec, // 8 string of 33 bytes - pub current_xds_network_name: [u8; 33], // String of 33 bytes - pub current_xds_program_name: [u8; 33], // String of 33 bytes - pub current_xds_call_letters: [u8; 7], // String of 7 bytes - pub current_xds_program_type: [u8; 33], // String of 33 bytes + pub current_xds_network_name: String, // 33 bytes + pub current_xds_program_name: String, // 33 bytes + pub current_xds_call_letters: String, // 7 bytes + pub current_xds_program_type: String, // 33 bytes - pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XdsBuffer - pub cur_xds_buffer_idx: i64, + pub xds_buffers: Vec, // of size NUM_BYTES_PER_PACKET + pub cur_xds_buffer_idx: Option, pub cur_xds_packet_class: i64, - pub cur_xds_payload: *mut u8, // Pointer to payload + pub cur_xds_payload: Vec, pub cur_xds_payload_length: i64, pub cur_xds_packet_type: i64, - pub timing: TimingContext, // Replacing ccx_common_timing_ctx with TimingContext + pub timing: TimingContext, // use TimingContext as ccx_common_timing_ctx pub current_ar_start: i64, pub current_ar_end: i64, - pub xds_write_to_file: i64, // Set to 1 if XDS data is to be written to file + pub xds_write_to_file: bool, // originally i64 +} + +impl CcxDecodersXdsContext { + pub fn how_many_used(&self) -> i64 { + let mut count = 0; + for buffer in self.xds_buffers.iter() { + if buffer.in_use { + count += 1; + } + } + count + } + + pub fn clear_xds_buffer(&mut self, index: usize) -> Result<(), &str> { + if index < self.xds_buffers.len() { + self.xds_buffers[index].clear(); + Ok(()) + } else { + Err("Index out of bounds") + } + } +} + +impl CcxDecodersXdsContext { + pub fn xds_init_library(timing: TimingContext, xds_write_to_file: bool) -> Self { + Self { + timing, + xds_write_to_file, + ..Default::default() + } + } } impl Default for CcxDecodersXdsContext { fn default() -> Self { - Self { + CcxDecodersXdsContext { current_xds_min: -1, current_xds_hour: -1, current_xds_date: -1, @@ -373,61 +104,197 @@ impl Default for CcxDecodersXdsContext { 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 { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - }; NUM_XDS_BUFFERS as usize], - cur_xds_buffer_idx: -1, - cur_xds_packet_class: -1, - cur_xds_payload: std::ptr::null_mut(), + + xds_program_description: vec![String::new(); 8], + current_xds_network_name: String::new(), + current_xds_program_name: String::new(), + current_xds_call_letters: String::new(), + current_xds_program_type: String::new(), + xds_buffers: vec![XdsBuffer::default(); NUM_XDS_BUFFERS], + cur_xds_buffer_idx: None, + cur_xds_packet_class: 0, + cur_xds_payload: Vec::new(), cur_xds_payload_length: 0, cur_xds_packet_type: 0, timing: TimingContext::default(), current_ar_start: 0, current_ar_end: 0, - xds_write_to_file: 0, + xds_write_to_file: true, } } } -impl Default for XdsBuffer { - fn default() -> Self { - Self { - in_use: 0, - xds_class: -1, - xds_type: -1, - bytes: [0; NUM_BYTES_PER_PACKET as usize], - used_bytes: 0, - } - } +//---------------------------------------------------------------- + +use std::ptr::NonNull; + +// #[derive(Debug)] +// pub enum SubtitleData { +// None, +// Eia608(Vec), +// } + +#[derive(Debug)] +pub struct CcSubtitle { + pub data: Vec, // originally SubtitleData + pub datatype: SubDataType, + pub nb_data: i64, + pub subtype: SubType, + pub enc_type: CcxEncodingType, + pub start_time: i64, + pub end_time: i64, + pub flags: i64, + pub lang_index: i64, + pub got_output: bool, + pub mode: Vec, // of size 5 + pub info: Vec, // of size 4 + pub time_out: i64, + pub next: Option>, + pub prev: Option>, } impl Default for CcSubtitle { fn default() -> Self { Self { - data: std::ptr::null_mut(), - datatype: SubDataType::Generic, + data: Vec::new(), + datatype: SubDataType::Default, nb_data: 0, - subtype: SubType::Cc608, - enc_type: CcxEncodingType::Utf8, + subtype: SubType::Default, + enc_type: CcxEncodingType::Default, start_time: 0, end_time: 0, flags: 0, lang_index: 0, got_output: false, - mode: [0; 5], - info: [0; 4], + mode: vec![0; 5], + info: vec![0; 4], time_out: 0, - next: std::ptr::null_mut(), - prev: std::ptr::null_mut(), + next: None, + prev: None, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubDataType { + #[default] + Generic = 0, + Dvb = 1, + Default, // fallback variant +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubType { + #[default] + Bitmap, + Cc608, + Text, + Raw, + Default, // fallback variant +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum CcxEncodingType { + #[default] + Unicode = 0, + Latin1 = 1, + Utf8 = 2, + Ascii = 3, + Default, // fallback variant +} + +//---------------------------------------------------------------- + +#[derive(Debug)] +pub struct Eia608Screen { + pub format: CcxEia608Format, + // pub characters: [[u8; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], + pub characters: Vec>, + // pub colors: [[CcxDecoder608ColorCode; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], + pub colors: Vec>, + // pub fonts: [[FontBits; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], + pub fonts: Vec>, + // pub row_used: [i64; CCX_DECODER_608_SCREEN_ROWS], + pub row_used: Vec, + + pub empty: i64, + pub start_time: i64, + pub end_time: i64, + pub mode: CcModes, + pub channel: i64, + pub my_field: i64, + pub xds_str: String, + pub xds_len: i64, + pub cur_xds_packet_class: i64, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxEia608Format { + SformatCcScreen, + SformatCcLine, + SformatXds, + Default, // fallback variant +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxDecoder608ColorCode { + ColWhite = 0, + ColGreen = 1, + ColBlue = 2, + ColCyan = 3, + ColRed = 4, + ColYellow = 5, + ColMagenta = 6, + ColUserDefined = 7, + ColBlack = 8, + ColTransparent = 9, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FontBits { + FontRegular = 0, + FontItalics = 1, + FontUnderlined = 2, + FontUnderlinedItalics = 3, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcModes { + ModePopOn = 0, + ModeRollUp2 = 1, + ModeRollUp3 = 2, + ModeRollUp4 = 3, + ModeText = 4, + ModePaintOn = 5, + ModeFakeRollUp1 = 100, // Fake mode, also used as default mode +} + +impl Default for Eia608Screen { + fn default() -> Eia608Screen { + Eia608Screen { + format: CcxEia608Format::Default, + characters: vec![ + vec![0; CCX_DECODER_608_SCREEN_WIDTH + 1]; + CCX_DECODER_608_SCREEN_ROWS + ], + colors: vec![ + vec![CcxDecoder608ColorCode::ColWhite; CCX_DECODER_608_SCREEN_WIDTH + 1]; + CCX_DECODER_608_SCREEN_ROWS + ], + fonts: vec![ + vec![FontBits::FontRegular; CCX_DECODER_608_SCREEN_WIDTH + 1]; + CCX_DECODER_608_SCREEN_ROWS + ], + row_used: vec![0; CCX_DECODER_608_SCREEN_ROWS], + empty: 0, + start_time: TS_START_OF_XDS, + end_time: 0, + mode: CcModes::ModeFakeRollUp1, + channel: 0, + my_field: 0, + xds_str: String::new(), + xds_len: 0, + cur_xds_packet_class: 0, } } } diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index b7fda833f..fef19a6aa 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1,8 +1,8 @@ pub mod activity; pub mod common; +pub mod decoder_xds; pub mod hardsubx; pub mod subtitle; pub mod teletext; pub mod time; pub mod util; -pub mod decoder_xds; diff --git a/src/rust/src/libccxr_exports/xds_exports.rs b/src/rust/src/libccxr_exports/xds_exports.rs index b81bc421f..c85587a2e 100644 --- a/src/rust/src/libccxr_exports/xds_exports.rs +++ b/src/rust/src/libccxr_exports/xds_exports.rs @@ -1,127 +1,81 @@ -use lib_ccxr::decoder_xds::functions_xds::*; -use lib_ccxr::decoder_xds::structs_xds::*; -use lib_ccxr::time::timing::*; +use lib_ccxr::decoder_xds::functions_xds::{do_end_of_xds, process_xds_bytes, xds_cea608_test}; +use lib_ccxr::decoder_xds::structs_xds::{CcSubtitle, CcxDecodersXdsContext}; +use lib_ccxr::time::TimingContext; #[no_mangle] -pub extern "C" fn ccxr_ccx_decoders_xds_init_library( - timing: TimingContext, - xds_write_to_file: i64, -) -> CcxDecodersXdsContext { - ccx_decoders_xds_init_library(timing, xds_write_to_file) // Call the pure Rust function -} - -#[no_mangle] -pub extern "C" fn ccxr_write_xds_string( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - p: *const u8, - len: usize, -) -> i32 { - write_xds_string(sub, ctx, p, len) // Call the pure Rust function -} - /// # Safety -/// -/// The `fmt` pointer must be a valid null-terminated C string, -/// and `args` must contain valid formatting arguments. Ensure that -/// `fmt` is not null and points to a properly allocated memory region. -#[no_mangle] -pub unsafe extern "C" fn ccxr_xdsprint( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - fmt: *const std::os::raw::c_char, - args: *const std::os::raw::c_char, +/// - `sub` must be a non-null pointer to a valid, mutable `CcSubtitle` instance. +/// - `ctx` must be a non-null pointer to a valid, mutable `CcxDecodersXdsContext` instance. +/// - The caller must ensure that the memory referenced by `sub` and `ctx` remains valid for the duration of the function call. +/// - Passing null or invalid pointers will result in undefined behavior. +pub unsafe extern "C" fn ccxr_do_end_of_xds( + sub: *mut CcSubtitle, + ctx: *mut CcxDecodersXdsContext, + expected_checksum: u8, ) { - // Call the pure Rust function - xdsprint( - sub, - ctx, - unsafe { std::ffi::CStr::from_ptr(fmt).to_str().unwrap_or("") }, - format_args!("{}", unsafe { - std::ffi::CStr::from_ptr(args).to_str().unwrap_or("") - }), - ); -} + if sub.is_null() || ctx.is_null() { + return; + } -#[no_mangle] -pub extern "C" fn ccxr_clear_xds_buffer(ctx: &mut CcxDecodersXdsContext, num: i64) { - clear_xds_buffer(ctx, num); // Call the pure Rust function -} + let sub = unsafe { &mut *sub }; + let ctx = unsafe { &mut *ctx }; -#[no_mangle] -pub extern "C" fn ccxr_how_many_used(ctx: &CcxDecodersXdsContext) -> i64 { - how_many_used(ctx) // Call the pure Rust function + do_end_of_xds(sub, ctx, expected_checksum); } #[no_mangle] -pub extern "C" fn ccxr_process_xds_bytes(ctx: &mut CcxDecodersXdsContext, hi: u8, lo: i64) { - process_xds_bytes(ctx, hi, lo); // Call the pure Rust function -} +/// # Safety +/// - `ctx` must be a non-null pointer to a valid, mutable `CcxDecodersXdsContext` instance. +/// - The caller must ensure that the memory referenced by `ctx` remains valid for the duration of the function call. +/// - Passing a null or invalid pointer will result in undefined behavior. +pub unsafe extern "C" fn ccxr_process_xds_bytes(ctx: *mut CcxDecodersXdsContext, hi: u8, lo: u8) { + if ctx.is_null() { + return; + } -#[no_mangle] -pub extern "C" fn ccxr_xds_do_copy_generation_management_system( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - c1: u8, - c2: u8, -) { - xds_do_copy_generation_management_system(sub, ctx, c1, c2); // Call the pure Rust function -} + let ctx = unsafe { &mut *ctx }; -#[no_mangle] -pub extern "C" fn ccxr_xds_do_content_advisory( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - c1: u8, - c2: u8, -) { - xds_do_content_advisory(sub, ctx, c1, c2); // Call the pure Rust function + process_xds_bytes(ctx, hi as i64, lo as i64); } #[no_mangle] -pub extern "C" fn ccxr_xds_do_private_data( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, -) -> i64 { - xds_do_private_data(sub, ctx) // Call the pure Rust function -} +/// # Safety +/// - `timing` must be a non-null pointer to a valid, mutable `TimingContext` instance. +/// - The caller must ensure that the memory referenced by `timing` remains valid for the duration of the function call. +/// - The returned pointer must be managed properly by the caller. It points to a heap-allocated `CcxDecodersXdsContext` instance, and the caller is responsible for freeing it using `Box::from_raw` when it is no longer needed. +/// - Failure to free the returned pointer will result in a memory leak. +/// - Passing a null or invalid pointer for `timing` will result in undefined behavior. +pub unsafe extern "C" fn ccxr_ccx_decoders_xds_init_library( + timing: *mut TimingContext, + xds_write_to_file: bool, +) -> *mut CcxDecodersXdsContext { + if timing.is_null() { + return std::ptr::null_mut(); + } -#[no_mangle] -pub extern "C" fn ccxr_xds_do_misc(ctx: &CcxDecodersXdsContext) -> i64 { - xds_do_misc(ctx) // Call the pure Rust function -} + let timing = unsafe { &mut *timing }; -#[no_mangle] -pub extern "C" fn ccxr_xds_do_current_and_future( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, -) -> i64 { - xds_do_current_and_future(sub, ctx) // Call the pure Rust function -} + let ctx = CcxDecodersXdsContext::xds_init_library(timing.clone(), xds_write_to_file); -#[no_mangle] -pub extern "C" fn ccxr_do_end_of_xds( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, - expected_checksum: i64, -) { - do_end_of_xds(sub, ctx, expected_checksum); // Call the pure Rust function + Box::into_raw(Box::new(ctx)) } #[no_mangle] -pub extern "C" fn ccxr_xds_do_channel( - sub: &mut CcSubtitle, - ctx: &mut CcxDecodersXdsContext, -) -> i64 { - xds_do_channel(sub, ctx) // Call the pure Rust function -} +/// # Safety +/// - `ctx` must be a non-null pointer to a valid, mutable `CcxDecodersXdsContext` instance. +/// - `sub` must be a non-null pointer to a valid, mutable `CcSubtitle` instance. +/// - The caller must ensure that the memory referenced by `ctx` and `sub` remains valid for the duration of the function call. +/// - Passing null or invalid pointers will result in undefined behavior. +pub unsafe extern "C" fn ccxr_xds_cea608_test( + ctx: *mut CcxDecodersXdsContext, + sub: *mut CcSubtitle, +) { + if ctx.is_null() || sub.is_null() { + return; + } -#[no_mangle] -pub extern "C" fn ccxr_xds_debug_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { - xds_debug_test(ctx, sub); // Call the pure Rust function -} + let ctx = unsafe { &mut *ctx }; + let sub = unsafe { &mut *sub }; -#[no_mangle] -pub extern "C" fn ccxr_xds_cea608_test(ctx: &mut CcxDecodersXdsContext, sub: &mut CcSubtitle) { - xds_cea608_test(ctx, sub); // Call the pure Rust function + xds_cea608_test(ctx, sub); }