diff --git a/Cargo.toml b/Cargo.toml index 39e56fa..a0a3566 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,13 @@ edition = "2021" license = "MIT" [dependencies] -base64 = "0.13.0" -bitstream-io = "1.3.0" -crc = "3.0.0" +base64 = "0.13" +bitstream-io = "1.3" +crc = "3.0" +thiserror = "1" +serde = { version = "1", features = ["derive"], optional = true } + +[dev-dependencies] +serde_json = "1" +anyhow = "1" +assert-json-diff = "2" diff --git a/src/commands.rs b/src/commands.rs index 41d2876..6df2e4b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,10 +1,14 @@ use crate::{CueError, TransportPacketWrite}; use std::io; +#[cfg(feature = "serde")] +use serde::Serialize; + pub trait SpliceCommand: TransportPacketWrite { - fn splice_command_type(&self) -> u8; + fn splice_command_type(&self) -> SpliceCommandType; } +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct SpliceNull {} impl SpliceNull { @@ -23,8 +27,8 @@ impl TransportPacketWrite for SpliceNull { } impl SpliceCommand for SpliceNull { - fn splice_command_type(&self) -> u8 { - SpliceCommandType::SpliceNull.into() + fn splice_command_type(&self) -> SpliceCommandType { + SpliceCommandType::SpliceNull } } diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index 855f212..1fad41d 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -1,10 +1,14 @@ mod segmentation; use crate::{CueError, TransportPacketWrite}; +pub use segmentation::*; use std::io; -pub use segmentation::*; +#[cfg(feature = "serde")] +use serde::Serialize; +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub enum SpliceDescriptor { Avail, DTMF, diff --git a/src/descriptors/segmentation.rs b/src/descriptors/segmentation.rs index 1a912ac..e79b9b0 100644 --- a/src/descriptors/segmentation.rs +++ b/src/descriptors/segmentation.rs @@ -1,6 +1,11 @@ use crate::{CueError, TransportPacketWrite}; use std::io; +#[cfg(feature = "serde")] +use serde::Serialize; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct SegmentationDescriptor { identifier: u32, segmentation_event_id: u32, @@ -20,6 +25,8 @@ impl TransportPacketWrite for SegmentationDescriptor { } } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] struct DeliveryRestriction { web_delivery_allowed_flag: bool, no_regional_blackout_flag: bool, @@ -27,6 +34,8 @@ struct DeliveryRestriction { device_restrictions: DeviceRestrictions, } +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] enum DeviceRestrictions { RestrictGroup0 = 0x00, RestrictGroup1 = 0x01, @@ -56,6 +65,8 @@ enum SegmentationUpidType { Reserved, } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] enum SegmentationUpid { NotUsed, UserDefinedDeprecated, @@ -78,6 +89,8 @@ enum SegmentationUpid { Reserved, } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize))] struct Component { component_tag: u8, pts_offset: u64, diff --git a/src/info.rs b/src/info.rs index 0eea4aa..fc8760a 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,13 +1,25 @@ -use crate::commands::SpliceCommand; +use crate::commands::{SpliceCommand, SpliceCommandType}; use crate::descriptors::SpliceDescriptor; use crate::{CueError, TransportPacketWrite}; use bitstream_io::{BigEndian, BitWrite, BitWriter}; -use std::io; -use crc::{Crc, Algorithm, CRC_32_MPEG_2}; +use crc::{Crc, CRC_32_MPEG_2}; +use std::fmt::{Display, Formatter}; + +#[cfg(feature = "serde")] +use serde::Serialize; pub const MPEG_2: Crc = Crc::::new(&CRC_32_MPEG_2); -pub struct SpliceInfoSection +pub struct SpliceInfoSection +where + C: SpliceCommand, + S: EncodingState, +{ + state: SpliceInfoState, + encoded: S, +} + +struct SpliceInfoState where C: SpliceCommand, { @@ -39,31 +51,131 @@ where descriptors: Vec, } -impl SpliceInfoSection +pub trait EncodingState {} + +struct NotEncoded; + +impl EncodingState for NotEncoded {} + +struct EncodedData { + section_length: u16, + splice_command_length: u16, + splice_command_type: SpliceCommandType, + descriptor_loop_length: u16, + crc32: u32, + final_data: Vec, +} + +impl EncodingState for EncodedData {} + +impl SpliceInfoSection where C: SpliceCommand, { fn new(splice_command: C) -> Self { Self { - table_id: 0xFC, - section_syntax_indicator: false, - private_indicator: false, - sap_type: SAPType::NotSpecified, - protocol_version: 0, - encrypted_packet: false, - encryption_algorithm: EncryptionAlgorithm::NotEncrypted, - pts_adjustment: 0, - cw_index: 0, - tier: 0xFFF, - splice_command, - descriptors: Vec::new(), + state: SpliceInfoState { + table_id: 0xFC, + section_syntax_indicator: false, + private_indicator: false, + sap_type: SAPType::NotSpecified, + protocol_version: 0, + encrypted_packet: false, + encryption_algorithm: EncryptionAlgorithm::NotEncrypted, + pts_adjustment: 0, + cw_index: 0, + tier: 0xFFF, + splice_command, + descriptors: Vec::new(), + }, + encoded: NotEncoded, } } +} +impl SpliceInfoSection +where + C: SpliceCommand, +{ + fn into_encoded(self) -> Result, CueError> { + // Write splice command to a temporary buffer + let mut splice_data = Vec::new(); + self.state.splice_command.write_to(&mut splice_data)?; + + // Write the descriptors to a temporary buffer + let mut descriptor_data = Vec::new(); + for descriptor in &self.state.descriptors { + descriptor.write_to(&mut descriptor_data)?; + } + + // Start writing the final output to a temporary buffer + let mut data = Vec::new(); + let mut buffer = BitWriter::endian(&mut data, BigEndian); + buffer.write(8, self.state.table_id)?; + buffer.write_bit(self.state.section_syntax_indicator)?; + buffer.write_bit(self.state.private_indicator)?; + buffer.write(2, self.state.sap_type as u8)?; + + // We know the section length by computing all known fixed size elements from now plus the + // splice command length and descriptors which are also known by now + const FIXED_INFO_SIZE_BYTES: usize = (8 + 1 + 6 + 33 + 8 + 12 + 12 + 8 + 16 + 32) / 8; + let mut section_length = + (FIXED_INFO_SIZE_BYTES + splice_data.len() + descriptor_data.len()) as u16; + if self.state.encrypted_packet { + section_length += 4; + } + buffer.write(12, section_length)?; + buffer.write(8, self.state.protocol_version)?; + buffer.write_bit(self.state.encrypted_packet)?; + let encryption_algorithm: u8 = self.state.encryption_algorithm.into(); + buffer.write(6, encryption_algorithm)?; + buffer.write(33, self.state.pts_adjustment)?; + buffer.write(8, self.state.cw_index)?; + buffer.write(12, self.state.tier)?; + let splice_command_length = splice_data.len() as u16; + buffer.write(12, splice_command_length)?; + let splice_command_type = self.state.splice_command.splice_command_type(); + buffer.write(8, u8::from(splice_command_type))?; + buffer.write_bytes(splice_data.as_slice())?; + let descriptor_loop_length = descriptor_data.len() as u16; + buffer.write(16, descriptor_loop_length)?; + buffer.write_bytes(descriptor_data.as_slice())?; + buffer.flush()?; + + // Finally, write to out + let mut final_data = Vec::new(); + let mut buffer = BitWriter::endian(&mut final_data, BigEndian); + buffer.write_bytes(data.as_slice())?; + // CRC 32 + if self.state.encrypted_packet { + // TODO: alignment stuffing here, in case of DES encryption this needs to be 8 bytes aligned + // encrypted_packet_crc32: + buffer.write(32, 0)?; + } + let crc32 = MPEG_2.checksum(data.as_slice()); + buffer.write(32, crc32)?; + buffer.flush()?; + + Ok(SpliceInfoSection { + state: self.state, + encoded: EncodedData { + section_length, + splice_command_length, + splice_command_type, + descriptor_loop_length, + crc32, + final_data, + }, + }) + } +} + +impl SpliceInfoSection +where + C: SpliceCommand, +{ pub fn as_base64(&self) -> Result { - let mut out = Vec::new(); - self.write_to(&mut out)?; - Ok(base64::encode(out.as_slice())) + Ok(base64::encode(self.encoded.final_data.as_slice())) } } @@ -76,6 +188,17 @@ pub enum SAPType { NotSpecified = 0x03, } +impl Display for SAPType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SAPType::Type1 => write!(f, "Type 1"), + SAPType::Type2 => write!(f, "Type 2"), + SAPType::Type3 => write!(f, "Type 3"), + SAPType::NotSpecified => write!(f, "Not Specified"), + } + } +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum EncryptionAlgorithm { NotEncrypted, @@ -112,69 +235,60 @@ impl From for u8 { } } -impl TransportPacketWrite for SpliceInfoSection -where - C: SpliceCommand, -{ - fn write_to(&self, out: &mut W) -> Result<(), CueError> +#[cfg(feature = "serde")] +mod serde_serialization { + use super::*; + use serde::ser::{Serialize, SerializeStruct, Serializer}; + use std::fmt::LowerHex; + + impl Serialize for SpliceInfoSection where - W: io::Write, + C: SpliceCommand + Serialize, { - // Write splice command to a temporary buffer - let mut splice_data = Vec::new(); - self.splice_command.write_to(&mut splice_data)?; + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[inline] + fn as_hex(value: T) -> String + where + T: LowerHex, + { + format!("0x{:x}", value) + } - // Write the descriptors to a temporary buffer - let mut descriptor_data = Vec::new(); - for descriptor in &self.descriptors { - descriptor.write_to(&mut descriptor_data)?; + let mut state = serializer.serialize_struct("SpliceInfoSection", 17)?; + state.serialize_field("table_id", &as_hex(self.state.table_id))?; + state.serialize_field( + "section_syntax_indicator", + &self.state.section_syntax_indicator, + )?; + state.serialize_field("private_indicator", &self.state.private_indicator)?; + state.serialize_field("sap_type", &as_hex(self.state.sap_type as u8))?; + state.serialize_field("section_length", &self.encoded.section_length)?; + state.serialize_field("protocol_version", &self.state.protocol_version)?; + state.serialize_field("encrypted_packet", &self.state.encrypted_packet)?; + state.serialize_field( + "encryption_algorithm", + &u8::from(self.state.encryption_algorithm), + )?; + state.serialize_field("pts_adjustment", &self.state.pts_adjustment)?; + state.serialize_field("cw_index", &as_hex(self.state.cw_index))?; + state.serialize_field("tier", &as_hex(self.state.tier))?; + state.serialize_field("splice_command_length", &self.encoded.splice_command_length)?; + state.serialize_field( + "splice_command_type", + &u8::from(self.encoded.splice_command_type), + )?; + state.serialize_field("splice_command", &self.state.splice_command)?; + state.serialize_field( + "descriptor_loop_length", + &self.encoded.descriptor_loop_length, + )?; + state.serialize_field("descriptor_loop", &self.state.descriptors)?; + state.serialize_field("crc_32", &as_hex(self.encoded.crc32))?; + state.end() } - - // Start writing the final output to a temporary buffer - let mut data = Vec::new(); - let mut buffer = BitWriter::endian(&mut data, BigEndian); - buffer.write(8, self.table_id)?; - buffer.write_bit(self.section_syntax_indicator)?; - buffer.write_bit(self.private_indicator)?; - buffer.write(2, self.sap_type as u8)?; - - // We know the section length by computing all known fixed size elements from now plus the - // splice command length and descriptors which are also known by now - const FIXED_INFO_SIZE_BYTES: usize = (8 + 1 + 6 + 33 + 8 + 12 + 12 + 8 + 16 + 32) / 8; - let mut section_length = FIXED_INFO_SIZE_BYTES + splice_data.len() + descriptor_data.len(); - if self.encrypted_packet { - section_length += 4; - } - buffer.write(12, section_length as u16)?; - buffer.write(8, self.protocol_version)?; - buffer.write_bit(self.encrypted_packet)?; - let encryption_algorithm: u8 = self.encryption_algorithm.into(); - buffer.write(6, encryption_algorithm)?; - buffer.write(33, self.pts_adjustment)?; - buffer.write(8, self.cw_index)?; - buffer.write(12, self.tier)?; - buffer.write(12, splice_data.len() as u16)?; - buffer.write(8, self.splice_command.splice_command_type())?; - buffer.write_bytes(splice_data.as_slice())?; - buffer.write(16, descriptor_data.len() as u16)?; - buffer.write_bytes(descriptor_data.as_slice())?; - buffer.flush()?; - - // Finally, write to out - let mut buffer = BitWriter::endian(out, BigEndian); - buffer.write_bytes(data.as_slice())?; - // CRC 32 - if self.encrypted_packet { - // TODO: alignment stuffing here, in case of DES encryption this needs to be 8 bytes aligned - // encrypted_packet_crc32: - buffer.write(32, 0)?; - } - // TODO: Calculate CRC32. Use the data information - // crc32: - buffer.write(32, MPEG_2.checksum(data.as_slice()))?; - buffer.flush()?; - - Ok(()) } } @@ -182,11 +296,49 @@ where mod tests { use super::*; use crate::commands::SpliceNull; + use anyhow::Result; + use assert_json_diff::assert_json_eq; #[test] - fn write_null_splice() { + fn write_null_splice() -> Result<()> { let splice = SpliceInfoSection::new(SpliceNull::new()); - assert_eq!(splice.as_base64().unwrap(), "/DARAAAAAAAAAP/wAAAAAHpPv/8=".to_string()); + assert_eq!( + splice.into_encoded()?.as_base64()?, + "/DARAAAAAAAAAP/wAAAAAHpPv/8=".to_string() + ); + + Ok(()) + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_as_json() -> Result<()> { + let splice = SpliceInfoSection::new(SpliceNull::new()); + + assert_json_eq!( + serde_json::to_value(&splice.into_encoded()?)?, + serde_json::json!({ + "table_id": "0xfc", + "section_syntax_indicator": false, + "private_indicator": false, + "sap_type": "0x3", + "section_length": 17, + "protocol_version": 0, + "encrypted_packet": false, + "encryption_algorithm": 0, + "pts_adjustment": 0, + "cw_index": "0x0", + "tier": "0xfff", + "splice_command_length": 0, + "splice_command_type": 0, + "splice_command": {}, + "descriptor_loop_length": 0, + "descriptor_loop": [], + "crc_32": "0x7a4fbfff" + }) + ); + + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index a8b01a8..3237130 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use std::io; +use thiserror::Error; mod commands; mod descriptors; @@ -10,13 +11,8 @@ pub trait TransportPacketWrite { W: io::Write; } -#[derive(Debug)] +#[derive(Error, Debug)] +#[error("Could not execute operation due to {0}")] pub enum CueError { - Io(io::Error), -} - -impl From for CueError { - fn from(err: io::Error) -> CueError { - CueError::Io(err) - } + Io(#[from] io::Error), }