From cf015c91ba6f144233479687849c9b9cb766d4c7 Mon Sep 17 00:00:00 2001 From: Chandragupt Singh Date: Fri, 28 Nov 2025 15:02:00 +0530 Subject: [PATCH 1/4] Fix ATSC EPG handling and fix incorrect bounds checks in EIT/VCT parsing --- src/lib_ccx/ts_tables_epg.c | 95 ++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/src/lib_ccx/ts_tables_epg.c b/src/lib_ccx/ts_tables_epg.c index 383a88bff..278eeb3e5 100644 --- a/src/lib_ccx/ts_tables_epg.c +++ b/src/lib_ccx/ts_tables_epg.c @@ -412,30 +412,51 @@ void EPG_output(struct lib_ccx_ctx *ctx) fprintf(f, "\n"); fprintf(f, " \n"); } - if (ccx_options.xmltvonlycurrent == 0) - { // print all events - for (i = 0; i < ctx->demux_ctx->nb_program; i++) - { - for (j = 0; j < ctx->eit_programs[i].array_len; j++) - EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); - } - - if (ctx->demux_ctx->nb_program == 0) // Stream has no PMT, fall back to unordered events - for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) - EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); - } - else - { // print current events only - for (i = 0; i < ctx->demux_ctx->nb_program; i++) - { - ce = ctx->eit_current_events[i]; - for (j = 0; j < ctx->eit_programs[i].array_len; j++) - { - if (ce == ctx->eit_programs[i].epg_events[j].id) - EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); - } - } - } +if (ccx_options.xmltvonlycurrent == 0) +{ // print all events + // Print events from mapped programs + for (i = 0; i < ctx->demux_ctx->nb_program; i++) + { + for (j = 0; j < ctx->eit_programs[i].array_len; j++) + EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); + } + + // CRITICAL FIX: Always check fallback storage, not just when nb_program==0 + // This fixes ATSC streams where VCT creates programs but EIT events + // end up in fallback storage due to source_id mapping issues + if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) + { + for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) + { + EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], + ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); + } + } +} +else +{ // print current events only + for (i = 0; i < ctx->demux_ctx->nb_program; i++) + { + ce = ctx->eit_current_events[i]; + for (j = 0; j < ctx->eit_programs[i].array_len; j++) + { + if (ce == ctx->eit_programs[i].epg_events[j].id) + EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); + } + } + + // CRITICAL FIX: Also check fallback for current events + if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) + { + ce = ctx->eit_current_events[TS_PMT_MAP_SIZE]; + for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) + { + if (ce == ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].id) + EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], + ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); + } + } +} fprintf(f, ""); fclose(f); } @@ -817,7 +838,7 @@ void EPG_ATSC_decode_multiple_string(uint8_t *offset, uint32_t length, struct EP char ISO_639_language_code[4]; uint8_t *offset_end = offset + length; #define CHECK_OFFSET(val) \ - if (offset + val < offset_end) \ + if (offset + (val) > offset_end) \ return CHECK_OFFSET(1); @@ -904,7 +925,7 @@ void EPG_ATSC_decode_EIT(struct lib_ccx_ctx *ctx, uint8_t *payload_start, uint32 num_events_in_section = payload_start[9]; #define CHECK_OFFSET(val) \ - if (offset + val < (payload_start + size)) \ + if (offset + (val) > (payload_start + size)) \ return offset = &payload_start[10]; @@ -1121,24 +1142,40 @@ void EPG_parse_table(struct lib_ccx_ctx *ctx, uint8_t *b, uint32_t size) { return; } + payload_start = &b[pointer_field + 1]; table_id = payload_start[0]; + switch (table_id) { - case 0x0cb: + // ATSC EIT tables (EIT-0..EIT-3 and extended range) + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: // Extended ATSC EIT + case 0xD0: // Extended ATSC EIT EPG_ATSC_decode_EIT(ctx, payload_start, size - (payload_start - b)); break; - case 0xc8: + + // ATSC VCT (Virtual Channel Table) + case 0xC8: + case 0xC9: // Cable VCT variant EPG_ATSC_decode_VCT(ctx, payload_start, size - (payload_start - b)); break; + default: - if (table_id >= 0x4e && table_id <= 0x6f) + // DVB EIT tables + if (table_id >= 0x4E && table_id <= 0x6F) EPG_DVB_decode_EIT(ctx, payload_start, size - (payload_start - b)); break; } + EPG_handle_output(ctx); } + + // reconstructs DVB EIT and ATSC tables void parse_EPG_packet(struct lib_ccx_ctx *ctx) { From 696e59fdd6197292e9e79b7b988560a1964d3864 Mon Sep 17 00:00:00 2001 From: Chandragupt Singh Date: Fri, 28 Nov 2025 15:44:21 +0530 Subject: [PATCH 2/4] docs: add changelog entry for enhanced ATSC EIT/VCT parsing and extended EIT table ID support --- docs/CHANGES.TXT | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT index 678906459..7098f79d5 100644 --- a/docs/CHANGES.TXT +++ b/docs/CHANGES.TXT @@ -1,5 +1,6 @@ 0.95 (2025-09-15) ----------------- +- Fix: Enhanced ATSC EIT/VCT parsing with corrected bounds checks, improved event mapping, added support for extended EIT table IDs (0xCD–0xD0), and reliable XMLTV generation. - Fix: ARM64/aarch64 build failure due to c_char type mismatch in nal.rs - Fix: HardSubX OCR on Rust - Removed the Share Module From 5b1e50d6d88a1c7636649924b3662e4b498b58c2 Mon Sep 17 00:00:00 2001 From: Chandragupt Singh Date: Fri, 28 Nov 2025 16:56:02 +0530 Subject: [PATCH 3/4] style: fix formatting for CI --- src/lib_ccx/ts_tables_epg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_ccx/ts_tables_epg.c b/src/lib_ccx/ts_tables_epg.c index 278eeb3e5..b2b9c110b 100644 --- a/src/lib_ccx/ts_tables_epg.c +++ b/src/lib_ccx/ts_tables_epg.c @@ -837,7 +837,7 @@ void EPG_ATSC_decode_multiple_string(uint8_t *offset, uint32_t length, struct EP int i, j; char ISO_639_language_code[4]; uint8_t *offset_end = offset + length; -#define CHECK_OFFSET(val) \ +#define CHECK_OFFSET(val) \ if (offset + (val) > offset_end) \ return From b89241725cb610958473ac709dc2006496116931 Mon Sep 17 00:00:00 2001 From: Chandragupt Singh Date: Fri, 28 Nov 2025 17:05:53 +0530 Subject: [PATCH 4/4] style: apply clang-format --- src/lib_ccx/ts_tables_epg.c | 96 ++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/lib_ccx/ts_tables_epg.c b/src/lib_ccx/ts_tables_epg.c index b2b9c110b..f1d483363 100644 --- a/src/lib_ccx/ts_tables_epg.c +++ b/src/lib_ccx/ts_tables_epg.c @@ -412,51 +412,51 @@ void EPG_output(struct lib_ccx_ctx *ctx) fprintf(f, "\n"); fprintf(f, " \n"); } -if (ccx_options.xmltvonlycurrent == 0) -{ // print all events - // Print events from mapped programs - for (i = 0; i < ctx->demux_ctx->nb_program; i++) - { - for (j = 0; j < ctx->eit_programs[i].array_len; j++) - EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); - } - - // CRITICAL FIX: Always check fallback storage, not just when nb_program==0 - // This fixes ATSC streams where VCT creates programs but EIT events - // end up in fallback storage due to source_id mapping issues - if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) - { - for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) - { - EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], - ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); - } - } -} -else -{ // print current events only - for (i = 0; i < ctx->demux_ctx->nb_program; i++) - { - ce = ctx->eit_current_events[i]; - for (j = 0; j < ctx->eit_programs[i].array_len; j++) - { - if (ce == ctx->eit_programs[i].epg_events[j].id) - EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); - } - } - - // CRITICAL FIX: Also check fallback for current events - if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) - { - ce = ctx->eit_current_events[TS_PMT_MAP_SIZE]; - for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) - { - if (ce == ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].id) - EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], - ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); - } - } -} + if (ccx_options.xmltvonlycurrent == 0) + { // print all events + // Print events from mapped programs + for (i = 0; i < ctx->demux_ctx->nb_program; i++) + { + for (j = 0; j < ctx->eit_programs[i].array_len; j++) + EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); + } + + // CRITICAL FIX: Always check fallback storage, not just when nb_program==0 + // This fixes ATSC streams where VCT creates programs but EIT events + // end up in fallback storage due to source_id mapping issues + if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) + { + for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) + { + EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], + ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); + } + } + } + else + { // print current events only + for (i = 0; i < ctx->demux_ctx->nb_program; i++) + { + ce = ctx->eit_current_events[i]; + for (j = 0; j < ctx->eit_programs[i].array_len; j++) + { + if (ce == ctx->eit_programs[i].epg_events[j].id) + EPG_print_event(&ctx->eit_programs[i].epg_events[j], ctx->demux_ctx->pinfo[i].program_number, f); + } + } + + // CRITICAL FIX: Also check fallback for current events + if (ctx->eit_programs[TS_PMT_MAP_SIZE].array_len > 0) + { + ce = ctx->eit_current_events[TS_PMT_MAP_SIZE]; + for (j = 0; j < ctx->eit_programs[TS_PMT_MAP_SIZE].array_len; j++) + { + if (ce == ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].id) + EPG_print_event(&ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j], + ctx->eit_programs[TS_PMT_MAP_SIZE].epg_events[j].service_id, f); + } + } + } fprintf(f, ""); fclose(f); } @@ -837,7 +837,7 @@ void EPG_ATSC_decode_multiple_string(uint8_t *offset, uint32_t length, struct EP int i, j; char ISO_639_language_code[4]; uint8_t *offset_end = offset + length; -#define CHECK_OFFSET(val) \ +#define CHECK_OFFSET(val) \ if (offset + (val) > offset_end) \ return @@ -924,7 +924,7 @@ void EPG_ATSC_decode_EIT(struct lib_ccx_ctx *ctx, uint8_t *payload_start, uint32 num_events_in_section = payload_start[9]; -#define CHECK_OFFSET(val) \ +#define CHECK_OFFSET(val) \ if (offset + (val) > (payload_start + size)) \ return offset = &payload_start[10]; @@ -1174,8 +1174,6 @@ void EPG_parse_table(struct lib_ccx_ctx *ctx, uint8_t *b, uint32_t size) EPG_handle_output(ctx); } - - // reconstructs DVB EIT and ATSC tables void parse_EPG_packet(struct lib_ccx_ctx *ctx) {