23 #include "spdlog/spdlog.h"
28 XMLNamespaces() :_default_ns() ,_prefix_to_ns_map() {};
29 XMLNamespaces(
const XMLNamespaces &to_copy) :_default_ns(to_copy._default_ns) ,_prefix_to_ns_map(to_copy._prefix_to_ns_map) {};
30 XMLNamespaces(XMLNamespaces &&to_move) :_default_ns(std::move(to_move._default_ns)) ,_prefix_to_ns_map(std::move(to_move._prefix_to_ns_map)) {};
31 XMLNamespaces(
const tinyxml2::XMLElement *element,
const XMLNamespaces &parent_ns = XMLNamespaces());
33 const tinyxml2::XMLElement *findChildElement(
const tinyxml2::XMLElement *element,
const std::string &name,
const std::string &ns = std::string())
const;
34 const tinyxml2::XMLElement *findSiblingElement(
const tinyxml2::XMLElement *child_elem,
const std::string &name,
const std::string &ns = std::string())
const;
35 const tinyxml2::XMLAttribute *findAttribute(
const tinyxml2::XMLElement *element,
const std::string &name,
const std::string &ns = std::string())
const;
36 bool matches(
const std::string &prefixed_name,
const std::string &name,
const std::string &ns = std::string())
const;
37 const std::string &elementNamespace(
const tinyxml2::XMLElement *element)
const;
40 std::string _default_ns;
41 std::map<std::string, std::string> _prefix_to_ns_map;
44 XMLNamespaces::XMLNamespaces(
const tinyxml2::XMLElement *element,
const XMLNamespaces &parent_ns)
45 :_default_ns(parent_ns._default_ns)
46 ,_prefix_to_ns_map(parent_ns._prefix_to_ns_map)
49 for (
auto attr_ptr = element->FirstAttribute(); attr_ptr; attr_ptr = attr_ptr->Next()) {
50 std::string attr_name = attr_ptr->Name();
51 if (attr_name ==
"xmlns") {
52 _default_ns = std::string(attr_ptr->Value());
53 }
else if (attr_name.substr(0,6) ==
"xmlns:") {
54 _prefix_to_ns_map.insert(std::make_pair(attr_name.substr(6), std::string(attr_ptr->Value())));
59 const tinyxml2::XMLElement *XMLNamespaces::findChildElement(
const tinyxml2::XMLElement *element,
const std::string &name,
const std::string &ns)
const
61 if (!element)
return nullptr;
62 auto elem_ptr = element->FirstChildElement();
63 if (!elem_ptr)
return nullptr;
64 XMLNamespaces child_ns(elem_ptr, *
this);
65 if (child_ns.matches(elem_ptr->Name(), name, ns))
return elem_ptr;
66 return findSiblingElement(elem_ptr, name, ns);
69 const tinyxml2::XMLElement *XMLNamespaces::findSiblingElement(
const tinyxml2::XMLElement *child_elem,
const std::string &name,
const std::string &ns)
const
71 if (!child_elem)
return nullptr;
72 for (
auto elem_ptr = child_elem->NextSiblingElement(); elem_ptr; elem_ptr = elem_ptr->NextSiblingElement()) {
73 XMLNamespaces child_ns(elem_ptr, *
this);
74 if (child_ns.matches(elem_ptr->Name(), name, ns))
return elem_ptr;
79 const tinyxml2::XMLAttribute *XMLNamespaces::findAttribute(
const tinyxml2::XMLElement *element,
const std::string &name,
const std::string &ns)
const
81 const std::string &elem_ns = elementNamespace(element);
82 for (
auto attr_ptr = element->FirstAttribute(); attr_ptr; attr_ptr = attr_ptr->Next()) {
83 XMLNamespaces attr_ns(*
this);
84 attr_ns._default_ns = elem_ns;
85 if (attr_ns.matches(attr_ptr->Name(), name, ns))
return attr_ptr;
90 bool XMLNamespaces::matches(
const std::string &prefixed_name,
const std::string &name,
const std::string &ns)
const
92 std::string match_ns(_default_ns);
93 std::string match_name(prefixed_name);
94 auto pos = match_name.find_first_of(
':');
95 if (pos != std::string::npos) {
96 auto prefix = match_name.substr(0,pos);
97 match_name.erase(0,pos+1);
98 auto it = _prefix_to_ns_map.find(prefix);
99 if (it != _prefix_to_ns_map.end()) {
100 match_ns = _prefix_to_ns_map.at(prefix);
105 return name == match_name && ns == match_ns;
108 const std::string &XMLNamespaces::elementNamespace(
const tinyxml2::XMLElement *element)
const
110 std::string elem_name(element->Name());
111 auto pos = elem_name.find_first_of(
':');
112 if (pos != std::string::npos) {
113 auto elem_prefix = elem_name.substr(0,pos);
114 auto it = _prefix_to_ns_map.find(elem_prefix);
115 if (it != _prefix_to_ns_map.end()) {
116 return _prefix_to_ns_map.at(elem_prefix);
118 static const std::string unknown(
"<Unknown>");
138 , _global_fec_oti( fec_oti )
139 , _fdt_namespace( fdt_namespace )
144 : _instance_id( instance_id )
145 , _instance_id_sent( instance_id - 1 )
148 static const std::string mbms2007_ns(
"urn:3GPP:metadata:2007:MBMS:FLUTE:FDT");
149 static const std::string mbms2012_ns(
"urn:3GPP:metadata:2012:MBMS:FLUTE:FDT");
150 tinyxml2::XMLDocument
doc(
true, tinyxml2::COLLAPSE_WHITESPACE);
151 doc.Parse(buffer, len);
152 auto fdt_instance =
doc.RootElement();
157 if (
doc.Error() || fdt_instance ==
nullptr) {
158 spdlog::warn(
"FDT parse failed ({}) at line {}, len={}",
159 doc.ErrorName() ?
doc.ErrorName() :
"?",
doc.ErrorLineNum(), len);
160 throw std::runtime_error(std::string(
"FDT XML parse failed: ") +
161 (
doc.ErrorName() ?
doc.ErrorName() :
"no root element"));
163 XMLNamespaces root_ns(fdt_instance);
164 auto fdt_ns = root_ns.elementNamespace(fdt_instance);
165 if (!root_ns.matches(fdt_instance->Name(),
"FDT-Instance", fdt_ns)) {
166 throw std::runtime_error(
"Root element is not FDT-Instance");
171 }
else if (fdt_ns ==
"http://www.example.com/flute") {
173 }
else if (fdt_ns ==
"urn:IETF:metadata:2005:FLUTE:FDT") {
177 }
else if (fdt_ns ==
"urn:3GPP:metadata:2022:FLUTE:FDT") {
180 throw std::runtime_error(
"FDT namespace not recognised");
183 _expires = std::stoull(root_ns.findAttribute(fdt_instance,
"Expires", fdt_ns)->Value());
185 spdlog::debug(
"Received new FDT with instance ID {}: {}",
instance_id, buffer);
187 auto val = root_ns.findAttribute(fdt_instance,
"FEC-OTI-FEC-Encoding-ID", fdt_ns);
188 if (val !=
nullptr) {
192 val = root_ns.findAttribute(fdt_instance,
"FEC-OTI-FEC-Instance-ID", fdt_ns);
193 if (val !=
nullptr) {
194 _global_fec_oti.
instance_id = strtoul(val->Value(),
nullptr, 0);
197 val = root_ns.findAttribute(fdt_instance,
"FEC-OTI-Maximum-Source-Block-Length", fdt_ns);
198 if (val !=
nullptr) {
202 val = root_ns.findAttribute(fdt_instance,
"FEC-OTI-Encoding-Symbol-Length", fdt_ns);
203 if (val !=
nullptr) {
207 val = root_ns.findAttribute(fdt_instance,
"FEC-OTI-Max-Number-of-Encoding-Symbols", fdt_ns);
208 if (val !=
nullptr) {
212 for (
auto file = root_ns.findChildElement(fdt_instance,
"File", fdt_ns);
213 file !=
nullptr; file = root_ns.findSiblingElement(file,
"File", fdt_ns)) {
215 XMLNamespaces file_ns(file, root_ns);
218 auto toi_str = file_ns.findAttribute(file,
"TOI", fdt_ns);
219 if (toi_str ==
nullptr) {
220 throw std::runtime_error(
"Missing TOI attribute on File element");
222 uint32_t toi = strtoull(toi_str->Value(),
nullptr, 0);
224 auto content_location = file_ns.findAttribute(file,
"Content-Location", fdt_ns);
225 if (content_location ==
nullptr) {
226 throw std::runtime_error(
"Missing Content-Location attribute on File element");
230 uint32_t content_length = 0;
231 val = file_ns.findAttribute(file,
"Content-Length", fdt_ns);
232 if (val !=
nullptr) {
233 content_length = strtoull(val->Value(),
nullptr, 0);
236 uint32_t transfer_length = 0;
237 val = file_ns.findAttribute(file,
"Transfer-Length", fdt_ns);
238 if (val !=
nullptr) {
239 transfer_length = strtoull(val->Value(),
nullptr, 0);
241 transfer_length = content_length;
244 auto content_md5 = std::string();
245 val = file_ns.findAttribute(file,
"Content-MD5", fdt_ns);
246 if (val !=
nullptr) {
247 content_md5 = val->Value();
250 auto content_encoding = std::string();
251 val = file_ns.findAttribute(file,
"Content-Encoding", fdt_ns);
252 if (val !=
nullptr) {
253 content_encoding = val->Value();
256 auto content_type = std::string();
257 val = file_ns.findAttribute(file,
"Content-Type", fdt_ns);
258 if (val !=
nullptr) {
259 content_type = val->Value();
263 val = file_ns.findAttribute(file,
"FEC-OTI-FEC-Encoding-ID", fdt_ns);
264 if (val !=
nullptr) {
265 encoding_id =
static_cast<FecScheme>(strtoul(val->Value(),
nullptr, 0));
268 auto fec_instance_id = _global_fec_oti.
instance_id;
269 val = file_ns.findAttribute(file,
"FEC-OTI-FEC-Instance-ID", fdt_ns);
270 if (val !=
nullptr) {
271 fec_instance_id = strtoul(val->Value(),
nullptr, 0);
275 val = file_ns.findAttribute(file,
"FEC-OTI-Maximum-Source-Block-Length", fdt_ns);
276 if (val !=
nullptr) {
277 max_source_block_length = strtoul(val->Value(),
nullptr, 0);
281 val = file_ns.findAttribute(file,
"FEC-OTI-Encoding-Symbol-Length", fdt_ns);
282 if (val !=
nullptr) {
283 encoding_symbol_length = strtoul(val->Value(),
nullptr, 0);
287 val = file_ns.findAttribute(file,
"FEC-OTI-Max-Number-of-Encoding-Symbols", fdt_ns);
288 if (val !=
nullptr) {
289 max_number_of_encoding_symbols = strtoul(val->Value(),
nullptr, 0);
292 auto mbms2012_file_etag =
"";
293 val = file_ns.findAttribute(file,
"File-ETag", mbms2012_ns);
294 if (val !=
nullptr) {
295 mbms2012_file_etag = val->Value();
300 bool no_cache =
false;
302 std::optional<uint64_t> cache_expires = std::nullopt;
303 auto cc = file_ns.findChildElement(file,
"Cache-Control", mbms2007_ns);
305 XMLNamespaces cc_ns(cc, file_ns);
309 auto no_cache_elem = cc_ns.findChildElement(cc,
"no-cache", mbms2007_ns);
319 auto expires_elem = cc_ns.findChildElement(cc,
"Expires", mbms2007_ns);
321 cache_expires = strtoul(expires_elem->GetText(),
nullptr, 0);
328 .transfer_length = transfer_length,
329 .encoding_symbol_length = encoding_symbol_length,
330 .max_source_block_length = max_source_block_length,
331 .max_number_of_encoding_symbols = max_number_of_encoding_symbols
336 std::string(content_location->Value()),
340 (cache_expires)?(cache_expires.value()):0,
349 _file_entries.push_back(fe);
355 if (_instance_id == _instance_id_sent) _instance_id++;
356 _file_entries.push_back(fe);
361 for (
auto it = _file_entries.cbegin(); it != _file_entries.cend();) {
362 if (it->toi == toi) {
363 it = _file_entries.erase(it);
368 if (_instance_id == _instance_id_sent) _instance_id++;
372 tinyxml2::XMLDocument
doc;
373 doc.InsertFirstChild(
doc.NewDeclaration() );
374 auto root =
doc.NewElement(
"FDT-Instance");
375 switch (_fdt_namespace) {
378 root->SetAttribute(
"xmlns",
"http://www.example.com/flute");
380 case FDT_NS_DRAFT_2005:
382 root->SetAttribute(
"xmlns",
"urn:IETF:metadata:2005:FLUTE:FDT");
388 case FDT_NS_3GPP_CONSOLIDATED_V2:
390 root->SetAttribute(
"xmlns",
"urn:3GPP:metadata:2022:FLUTE:FDT");
395 root->SetAttribute(
"Expires", std::to_string(_expires).c_str());
396 root->SetAttribute(
"FEC-OTI-FEC-Encoding-ID", (
unsigned)_global_fec_oti.encoding_id);
397 if (_global_fec_oti.instance_id) root->SetAttribute(
"FEC-OTI-FEC-Instance-ID", (
unsigned)_global_fec_oti.instance_id);
398 root->SetAttribute(
"FEC-OTI-Maximum-Source-Block-Length", (
unsigned)_global_fec_oti.max_source_block_length);
399 root->SetAttribute(
"FEC-OTI-Encoding-Symbol-Length", (
unsigned)_global_fec_oti.encoding_symbol_length);
400 root->SetAttribute(
"xmlns:mbms2007",
"urn:3GPP:metadata:2007:MBMS:FLUTE:FDT");
401 root->SetAttribute(
"xmlns:mbms2012",
"urn:3GPP:metadata:2012:MBMS:FLUTE:FDT");
402 doc.InsertEndChild(root);
404 for (
const auto& file : _file_entries) {
405 auto f =
doc.NewElement(
"File");
406 f->SetAttribute(
"TOI", file.toi);
407 f->SetAttribute(
"Content-Location", file.content_location.c_str());
408 f->SetAttribute(
"Content-Length", file.content_length);
409 if (file.fec_oti.transfer_length) f->SetAttribute(
"Transfer-Length", file.fec_oti.transfer_length);
410 if (!file.content_md5.empty()) f->SetAttribute(
"Content-MD5", file.content_md5.c_str());
411 if (!file.content_encoding.empty()) f->SetAttribute(
"Content-Encoding", file.content_encoding.c_str());
412 if (!file.content_type.empty()) f->SetAttribute(
"Content-Type", file.content_type.c_str());
413 if (file.fec_oti.encoding_id != _global_fec_oti.encoding_id)
414 f->SetAttribute(
"FEC-OTI-FEC-Encoding-ID", (
unsigned)file.fec_oti.encoding_id);
415 if (file.fec_oti.instance_id != 0 && file.fec_oti.instance_id != _global_fec_oti.instance_id)
416 f->SetAttribute(
"FEC-OTI-FEC-Instance-ID", (
unsigned)file.fec_oti.instance_id);
417 if (file.fec_oti.max_source_block_length != 0 &&
418 file.fec_oti.max_source_block_length != _global_fec_oti.max_source_block_length)
419 f->SetAttribute(
"FEC-OTI-Maximum-Source-Block-Length", (
unsigned)file.fec_oti.max_source_block_length);
420 if (file.fec_oti.encoding_symbol_length != 0 &&
421 file.fec_oti.encoding_symbol_length != _global_fec_oti.encoding_symbol_length)
422 f->SetAttribute(
"FEC-OTI-Encoding-Symbol-Length", (
unsigned)file.fec_oti.encoding_symbol_length);
423 if (!file.etag.empty()) f->SetAttribute(
"mbms2012:File-ETag", file.etag.c_str());
424 if (file.cache_control.no_cache || file.cache_control.cache_expires) {
425 auto cc =
doc.NewElement(
"mbms2007:Cache-Control");
426 if (file.cache_control.no_cache) {
427 auto noc =
doc.NewElement(
"mbms2007:no-cache");
428 noc->SetText(
"true");
429 cc->InsertEndChild(noc);
431 auto exp =
doc.NewElement(
"mbms2007:Expires");
432 exp->SetText(std::to_string(file.expires).c_str());
433 cc->InsertEndChild(exp);
435 f->InsertEndChild(cc);
437 root->InsertEndChild(f);
441 tinyxml2::XMLPrinter printer;
443 return std::string(printer.CStr());
FdtNamespace
FDT namespace enumeration.
@ FDT_NS_3GPP_CONSOLIDATED_V2
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.
FecScheme
Error correction schemes.
uint32_t max_source_block_length
uint32_t max_number_of_encoding_symbols
uint32_t encoding_symbol_length
An entry for a file in the FDT.
std::string content_location
std::optional< uint64_t > cache_expires
std::string content_encoding
struct LibFlute::FileDeliveryTable::FileEntry::@0 cache_control
bool operator==(const FileEntry &other) const