From 029fa9b8dc9a9973accaa9b9f0c5f9b6a772ed04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Fri, 8 Sep 2023 10:02:18 +0200 Subject: [PATCH] net/ndi: improve interoperability robustness `quick-xml::reader::Reader::trim_text(true)` doesn't remove white spaces and tabs from XML text. Besides, for interoperability robustness we also need to remove carriage returns and line feeds. Also improve the default capacities for the `SmallVec`s. Part-of: --- net/ndi/src/ndi_cc_meta.rs | 68 ++++++++++++++++++++++++++++++++------ net/ndi/src/video_anc.rs | 4 +-- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/net/ndi/src/ndi_cc_meta.rs b/net/ndi/src/ndi_cc_meta.rs index 35f400d4..6d004361 100644 --- a/net/ndi/src/ndi_cc_meta.rs +++ b/net/ndi/src/ndi_cc_meta.rs @@ -21,12 +21,12 @@ const C708_TAG_BYTES: &[u8] = C708_TAG.as_bytes(); const LINE_ATTR: &str = "line"; const DEFAULT_LINE_VALUE: &str = "21"; -/// Video anc AFD content padded to 32bit alignment encoded in base64 -const NDI_CC_CONTENT_MAX_LEN: usize = (video_anc::VIDEO_ANC_AFD_MAX_LEN + 3) * 3 / 2; +/// Video anc AFD content padded to 32bit alignment encoded in base64 + padding +const NDI_CC_CONTENT_CAPACITY: usize = (video_anc::VIDEO_ANC_AFD_CAPACITY + 3) * 3 / 2 + 2; /// Video anc AFD padded to 32bit alignment encoded in base64 -/// + XML tags with brackets and end '/' -const NDI_CC_MAX_LEN: usize = NDI_CC_CONTENT_MAX_LEN + 13; +/// + XML tags with brackets and end '/' + attr +const NDI_CC_CAPACITY: usize = NDI_CC_CONTENT_CAPACITY + 13 + 10; #[derive(thiserror::Error, Debug, Eq, PartialEq)] /// NDI Video Caption related Errors. @@ -50,7 +50,7 @@ where use quick_xml::events::{BytesText, Event}; use std::borrow::Cow; - let mut buf = String::with_capacity(NDI_CC_CONTENT_MAX_LEN); + let mut buf = String::with_capacity(NDI_CC_CONTENT_CAPACITY); let mut input = Cow::from(data); let alignment_rem = input.len() % 4; @@ -83,7 +83,7 @@ pub fn encode_video_caption_meta(video_buf: &gst::BufferRef) -> Result::with_capacity(NDI_CC_MAX_LEN)); + let mut writer = Writer::new(Vec::::with_capacity(NDI_CC_CAPACITY)); let cc_meta_iter = video_buf.iter_meta::(); for cc_meta in cc_meta_iter { @@ -164,15 +164,20 @@ pub fn parse_ndi_cc_meta(input: &str) -> Result> { let mut ndi_cc = Vec::new(); let mut reader = Reader::from_str(input); - reader.trim_text(true); - let mut content = SmallVec::<[u8; NDI_CC_CONTENT_MAX_LEN]>::new(); - let mut buf = Vec::with_capacity(NDI_CC_MAX_LEN); + let mut content = SmallVec::<[u8; NDI_CC_CONTENT_CAPACITY]>::new(); + let mut buf = Vec::with_capacity(NDI_CC_CAPACITY); loop { match reader.read_event_into(&mut buf)? { Event::Eof => break, Event::Start(_) => content.clear(), - Event::Text(e) => content.extend(e.iter().copied()), + Event::Text(e) => { + content.extend( + e.iter() + .copied() + .filter(|&b| (b != b' ') && (b != b'\t') && (b != b'\n') && (b != b'\r')), + ); + } Event::End(e) => match e.name().as_ref() { C608_TAG_BYTES => { let adf_packet = BASE64.decode(content.as_slice()).context(C608_TAG)?; @@ -368,6 +373,49 @@ mod tests { assert!(ndi_cc_list.is_empty()); } + #[test] + fn parse_ndi_meta_c708_newlines_and_indent() { + let mut ndi_cc_list = parse_ndi_cc_meta( + r#" + AD///WFAZVpaaZVj9Q4AgCcn4vxlEsvmAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIA + vqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAvqAIAnSAIA + hutwA= +"#, + ) + .unwrap(); + + let ndi_cc = ndi_cc_list.pop().unwrap(); + assert_eq!(ndi_cc.cc_type, VideoCaptionType::Cea708Cdp); + assert_eq!( + ndi_cc.data.as_slice(), + [ + 0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00, + 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, + 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, + 0x1b, + ] + ); + + assert!(ndi_cc_list.is_empty()); + } + + #[test] + fn parse_ndi_meta_c608_newlines_spaces_inline() { + let mut ndi_cc_list = parse_ndi_cc_meta( + "\n\tAD///WFAo\n\n\r DYBlEsq\r\n\tYAAAAA== \n", + ) + .unwrap(); + + let ndi_cc = ndi_cc_list.pop().unwrap(); + assert_eq!(ndi_cc.cc_type, VideoCaptionType::Cea608S3341a); + assert_eq!(ndi_cc.data.as_slice(), [0x80, 0x94, 0x2c]); + + assert!(ndi_cc_list.is_empty()); + } + #[test] fn parse_ndi_meta_c608_and_c708() { let ndi_cc_list = parse_ndi_cc_meta( diff --git a/net/ndi/src/video_anc.rs b/net/ndi/src/video_anc.rs index 06b9733b..ce0b7ba2 100644 --- a/net/ndi/src/video_anc.rs +++ b/net/ndi/src/video_anc.rs @@ -31,9 +31,9 @@ const EIA_608_ANCILLARY_DID_16: u16 = 0x6102; // ADF + DID/SDID + DATA COUNT + PAYLOAD + checksum: // 3 + 2 + 1 + 256 max + 1 = 263 // Those are 10bit words, so we need 329 bytes max. -pub const VIDEO_ANC_AFD_MAX_LEN: usize = 329; +pub const VIDEO_ANC_AFD_CAPACITY: usize = 329; -pub type VideoAncillaryAFD = SmallVec<[u8; VIDEO_ANC_AFD_MAX_LEN]>; +pub type VideoAncillaryAFD = SmallVec<[u8; VIDEO_ANC_AFD_CAPACITY]>; fn with_afd_parity(val: u8) -> u16 { let p = (val.count_ones() % 2) as u16;