libflute
FileDeliveryTable.cpp
Go to the documentation of this file.
1 // libflute - FLUTE/ALC library
2 //
3 // Copyright (C) 2021 Klaus Kühnhammer (Österreichische Rundfunksender GmbH & Co KG)
4 // 2025 British Broadcasting Corporation (David Waring <david.waring2@bbc.co.uk>)
5 //
6 // Licensed under the License terms and conditions for use, reproduction, and
7 // distribution of 5G-MAG software (the “License”). You may not use this file
8 // except in compliance with the License. You may obtain a copy of the License at
9 // https://www.5g-mag.com/reference-tools. Unless required by applicable law or
10 // agreed to in writing, software distributed under the License is distributed on
11 // an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 // or implied.
13 //
14 // See the License for the specific language governing permissions and limitations
15 // under the License.
16 //
17 #include "FileDeliveryTable.h"
18 #include "tinyxml2.h"
19 #include <iostream>
20 #include <string>
21 #include <map>
22 #include "spdlog/spdlog.h"
23 
24 namespace {
25  class XMLNamespaces {
26  public:
27  XMLNamespaces() :_default_ns() ,_prefix_to_ns_map() {};
28  XMLNamespaces(const XMLNamespaces &to_copy) :_default_ns(to_copy._default_ns) ,_prefix_to_ns_map(to_copy._prefix_to_ns_map) {};
29  XMLNamespaces(XMLNamespaces &&to_move) :_default_ns(std::move(to_move._default_ns)) ,_prefix_to_ns_map(std::move(to_move._prefix_to_ns_map)) {};
30  XMLNamespaces(const tinyxml2::XMLElement *element, const XMLNamespaces &parent_ns = XMLNamespaces());
31 
32  const tinyxml2::XMLElement *findChildElement(const tinyxml2::XMLElement *element, const std::string &name, const std::string &ns = std::string()) const;
33  const tinyxml2::XMLElement *findSiblingElement(const tinyxml2::XMLElement *child_elem, const std::string &name, const std::string &ns = std::string()) const;
34  const tinyxml2::XMLAttribute *findAttribute(const tinyxml2::XMLElement *element, const std::string &name, const std::string &ns = std::string()) const;
35  bool matches(const std::string &prefixed_name, const std::string &name, const std::string &ns = std::string()) const;
36  const std::string &elementNamespace(const tinyxml2::XMLElement *element) const;
37 
38  private:
39  std::string _default_ns; // namespace given for "xmlns=..."
40  std::map<std::string, std::string> _prefix_to_ns_map; // namespaces given for "xmlns:prefix=..."
41  };
42 
43  XMLNamespaces::XMLNamespaces(const tinyxml2::XMLElement *element, const XMLNamespaces &parent_ns)
44  :_default_ns(parent_ns._default_ns)
45  ,_prefix_to_ns_map(parent_ns._prefix_to_ns_map)
46  {
47  if (!element) return;
48  for (auto attr_ptr = element->FirstAttribute(); attr_ptr; attr_ptr = attr_ptr->Next()) {
49  std::string attr_name = attr_ptr->Name();
50  if (attr_name == "xmlns") {
51  _default_ns = std::string(attr_ptr->Value());
52  } else if (attr_name.substr(0,6) == "xmlns:") {
53  _prefix_to_ns_map.insert(std::make_pair(attr_name.substr(6), std::string(attr_ptr->Value())));
54  }
55  }
56  }
57 
58  const tinyxml2::XMLElement *XMLNamespaces::findChildElement(const tinyxml2::XMLElement *element, const std::string &name, const std::string &ns) const
59  {
60  if (!element) return nullptr;
61  auto elem_ptr = element->FirstChildElement();
62  if (!elem_ptr) return nullptr;
63  XMLNamespaces child_ns(elem_ptr, *this);
64  if (child_ns.matches(elem_ptr->Name(), name, ns)) return elem_ptr;
65  return findSiblingElement(elem_ptr, name, ns);
66  }
67 
68  const tinyxml2::XMLElement *XMLNamespaces::findSiblingElement(const tinyxml2::XMLElement *child_elem, const std::string &name, const std::string &ns) const
69  {
70  if (!child_elem) return nullptr;
71  for (auto elem_ptr = child_elem->NextSiblingElement(); elem_ptr; elem_ptr = elem_ptr->NextSiblingElement()) {
72  XMLNamespaces child_ns(elem_ptr, *this);
73  if (child_ns.matches(elem_ptr->Name(), name, ns)) return elem_ptr;
74  }
75  return nullptr;
76  }
77 
78  const tinyxml2::XMLAttribute *XMLNamespaces::findAttribute(const tinyxml2::XMLElement *element, const std::string &name, const std::string &ns) const
79  {
80  const std::string &elem_ns = elementNamespace(element);
81  for (auto attr_ptr = element->FirstAttribute(); attr_ptr; attr_ptr = attr_ptr->Next()) {
82  XMLNamespaces attr_ns(*this);
83  attr_ns._default_ns = elem_ns;
84  if (attr_ns.matches(attr_ptr->Name(), name, ns)) return attr_ptr;
85  }
86  return nullptr;
87  }
88 
89  bool XMLNamespaces::matches(const std::string &prefixed_name, const std::string &name, const std::string &ns) const
90  {
91  std::string match_ns(_default_ns);
92  std::string match_name(prefixed_name);
93  auto pos = match_name.find_first_of(':');
94  if (pos != std::string::npos) {
95  auto prefix = match_name.substr(0,pos);
96  match_name.erase(0,pos+1);
97  auto it = _prefix_to_ns_map.find(prefix);
98  if (it != _prefix_to_ns_map.end()) {
99  match_ns = _prefix_to_ns_map.at(prefix);
100  } else {
101  match_ns = prefix;
102  }
103  }
104  return name == match_name && ns == match_ns;
105  }
106 
107  const std::string &XMLNamespaces::elementNamespace(const tinyxml2::XMLElement *element) const
108  {
109  std::string elem_name(element->Name());
110  auto pos = elem_name.find_first_of(':');
111  if (pos != std::string::npos) {
112  auto elem_prefix = elem_name.substr(0,pos);
113  auto it = _prefix_to_ns_map.find(elem_prefix);
114  if (it != _prefix_to_ns_map.end()) {
115  return _prefix_to_ns_map.at(elem_prefix);
116  } else {
117  static const std::string unknown("<Unknown>");
118  return unknown;
119  }
120  }
121  return _default_ns;
122  }
123 }
124 
126 {
127  return toi == other.toi && content_length == other.content_length && expires == other.expires &&
128  cache_control.no_cache == other.cache_control.no_cache && fec_oti == other.fec_oti &&
129  cache_control.cache_expires == other.cache_control.cache_expires && content_location == other.content_location &&
131  etag == other.etag;
132 }
133 
135  : _instance_id( instance_id )
136  , _instance_id_sent( instance_id - 1 )
137  , _global_fec_oti( fec_oti )
138  , _fdt_namespace( fdt_namespace )
139 {
140 }
141 
142 LibFlute::FileDeliveryTable::FileDeliveryTable(uint32_t instance_id, char* buffer, size_t len)
143  : _instance_id( instance_id )
144  , _instance_id_sent( instance_id - 1 )
145  , _global_fec_oti()
146 {
147  static const std::string mbms2007_ns("urn:3GPP:metadata:2007:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2
148  static const std::string mbms2012_ns("urn:3GPP:metadata:2012:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2
149  tinyxml2::XMLDocument doc(true, tinyxml2::COLLAPSE_WHITESPACE);
150  doc.Parse(buffer, len);
151  auto fdt_instance = doc.RootElement();
152  XMLNamespaces root_ns(fdt_instance);
153  auto fdt_ns = root_ns.elementNamespace(fdt_instance);
154  if (!root_ns.matches(fdt_instance->Name(), "FDT-Instance", fdt_ns)) {
155  throw "Root element is not FDT-Instance";
156  }
157 
158  if (fdt_ns == "") {
159  _fdt_namespace = FDT_NS_NONE;
160  } else if (fdt_ns == "http://www.example.com/flute") { // RFC 3926 Section 3.4.2
161  _fdt_namespace = FDT_NS_RFC3926;
162  } else if (fdt_ns == "urn:IETF:metadata:2005:FLUTE:FDT") { // 3GPP TS 26.346 Clause 7.2.10.1
163  _fdt_namespace = FDT_NS_DRAFT_2005;
164 // } else if (fdt_ns == "urn:ietf:params:xml:ns:fdt") { // RFC 6726 - FLUTEv2 - needs more work
165 // _fdt_namespace = FDT_NS_RFC6726;
166  } else if (fdt_ns == "urn:3GPP:metadata:2022:FLUTE:FDT") { // 3GPP TS 26.346 Clause L.6.1
167  _fdt_namespace = FDT_NS_3GPP_CONSOLIDATED_V2;
168  } else {
169  throw "FDT namespace not recognised";
170  }
171 
172  _expires = std::stoull(root_ns.findAttribute(fdt_instance, "Expires", fdt_ns)->Value());
173 
174  spdlog::debug("Received new FDT with instance ID {}: {}", instance_id, buffer);
175 
176  auto val = root_ns.findAttribute(fdt_instance, "FEC-OTI-FEC-Encoding-ID", fdt_ns);
177  if (val != nullptr) {
178  _global_fec_oti.encoding_id = static_cast<FecScheme>(strtoul(val->Value(), nullptr, 0));
179  }
180 
181  val = root_ns.findAttribute(fdt_instance, "FEC-OTI-FEC-Instance-ID", fdt_ns);
182  if (val != nullptr) {
183  _global_fec_oti.instance_id = strtoul(val->Value(), nullptr, 0);
184  }
185 
186  val = root_ns.findAttribute(fdt_instance, "FEC-OTI-Maximum-Source-Block-Length", fdt_ns);
187  if (val != nullptr) {
188  _global_fec_oti.max_source_block_length = strtoul(val->Value(), nullptr, 0);
189  }
190 
191  val = root_ns.findAttribute(fdt_instance, "FEC-OTI-Encoding-Symbol-Length", fdt_ns);
192  if (val != nullptr) {
193  _global_fec_oti.encoding_symbol_length = strtoul(val->Value(), nullptr, 0);
194  }
195 
196  val = root_ns.findAttribute(fdt_instance, "FEC-OTI-Max-Number-of-Encoding-Symbols", fdt_ns);
197  if (val != nullptr) {
198  _global_fec_oti.max_number_of_encoding_symbols = strtoul(val->Value(), nullptr, 0);
199  }
200 
201  for (auto file = root_ns.findChildElement(fdt_instance, "File", fdt_ns);
202  file != nullptr; file = root_ns.findSiblingElement(file, "File", fdt_ns)) {
203 
204  XMLNamespaces file_ns(file, root_ns);
205 
206  // File required attributes
207  auto toi_str = file_ns.findAttribute(file, "TOI", fdt_ns);
208  if (toi_str == nullptr) {
209  throw "Missing TOI attribute on File element";
210  }
211  uint32_t toi = strtoull(toi_str->Value(), nullptr, 0);
212 
213  auto content_location = file_ns.findAttribute(file, "Content-Location", fdt_ns);
214  if (content_location == nullptr) {
215  throw "Missing Content-Location attribute on File element";
216  }
217 
218  // File optional attributes
219  uint32_t content_length = 0;
220  val = file_ns.findAttribute(file, "Content-Length", fdt_ns);
221  if (val != nullptr) {
222  content_length = strtoull(val->Value(), nullptr, 0);
223  }
224 
225  uint32_t transfer_length = 0;
226  val = file_ns.findAttribute(file, "Transfer-Length", fdt_ns);
227  if (val != nullptr) {
228  transfer_length = strtoull(val->Value(), nullptr, 0);
229  } else {
230  transfer_length = content_length;
231  }
232 
233  auto content_md5 = std::string();
234  val = file_ns.findAttribute(file, "Content-MD5", fdt_ns);
235  if (val != nullptr) {
236  content_md5 = val->Value();
237  }
238 
239  auto content_encoding = std::string();
240  val = file_ns.findAttribute(file, "Content-Encoding", fdt_ns);
241  if (val != nullptr) {
242  content_encoding = val->Value();
243  }
244 
245  auto content_type = std::string();
246  val = file_ns.findAttribute(file, "Content-Type", fdt_ns);
247  if (val != nullptr) {
248  content_type = val->Value();
249  }
250 
251  auto encoding_id = _global_fec_oti.encoding_id;
252  val = file_ns.findAttribute(file, "FEC-OTI-FEC-Encoding-ID", fdt_ns);
253  if (val != nullptr) {
254  encoding_id = static_cast<FecScheme>(strtoul(val->Value(), nullptr, 0));
255  }
256 
257  auto fec_instance_id = _global_fec_oti.instance_id;
258  val = file_ns.findAttribute(file, "FEC-OTI-FEC-Instance-ID", fdt_ns);
259  if (val != nullptr) {
260  fec_instance_id = strtoul(val->Value(), nullptr, 0);
261  }
262 
263  auto max_source_block_length = _global_fec_oti.max_source_block_length;
264  val = file_ns.findAttribute(file, "FEC-OTI-Maximum-Source-Block-Length", fdt_ns);
265  if (val != nullptr) {
266  max_source_block_length = strtoul(val->Value(), nullptr, 0);
267  }
268 
269  auto encoding_symbol_length = _global_fec_oti.encoding_symbol_length;
270  val = file_ns.findAttribute(file, "FEC-OTI-Encoding-Symbol-Length", fdt_ns);
271  if (val != nullptr) {
272  encoding_symbol_length = strtoul(val->Value(), nullptr, 0);
273  }
274 
275  auto max_number_of_encoding_symbols = _global_fec_oti.max_number_of_encoding_symbols;
276  val = file_ns.findAttribute(file, "FEC-OTI-Max-Number-of-Encoding-Symbols", fdt_ns);
277  if (val != nullptr) {
278  max_number_of_encoding_symbols = strtoul(val->Value(), nullptr, 0);
279  }
280 
281  auto mbms2012_file_etag = "";
282  val = file_ns.findAttribute(file, "File-ETag", mbms2012_ns);
283  if (val != nullptr) {
284  mbms2012_file_etag = val->Value();
285  }
286 
287  // File optional elements
288 
289  bool no_cache = false;
290  //bool max_stale = false;
291  std::optional<uint64_t> cache_expires = std::nullopt;
292  auto cc = file_ns.findChildElement(file, "Cache-Control", mbms2007_ns);
293  if (cc) {
294  XMLNamespaces cc_ns(cc, file_ns);
295 
296  // mbms2007:Cache-Control optional elements
297 
298  auto no_cache_elem = cc_ns.findChildElement(cc, "no-cache", mbms2007_ns);
299  if (no_cache_elem) {
300  no_cache = true;
301  }
302 
303  //auto max_stale_elem = cc_ns.findChildElement(cc, "max-stale", mbms2007_ns);
304  //if (max_stale_elem) {
305  // max_stale = true;
306  //}
307 
308  auto expires_elem = cc_ns.findChildElement(cc, "Expires", mbms2007_ns);
309  if (expires_elem) {
310  cache_expires = strtoul(expires_elem->GetText(), nullptr, 0);
311  }
312  }
313 
314  FecOti fec_oti{
315  .encoding_id = (FecScheme)encoding_id,
316  .instance_id = fec_instance_id,
317  .transfer_length = transfer_length,
318  .encoding_symbol_length = encoding_symbol_length,
319  .max_source_block_length = max_source_block_length,
320  .max_number_of_encoding_symbols = max_number_of_encoding_symbols
321  };
322 
323  FileEntry fe{
324  toi,
325  std::string(content_location->Value()),
326  content_length,
327  content_md5,
328  content_type,
329  (cache_expires)?(cache_expires.value()):0,
330  fec_oti,
331  {
332  no_cache,
333  cache_expires
334  },
335  content_encoding,
336  mbms2012_file_etag
337  };
338  _file_entries.push_back(fe);
339  }
340 }
341 
343 {
344  if (_instance_id == _instance_id_sent) _instance_id++;
345  _file_entries.push_back(fe);
346 }
347 
348 auto LibFlute::FileDeliveryTable::remove(uint32_t toi) -> void
349 {
350  for (auto it = _file_entries.cbegin(); it != _file_entries.cend();) {
351  if (it->toi == toi) {
352  it = _file_entries.erase(it);
353  } else {
354  ++it;
355  }
356  }
357  if (_instance_id == _instance_id_sent) _instance_id++;
358 }
359 
360 auto LibFlute::FileDeliveryTable::to_string() const -> std::string {
361  tinyxml2::XMLDocument doc;
362  doc.InsertFirstChild( doc.NewDeclaration() );
363  auto root = doc.NewElement("FDT-Instance");
364  switch (_fdt_namespace) {
365  case FDT_NS_RFC3926:
366  // RFC 3926 Section 3.4.2
367  root->SetAttribute("xmlns", "http://www.example.com/flute");
368  break;
369  case FDT_NS_DRAFT_2005:
370  // 3GPP TS 26.346 Clause 7.2.10.1
371  root->SetAttribute("xmlns", "urn:IETF:metadata:2005:FLUTE:FDT");
372  break;
373 // case FDT_NS_RFC6726: // FLUTE v2 - Will need other things implementing to use this
374 // // RFC 6726
375 // root->SetAttribute("xmlns", "urn:ietf:params:xml:ns:fdt");
376 // break;
377  case FDT_NS_3GPP_CONSOLIDATED_V2:
378  // 3GPP TS 26.346 Clause L.6.1
379  root->SetAttribute("xmlns", "urn:3GPP:metadata:2022:FLUTE:FDT");
380  break;
381  default:
382  break;
383  }
384  root->SetAttribute("Expires", std::to_string(_expires).c_str());
385  root->SetAttribute("FEC-OTI-FEC-Encoding-ID", (unsigned)_global_fec_oti.encoding_id);
386  if (_global_fec_oti.instance_id) root->SetAttribute("FEC-OTI-FEC-Instance-ID", (unsigned)_global_fec_oti.instance_id);
387  root->SetAttribute("FEC-OTI-Maximum-Source-Block-Length", (unsigned)_global_fec_oti.max_source_block_length);
388  root->SetAttribute("FEC-OTI-Encoding-Symbol-Length", (unsigned)_global_fec_oti.encoding_symbol_length);
389  root->SetAttribute("xmlns:mbms2007", "urn:3GPP:metadata:2007:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2
390  root->SetAttribute("xmlns:mbms2012", "urn:3GPP:metadata:2012:MBMS:FLUTE:FDT"); // 3GPP TS 26.346 Clause 7.2.10.2
391  doc.InsertEndChild(root);
392 
393  for (const auto& file : _file_entries) {
394  auto f = doc.NewElement("File");
395  f->SetAttribute("TOI", file.toi);
396  f->SetAttribute("Content-Location", file.content_location.c_str());
397  f->SetAttribute("Content-Length", file.content_length);
398  if (file.fec_oti.transfer_length) f->SetAttribute("Transfer-Length", file.fec_oti.transfer_length);
399  if (!file.content_md5.empty()) f->SetAttribute("Content-MD5", file.content_md5.c_str());
400  if (!file.content_encoding.empty()) f->SetAttribute("Content-Encoding", file.content_encoding.c_str());
401  if (!file.content_type.empty()) f->SetAttribute("Content-Type", file.content_type.c_str());
402  if (file.fec_oti.encoding_id != _global_fec_oti.encoding_id)
403  f->SetAttribute("FEC-OTI-FEC-Encoding-ID", (unsigned)file.fec_oti.encoding_id);
404  if (file.fec_oti.instance_id != 0 && file.fec_oti.instance_id != _global_fec_oti.instance_id)
405  f->SetAttribute("FEC-OTI-FEC-Instance-ID", (unsigned)file.fec_oti.instance_id);
406  if (file.fec_oti.max_source_block_length != 0 &&
407  file.fec_oti.max_source_block_length != _global_fec_oti.max_source_block_length)
408  f->SetAttribute("FEC-OTI-Maximum-Source-Block-Length", (unsigned)file.fec_oti.max_source_block_length);
409  if (file.fec_oti.encoding_symbol_length != 0 &&
410  file.fec_oti.encoding_symbol_length != _global_fec_oti.encoding_symbol_length)
411  f->SetAttribute("FEC-OTI-Encoding-Symbol-Length", (unsigned)file.fec_oti.encoding_symbol_length);
412  if (!file.etag.empty()) f->SetAttribute("mbms2012:File-ETag", file.etag.c_str());
413  if (file.cache_control.no_cache || file.cache_control.cache_expires) {
414  auto cc = doc.NewElement("mbms2007:Cache-Control");
415  if (file.cache_control.no_cache) {
416  auto noc = doc.NewElement("mbms2007:no-cache");
417  noc->SetText("true");
418  cc->InsertEndChild(noc);
419  } else {
420  auto exp = doc.NewElement("mbms2007:Expires");
421  exp->SetText(std::to_string(file.expires).c_str());
422  cc->InsertEndChild(exp);
423  }
424  f->InsertEndChild(cc);
425  }
426  root->InsertEndChild(f);
427  }
428 
429 
430  tinyxml2::XMLPrinter printer;
431  doc.Print(&printer);
432  return std::string(printer.CStr());
433 }
FdtNamespace
FDT namespace enumeration.
uint32_t instance_id()
Get the FDT instance ID.
void remove(uint32_t toi)
Remove a file entry.
std::string to_string() const
Serialize the FDT to an XML string.
void add(const FileEntry &entry)
Add a file entry.
FileDeliveryTable(uint32_t instance_id, FecOti fec_oti, FdtNamespace fdt_namespace=FDT_NS_NONE)
Create an empty FDT.
static char doc[]
FecScheme
Error correction schemes.
Definition: flute_types.h:44
OTI values struct.
Definition: flute_types.h:51
uint32_t instance_id
Definition: flute_types.h:53
FecScheme encoding_id
Definition: flute_types.h:52
uint32_t max_source_block_length
Definition: flute_types.h:56
uint32_t max_number_of_encoding_symbols
Definition: flute_types.h:57
uint32_t encoding_symbol_length
Definition: flute_types.h:55
An entry for a file in the FDT.
std::optional< uint64_t > cache_expires
struct LibFlute::FileDeliveryTable::FileEntry::@0 cache_control
bool operator==(const FileEntry &other) const