dash_runtime/address/
template.rs1use broadcast_common::impl_spec_display;
11use transmux::dash_parse::MAX_FORMAT_WIDTH;
12
13const IDENT_RESERVED: &str = "RepresentationID";
14const IDENT_NUMBER: &str = "Number";
15const IDENT_TIME: &str = "Time";
16const IDENT_BANDWIDTH: &str = "Bandwidth";
17const IDENT_SUB_NUMBER: &str = "SubNumber";
18const TAG_ZERO_FLAG: char = '0';
19const TAG_CONVERSION: char = 'd';
20const DELIMITER: char = '$';
21const TAG_START: char = '%';
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25#[non_exhaustive]
26pub struct TemplateUse {
27 pub representation_id: bool,
29 pub number: bool,
31 pub time: bool,
33 pub bandwidth: bool,
35 pub sub_number: bool,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum TemplateFault {
43 UnterminatedIdentifier,
45 UnknownIdentifier,
47 FormatOnRepresentationId,
49 BadFormatTag,
51 WidthTooLarge,
53 NumberAndTime,
55 MissingNumberOrTime,
57 DynamicIdentifierInInitialization,
59}
60
61impl TemplateFault {
62 #[must_use]
64 pub fn name(&self) -> &'static str {
65 match self {
66 Self::UnterminatedIdentifier => "unterminated_identifier",
67 Self::UnknownIdentifier => "unknown_identifier",
68 Self::FormatOnRepresentationId => "format_on_representation_id",
69 Self::BadFormatTag => "bad_format_tag",
70 Self::WidthTooLarge => "width_too_large",
71 Self::NumberAndTime => "number_and_time",
72 Self::MissingNumberOrTime => "missing_number_or_time",
73 Self::DynamicIdentifierInInitialization => "dynamic_identifier_in_initialization",
74 }
75 }
76}
77impl_spec_display!(TemplateFault);
78
79fn check_tag(tag: &str) -> Result<(), TemplateFault> {
80 let inner = tag
81 .strip_suffix(TAG_CONVERSION)
82 .ok_or(TemplateFault::BadFormatTag)?;
83 let digits = inner
84 .strip_prefix(TAG_ZERO_FLAG)
85 .ok_or(TemplateFault::BadFormatTag)?;
86 if !digits.bytes().all(|b| b.is_ascii_digit()) {
87 return Err(TemplateFault::BadFormatTag);
88 }
89 if !digits.is_empty() {
90 let width: usize = digits.parse().map_err(|_| TemplateFault::WidthTooLarge)?;
91 if width > MAX_FORMAT_WIDTH {
92 return Err(TemplateFault::WidthTooLarge);
93 }
94 }
95 Ok(())
96}
97
98pub fn scan(template: &str) -> Result<TemplateUse, TemplateFault> {
103 let mut used = TemplateUse::default();
104 let mut rest = template;
105 while let Some(at) = rest.find(DELIMITER) {
106 let tail = &rest[at + 1..];
107 if let Some(after) = tail.strip_prefix(DELIMITER) {
108 rest = after;
109 continue;
110 }
111 let end = tail
112 .find(DELIMITER)
113 .ok_or(TemplateFault::UnterminatedIdentifier)?;
114 let ident = &tail[..end];
115 let (name, tag) = match ident.split_once(TAG_START) {
116 Some((n, t)) => (n, Some(format!("{TAG_START}{t}"))),
117 None => (ident, None),
118 };
119 match name {
120 IDENT_RESERVED => {
121 if tag.is_some() {
122 return Err(TemplateFault::FormatOnRepresentationId);
123 }
124 used.representation_id = true;
125 }
126 IDENT_NUMBER | IDENT_TIME | IDENT_BANDWIDTH | IDENT_SUB_NUMBER => {
127 if let Some(t) = &tag {
128 check_tag(&t[TAG_START.len_utf8()..])?;
129 }
130 match name {
131 IDENT_NUMBER => used.number = true,
132 IDENT_TIME => used.time = true,
133 IDENT_BANDWIDTH => used.bandwidth = true,
134 _ => used.sub_number = true,
135 }
136 }
137 _ => return Err(TemplateFault::UnknownIdentifier),
138 }
139 rest = &tail[end + 1..];
140 }
141 Ok(used)
142}
143
144pub fn scan_media(template: &str) -> Result<TemplateUse, TemplateFault> {
149 let used = scan(template)?;
150 if used.number && used.time {
151 return Err(TemplateFault::NumberAndTime);
152 }
153 if !used.number && !used.time {
154 return Err(TemplateFault::MissingNumberOrTime);
155 }
156 Ok(used)
157}
158
159pub fn scan_initialization(template: &str) -> Result<TemplateUse, TemplateFault> {
164 let used = scan(template)?;
165 if used.number || used.time {
166 return Err(TemplateFault::DynamicIdentifierInInitialization);
167 }
168 Ok(used)
169}
170
171#[must_use]
173pub fn substitute(
174 template: &str,
175 representation_id: &str,
176 number: Option<u64>,
177 time: Option<u64>,
178 bandwidth: u64,
179) -> String {
180 transmux::SegmentTemplate::resolve(template, representation_id, number, time, Some(bandwidth))
181}