libflute
Transmitter.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 <errno.h>
18 #include <fcntl.h>
19 #include <netinet/ip.h>
20 #include <netinet/udp.h>
21 #if HAVE_MMAP
22 #include <sys/mman.h>
23 #endif
24 #include <unistd.h>
25 
26 // Suppress warnings about MD5 being deprecated in later versions of OpenSSL
27 #define OPENSSL_SUPPRESS_DEPRECATED 1
28 #include <openssl/md5.h>
29 #include "../utils/base64.h"
30 
31 #include <zlib.h>
32 
33 #include <ctime>
34 #include <cstdio>
35 #include <chrono>
36 #include <cstring>
37 #include <exception>
38 #include <iostream>
39 #include <list>
40 #include <string>
41 #include <system_error>
42 
43 #include "spdlog/spdlog.h"
44 #include "File.h"
45 #include "IpSec.h"
46 
47 #include "Transmitter.h"
48 
49 namespace LibFlute {
50 
51 static void create_udp_pkt( char *udp_buffer, const boost::asio::ip::udp::endpoint &endpoint, const char *data, size_t data_len,
52  const boost::asio::ip::address &local_address );
53 static void create_ip_hdr( char *ip_buffer, const boost::asio::ip::udp::endpoint &endpoint, size_t pkt_size,
54  const boost::asio::ip::address &local_address );
55 static uint16_t calculate_sum( const uint8_t *buffer, size_t len );
56 static void write_uint16_be( uint8_t *buffer, uint16_t value );
57 static void write_uint32_be( uint8_t *buffer, uint32_t value );
58 
59 /*****************************************************************************
60  * Transmitter::FileDescription class
61  *****************************************************************************/
62 
63 Transmitter::FileDescription::FileDescription ( const std::string &content_location, const std::string &filename )
64  : _tsi()
65  , _file_entry({ .toi=0, .content_location=content_location})
67  , _filename()
68  , _file_handle(-1)
69  , _data(nullptr)
70  , _data_length(0)
71 {
72  _attach_file(filename);
73  _calculate_file_entry();
74 }
75 
76 Transmitter::FileDescription::FileDescription(const std::string &content_location, const std::vector<char> &data)
77  : _tsi()
78  , _file_entry({ .toi=0, .content_location=content_location})
80  , _filename()
81  , _file_handle(-1)
82  , _data(data.data())
83  , _data_length(data.size())
84 {
85  _calculate_file_entry();
86 }
87 
88 Transmitter::FileDescription::FileDescription(const std::string &content_location, const std::vector<unsigned char> &data)
89  : _tsi()
90  , _file_entry({ .toi=0, .content_location=content_location})
92  , _filename()
93  , _file_handle(-1)
94  , _data(reinterpret_cast<const char*>(data.data()))
95  , _data_length(data.size())
96 {
97  _calculate_file_entry();
98 }
99 
100 Transmitter::FileDescription::FileDescription(const std::string &content_location, const char *data, size_t length)
101  : _tsi()
102  , _file_entry({ .toi=0, .content_location=content_location})
104  , _filename()
105  , _file_handle(-1)
106  , _data(data)
107  , _data_length(data?length:0)
108 {
109  _calculate_file_entry();
110 }
111 
112 Transmitter::FileDescription::FileDescription(const std::string &content_location)
113  : _tsi()
114  , _file_entry({ .toi=0, .content_location=content_location})
116  , _filename()
117  , _file_handle(-1)
118  , _data(nullptr)
119  , _data_length(0)
120 {
121  _calculate_file_entry();
122 }
123 
125  : _tsi(other._tsi)
126  , _file_entry(other._file_entry)
127  , _compression_type(other._compression_type)
128  , _filename(other._filename)
129  , _file_handle(-1)
130  , _data(other._data)
131  , _data_length(other._data_length)
132 {
133  if (!_filename.empty()) {
134  if (other._file_handle >= 0) {
135  _file_handle = dup(other._file_handle);
136  }
137 #if HAVE_MMAP
138  // Map the file contents into memory
139  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
140 #else
141  // copy the file contents into a new memory block
142  char *data = new char[_data_length];
143  _data = data;
144  memcpy(data, other._data, _data_length);
145 #endif
146  }
147 }
148 
150  : _tsi(std::move(other._tsi))
151  , _file_entry(other._file_entry)
152  , _compression_type(other._compression_type)
153  , _filename(std::move(other._filename))
154  , _file_handle(other._file_handle)
155  , _data(other._data)
156  , _data_length(other._data_length)
157 {
158  other._data = nullptr;
159  other._data_length = 0;
160  other._file_handle = -1;
161 }
162 
164 {
165  _free_file_data();
166 }
167 
169 {
170  _tsi = other._tsi;
171  _file_entry = other._file_entry;
172  _compression_type = other._compression_type;
173  _filename = other._filename;
174  _file_handle = -1;
175  _data = other._data;
176  _data_length = other._data_length;
177 
178  if (!_filename.empty()) {
179  if (other._file_handle >= 0) {
180  _file_handle = dup(other._file_handle);
181  }
182 #if HAVE_MMAP
183  // Map the file contents into memory
184  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
185 #else
186  // copy the file contents into a new memory block
187  char *data = new char[_data_length];
188  _data = data;
189  memcpy(data, other._data, _data_length);
190 #endif
191  }
192 
193  return *this;
194 }
195 
197 {
198  _tsi = std::move(other._tsi);
199  _file_entry = other._file_entry;
200  _compression_type = other._compression_type;
201  _filename = std::move(other._filename);
202  _file_handle = other._file_handle;
203  other._file_handle = -1;
204 
205  _data = other._data;
206  other._data = nullptr;
207  _data_length = other._data_length;
208  other._data_length = 0;
209 
210  return *this;
211 }
212 
214 {
215  if (_tsi != other._tsi) return false;
216  if (_compression_type != other._compression_type) return false;
217 
218  // _file_entry
219  if (_file_entry != other._file_entry) return false;
220 
221  //if (_filename != other._filename) return false;
222 
223  if (_data_length != other._data_length) return false;
224 
225  if (_data == other._data) return true;
226  return memcmp(_data, other._data, _data_length) == 0;
227 }
228 
230 {
231  return _data;
232 }
233 
235 {
236  return _data_length;
237 }
238 
239 void Transmitter::FileDescription::_reset_toi()
240 {
241  // Remembers the TOI being vacated so Transmitter::send() can remove its now-stale FDT entry
242  // when it assigns a new one -- without this, a FileDescription whose content genuinely
243  // changes (as opposed to being resent unchanged) leaves the FDT entry for its previous TOI
244  // orphaned forever, since nothing else ever points at that old TOI again to clean it up.
245  // The FDT grows without bound as a result, one orphaned entry per content change.
246  if (_file_entry.toi != 0) _previous_toi = _file_entry.toi;
247  _file_entry.toi = 0;
248 }
249 
252 {
253  if (compression != _compression_type) {
254  _compression_type = compression;
255  switch (_compression_type) {
256  case COMPRESSION_GZIP:
257  _file_entry.content_encoding = "gzip";
258  break;
259  case COMPRESSION_DEFLATE:
260  _file_entry.content_encoding = "deflate";
261  break;
262  default:
263  _file_entry.content_encoding.clear();
264  break;
265  }
266  /* change in compression will change transmitted data, reset the TOI */
267  _reset_toi();
268  _calculate_file_entry();
269  }
270 
271  return *this;
272 }
273 
275 {
276  _file_entry.content_location = location;
277 
278  return *this;
279 }
280 
282 {
283  if (filename != _filename) {
284  _free_file_data();
285  _attach_file(filename);
286  /* Assume a change of filename changes the contents too and zero the TOI */
287  _reset_toi();
288  _calculate_file_entry();
289  }
290 
291  return *this;
292 }
293 
295 {
296  if (!data) data_length=0;
297  if (data != _data || _data_length != data_length) {
298  /* data area has changed in some way, do we need to reset the TOI? */
299  if (_data_length != data_length) {
300  /* data length has changed, reset the TOI */
301  _reset_toi();
302  } else if (data) {
303  if (!_data) {
304  if (data_length) {
305  /* data being added, reset the TOI */
306  _reset_toi();
307  }
308  } else if (data_length) {
309  /* had data before and have new data now, but are they the same? */
310  unsigned char md5[MD5_DIGEST_LENGTH];
311  MD5(reinterpret_cast<const unsigned char*>(data), data_length, md5);
312  if (_file_entry.content_md5 != base64_encode(md5, sizeof(md5))) {
313  /* data contents are different, reset TOI */
314  _reset_toi();
315  }
316  }
317  } else if (_data) {
318  /* data being removed, reset the TOI */
319  _reset_toi();
320  }
321 
322  _free_file_data();
323  _data = data;
324  _data_length = data_length;
325  _calculate_file_entry();
326  }
327 
328  return *this;
329 }
330 
332 {
333  return set_content(data.data(), data.size());
334 }
335 
337 {
338  return set_content(reinterpret_cast<const char*>(data.data()), data.size());
339 }
340 
342 {
343  _file_entry.content_type = content_type;
344  return *this;
345 }
346 
348 {
349  static bool is_set = false;
351  if (!is_set) {
352  std::tm ntp_epoch_tm = {.tm_mday=1, .tm_mon=0, .tm_year=0};
353  ntp_epoch = std::chrono::system_clock::from_time_t(std::mktime(&ntp_epoch_tm));
354  is_set = true;
355  }
356  return ntp_epoch;
357 }
358 
361 {
362  auto diff = std::chrono::duration_cast<std::chrono::seconds>(expiry_time - _get_ntp_epoch());
363  _file_entry.expires = diff.count();
364  _file_entry.cache_control.cache_expires = _file_entry.expires;
365 
366  return *this;
367 }
368 
370 {
371  auto durn = std::chrono::duration_cast<date_time_type::duration>(std::chrono::seconds(_file_entry.expires));
372  return _get_ntp_epoch() + durn;
373 }
374 
376 {
377  _file_entry.etag = etag;
378  return *this;
379 }
380 
381 const std::string &Transmitter::FileDescription::get_etag() const
382 {
383  return _file_entry.etag;
384 }
385 
387 {
388  if (static_cast<unsigned>(_file_entry.fec_oti.encoding_id) == 0) {
389  _file_entry.fec_oti.encoding_id = fec_oti.encoding_id;
390  }
391  if (!_file_entry.fec_oti.instance_id) {
392  _file_entry.fec_oti.instance_id = fec_oti.instance_id;
393  }
394  if (!_file_entry.fec_oti.transfer_length) {
395  _file_entry.fec_oti.transfer_length = fec_oti.transfer_length;
396  }
397  if (!_file_entry.fec_oti.encoding_symbol_length) {
398  _file_entry.fec_oti.encoding_symbol_length = fec_oti.encoding_symbol_length;
399  }
400  if (!_file_entry.fec_oti.max_source_block_length) {
401  _file_entry.fec_oti.max_source_block_length = fec_oti.max_source_block_length;
402  }
403  if (!_file_entry.fec_oti.max_number_of_encoding_symbols) {
404  _file_entry.fec_oti.max_number_of_encoding_symbols = fec_oti.max_number_of_encoding_symbols;
405  }
406  return *this;
407 }
408 
409 void Transmitter::FileDescription::_attach_file(const std::string &filename)
410 {
411  _filename = filename;
412  _file_handle = open(_filename.c_str(), O_RDONLY);
413  if (_file_handle < 0) {
414  throw std::system_error(errno, std::generic_category(), "Could not open the file");
415  }
416  // Get the size
417  off_t pos = lseek(_file_handle, 0, SEEK_END);
418  if (pos < 0) {
419  throw std::system_error(errno, std::generic_category(), "Could not find the file length");
420  }
421  _data_length = static_cast<size_t>(pos);
422  lseek(_file_handle, 0, SEEK_SET);
423 
424 #if HAVE_MMAP
425  // Map the file contents into memory
426  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
427 #else
428  // Load the file contents into memory
429  char *data = new char[_data_length];
430  _data = data;
431  ssize_t nread = read(_file_handle, data, _data_length);
432  if (nread < 0 || static_cast<size_t>(nread) != _data_length) {
433  throw std::system_error(errno, std::generic_category(), "Could not read the file contents");
434  }
435  close(_file_handle);
436  _file_handle = -1;
437 #endif
438 }
439 
440 void Transmitter::FileDescription::_free_file_data()
441 {
442  if (!_filename.empty()) {
443 #if HAVE_MMAP
444  if (_data) munmap(const_cast<char*>(_data), _data_length);
445  if (_file_handle >= 0) close(_file_handle);
446 #else
447  delete[] const_cast<char*>(_data);
448 #endif
449  _filename.clear();
450  }
451 }
452 
453 void Transmitter::FileDescription::_calculate_file_entry()
454 {
455  // Content length
456  _file_entry.content_length = _data_length;
457 
458  // Initial transfer length assumes no encoding, this may be changed on transmission
459  _file_entry.fec_oti.transfer_length = _data_length;
460 
461  // MD5 checksum
462  if (_data && _data_length) {
463  unsigned char md5[MD5_DIGEST_LENGTH];
464  MD5(reinterpret_cast<const unsigned char*>(_data), _data_length, md5);
465  _file_entry.content_md5 = base64_encode(md5, sizeof(md5));
466  } else {
467  _file_entry.content_md5.clear();
468  }
469 }
470 
471 /*****************************************************************************
472  * Transmitter class
473  *****************************************************************************/
474 
475 Transmitter::Transmitter ( const std::string& destination_address, short port,
476  uint64_t tsi, unsigned short mtu, uint32_t rate_limit,
477  boost::asio::io_context& io_context,
478  const std::optional<boost::asio::ip::udp::endpoint> &tunnel_endpoint,
479  Transmitter::FdtNamespace fdt_namespace, bool active,
480  const std::optional<std::string> &source_address )
481  : _endpoint(boost::asio::ip::make_address(destination_address), port)
482  , _source_address()
483  , _socket(io_context, _endpoint.protocol())
484  , _io_context(io_context)
485  , _send_timer(io_context)
486  , _fdt_timer(io_context)
487  , _tsi(tsi)
488  , _mtu(mtu)
489  , _files()
490  , _files_mutex()
491  , _mcast_address(destination_address)
492  , _rate_limit(rate_limit)
493  , _tunnel_endpoint(tunnel_endpoint)
494  , _tunnel_local_address()
495  , _active(active)
496 {
497  if (source_address) {
498  _source_address = boost::asio::ip::make_address(source_address.value());
499  }
500  _max_payload = mtu -
501  20 - // IPv4 header
502  8 - // UDP header
503  32 - // ALC Header with EXT_FDT and EXT_FTI
504  4; // SBN and ESI for compact no-code FEC
505  if (_tunnel_endpoint.has_value()) {
506  // Remove extra overhead for UDP tunnelling, if set
507  _max_payload -= 20 + // IPv4 header
508  8; // UDP header
509  boost::asio::ip::udp::socket local_socket(_io_context, _tunnel_endpoint.value().protocol());
510  local_socket.connect(_tunnel_endpoint.value());
511  _tunnel_local_address = local_socket.local_endpoint().address();
512  }
513  uint32_t max_source_block_length = 64;
514 
515  _socket.set_option(boost::asio::ip::multicast::enable_loopback(true));
516  _socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
517 
518  if (_source_address && !_tunnel_endpoint) {
519  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
520  }
521 
522  _fec_oti = FecOti{
524  .encoding_symbol_length = _max_payload,
525  .max_source_block_length = max_source_block_length};
526  _fdt = std::make_unique<FileDeliveryTable>(1, _fec_oti, fdt_namespace);
527 
528  if (_active) {
529  start_fdt_repeat_timer();
530  send_next_packet();
531  }
532 }
533 
534 Transmitter::~Transmitter() = default;
535 
536 auto Transmitter::udp_tunnel_address(const boost::asio::ip::udp::endpoint &new_tunnel_endpoint) -> Transmitter&
537 {
538  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint));
539 }
540 
541 auto Transmitter::udp_tunnel_address(boost::asio::ip::udp::endpoint &&new_tunnel_endpoint) -> Transmitter&
542 {
543  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::move(new_tunnel_endpoint)));
544 }
545 
546 auto Transmitter::udp_tunnel_address(const std::optional<boost::asio::ip::udp::endpoint> &new_tunnel_endpoint) -> Transmitter&
547 {
548  return udp_tunnel_address(std::move(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint)));
549 }
550 
551 auto Transmitter::udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint> &&new_tunnel_endpoint) -> Transmitter&
552 {
553  if (!!_tunnel_endpoint == !!new_tunnel_endpoint) {
554  /* change existing tunnel */
555  if (_tunnel_endpoint) _tunnel_endpoint = new_tunnel_endpoint;
556  } else if (_tunnel_endpoint) {
557  /* removing tunnel */
558  _max_payload += 20 + // IPv4 header
559  8; // UDP header
560  _tunnel_endpoint = std::nullopt;
561  } else {
562  /* new tunnel */
563  _tunnel_endpoint = std::move(new_tunnel_endpoint);
564  _max_payload -= 20 + // IPv4 header
565  8; // UDP header
566  }
567 
568  if (_tunnel_endpoint) {
569  boost::asio::ip::udp::socket local_socket(_io_context, _tunnel_endpoint.value().protocol());
570  local_socket.connect(_tunnel_endpoint.value());
571  _tunnel_local_address = local_socket.local_endpoint().address();
572  }
573  return *this;
574 }
575 
576 auto Transmitter::udp_tunnel_address(const std::nullopt_t&) -> Transmitter&
577 {
578  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::nullopt));
579 }
580 
581 auto Transmitter::endpoint(const std::string &address, uint32_t port) -> Transmitter&
582 {
583  return endpoint(boost::asio::ip::udp::endpoint(boost::asio::ip::make_address(address), port));
584 }
585 
586 auto Transmitter::endpoint(const boost::asio::ip::udp::endpoint &destination) -> Transmitter&
587 {
588  _endpoint = destination;
589  return *this;
590 }
591 
592 auto Transmitter::endpoint(boost::asio::ip::udp::endpoint &&destination) -> Transmitter&
593 {
594  _endpoint = std::move(destination);
595  return *this;
596 }
597 
598 auto Transmitter::source_address(const std::optional<boost::asio::ip::address> &source_address) -> Transmitter&
599 {
600  _source_address = source_address;
601  if (_source_address && !_tunnel_endpoint) {
602  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
603  }
604  return *this;
605 }
606 
607 auto Transmitter::source_address(std::optional<boost::asio::ip::address> &&source_address) -> Transmitter&
608 {
609  _source_address = std::move(source_address);
610  if (_source_address && !_tunnel_endpoint) {
611  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
612  }
613  return *this;
614 }
615 
616 auto Transmitter::enable_ipsec(uint32_t spi, const std::string& key) -> void
617 {
618  IpSec::enable_esp(spi, _mcast_address, IpSec::Direction::Out, key);
619 }
620 
621 auto Transmitter::handle_send_to(const boost::system::error_code& error) -> void
622 {
623  if (!error) {
624  }
625 }
626 
628 {
629  return std::chrono::duration_cast<std::chrono::seconds>(
630  std::chrono::system_clock::now().time_since_epoch()).count() +
631  2'208'988'800; /* add the difference in seconds between the Unix epoch (1 January 1970, 00:00:00 UTC)
632  and the NTP epoch (1 January 1900, 00:00:00 UTC) */
633 }
634 
635 auto Transmitter::send_fdt() -> void {
636  if (_fdt->file_entries().empty()) return;
637  _fdt->set_expires(seconds_since_epoch() + _fdt_repeat_interval * 2);
638  auto fdt = _fdt->to_string();
639  auto file = std::make_shared<File>(
640  0,
641  _fec_oti,
642  "",
643  "",
644  seconds_since_epoch() + _fdt_repeat_interval * 2,
645  (char*)fdt.c_str(),
646  fdt.length(),
647  true);
648  if (file) {
649  file->set_fdt_instance_id( _fdt->instance_id() );
650  spdlog::debug("Sending FDT instance {}:\n{}", _fdt->instance_id(), _fdt->to_string());
651  {
652  std::lock_guard<std::mutex> guard(_files_mutex);
653  _files.insert_or_assign(0, file);
654  }
655  _fdt->sent();
656  }
657 }
658 
660  const std::string& content_location,
661  const std::string& content_type,
662  uint32_t expires,
663  char* data,
664  size_t length) -> uint16_t
665 {
666  auto toi = _toi;
667  _toi++;
668  if (_toi == 0) _toi = 1; // clamp to >= 1 in case it wraps
669 
670  auto file = std::make_shared<File>(
671  toi,
672  _fec_oti,
673  content_location,
674  content_type,
675  expires,
676  data,
677  length);
678 
679  _fdt->add(file->meta());
680  send_fdt();
681  {
682  std::lock_guard<std::mutex> guard(_files_mutex);
683  _files.insert({toi, file});
684  }
685  return toi;
686 }
687 
688 auto Transmitter::send(const std::shared_ptr<Transmitter::FileDescription> &file_description) -> uint16_t
689 {
690  if (file_description->has_tsi() && file_description->tsi() != _tsi) {
691  // Reset TOI if the file_description is being used on a new TSI
692  file_description->toi(0);
693  spdlog::debug("Reset TOI for FileDescription");
694  }
695 
696  // Set the TSI and TOI for the FileDescription
697  file_description->tsi(_tsi);
698  bool is_resend = (file_description->toi() != 0);
699  if (file_description->toi() == 0) {
700  if (file_description->previous_toi() != 0) {
701  // This FileDescription's content changed since its last send (set_content()/
702  // set_compression() zeroed its TOI for exactly this reason) -- the FDT entry for its
703  // old TOI is otherwise never revisited once a new TOI is assigned below, and would sit
704  // in the FDT forever, growing it by one stale entry per content change.
705  _fdt->remove(file_description->previous_toi());
706  file_description->reset_previous_toi();
707  }
708  file_description->toi(_toi);
709  _toi++;
710  if (_toi == 0) _toi = 1; // clamp to >= 1 in case it wraps
711  spdlog::debug("Assigned new TOI {}", file_description->toi());
712  }
713 
714  // Copy in default FEC parameters if not already set
715  file_description->merge_fec_oti(_fec_oti);
716 
717  auto file = std::make_shared<File>(file_description);
718  {
719  std::lock_guard<std::mutex> guard(_files_mutex);
720  // A reused (carousel-repeat) FileDescription keeps the same TOI, but map::insert() is a
721  // no-op if that key is already present -- silently keeping the stale File and discarding
722  // the just-built one with the updated content. Use assignment so a resend actually
723  // replaces it.
724  _files[file_description->toi()] = file;
725  }
726  if (is_resend) {
727  // Without this, add() below unconditionally appends another <File> entry for the same
728  // TOI on every single carousel repetition, without ever removing the previous one (the
729  // FDT has no other dedup by TOI) -- the FDT grows without bound the longer the object
730  // stays in the carousel, and eventually becomes too large to serialise/parse correctly.
731  _fdt->remove(file_description->toi());
732  }
733  _fdt->add(file->meta());
734  send_fdt();
735  return file_description->toi();
736 }
737 
738 auto Transmitter::fdt_send_tick(const boost::system::error_code& error) -> void
739 {
740  if (error == boost::asio::error::operation_aborted) return;
741  if (_active) {
742  send_fdt();
743  start_fdt_repeat_timer();
744  }
745 }
746 
747 auto Transmitter::file_transmitted(uint32_t toi) -> void
748 {
749  {
750  std::lock_guard<std::mutex> guard(_files_mutex);
751  _files.erase(toi);
752  }
753  if (toi != 0) {
754  _fdt->remove(toi);
755  send_fdt();
756 
757  if (_completion_cb) {
758  _completion_cb(toi);
759  }
760  }
761 
762  {
763  std::lock_guard<std::mutex> guard(_files_mutex);
764  if (_deactivate_when_all_files_sent && _files.empty()) {
765  _complete_deactivation();
766  }
767  }
768 }
769 
770 auto Transmitter::send_next_packet() -> void
771 {
772  uint32_t bytes_queued = 0;
773 
774  if (!_active) return;
775  std::shared_ptr<File> file;
776  {
777  std::lock_guard<std::mutex> guard(_files_mutex);
778  for (auto& file_m : _files) {
779  auto &next_file = file_m.second;
780 
781  if (next_file && !next_file->complete()) {
782  file = next_file;
783  break;
784  }
785  }
786  }
787  if (file) {
788  auto symbols = file->get_next_symbols(_max_payload);
789 
790  if (symbols.size()) {
791  for(const auto& symbol : symbols) {
792  spdlog::debug("sending TOI {} SBN {} ID {}", file->meta().toi, symbol.source_block_number(), symbol.id() );
793  }
794  auto packet = std::make_shared<AlcPacket>(_tsi, file->meta().toi, file->meta().fec_oti, symbols, _max_payload, file->fdt_instance_id());
795  bytes_queued += packet->size();
796 
797  boost::asio::ip::udp::endpoint send_endpoint;
798  const char *data = nullptr;
799  size_t data_size = 0;
800  std::shared_ptr<std::vector<char>> tunnel_data;
801  if (_tunnel_endpoint) {
802  send_endpoint = _tunnel_endpoint.value();
803  data_size = packet->size() + 20 /* IP header */ + 8 /* UDP header */;
804  // Own the encapsulated packet buffer via a shared_ptr held by the
805  // async_send_to completion lambda below, so it stays alive until the
806  // send actually completes (a raw new[] here with no matching delete[]
807  // would leak on every tunnelled packet).
808  tunnel_data = std::make_shared<std::vector<char>>(data_size);
809  data = tunnel_data->data();
810  auto local_address = _source_address ? _source_address.value() : _tunnel_local_address;
811  create_udp_pkt(const_cast<char*>(data) + 20, _endpoint, packet->data(), packet->size(), local_address);
812  create_ip_hdr(const_cast<char*>(data), _endpoint, data_size, local_address);
813  } else {
814  send_endpoint = _endpoint;
815  data = packet->data();
816  data_size = packet->size();
817  }
818  _socket.async_send_to(
819  boost::asio::buffer(data, data_size),
820  send_endpoint,
821  [file, symbols, packet, tunnel_data, this](
822  const boost::system::error_code& error,
823  std::size_t bytes_transferred)
824  {
825  (void)packet;
826  (void)tunnel_data;
827  (void)bytes_transferred;
828  if (error) {
829  spdlog::debug("sent_to error: {}", error.message());
830  } else {
831  file->mark_completed(symbols, !error);
832  if (file->complete()) {
833  file_transmitted(file->meta().toi);
834  }
835  }
836  });
837  }
838  }
839  if (_active) {
840  if (!bytes_queued) {
841  _send_timer.expires_after(std::chrono::milliseconds(10));
842  _send_timer.async_wait( boost::bind(&Transmitter::send_next_packet, this));
843  } else {
844  if (_rate_limit == 0) {
845  boost::asio::post(_io_context, boost::bind(&Transmitter::send_next_packet, this));
846  } else {
847  auto send_duration = ((bytes_queued * 8.0) / (double)_rate_limit/1000.0) * 1000.0 * 1000.0;
848  spdlog::trace("Rate limiter: queued {} bytes, limit {} kbps, next send in {} us",
849  bytes_queued, _rate_limit, send_duration);
850  _send_timer.expires_after(std::chrono::microseconds(
851  static_cast<int>(ceil(send_duration))));
852  _send_timer.async_wait( boost::bind(&Transmitter::send_next_packet, this));
853  }
854  }
855  }
856 }
857 
858 auto Transmitter::activate() -> void
859 {
860  if (!_active) {
861  _deactivate_when_all_files_sent = false;
862  _active = true;
863  start_fdt_repeat_timer();
864  send_next_packet();
865  }
866 }
867 
868 auto Transmitter::deactivate(bool finish_file_transmissions) -> void
869 {
870  if (_active) {
871  if (finish_file_transmissions) {
872  std::lock_guard<std::mutex> guard(_files_mutex);
873  if (!_files.empty()) {
874  _deactivate_when_all_files_sent = true;
875  return;
876  }
877 
878  _complete_deactivation();
879  return;
880  }
881 
882  _complete_deactivation();
883  }
884 }
885 
886 auto Transmitter::_complete_deactivation() -> void
887 {
888  _deactivate_when_all_files_sent = false;
889  _active = false;
890  _fdt_timer.cancel();
891  _send_timer.cancel();
892 }
893 
894 auto Transmitter::start_fdt_repeat_timer() -> void
895 {
896  _fdt_timer.expires_after(std::chrono::seconds(_fdt_repeat_interval));
897  _fdt_timer.async_wait( boost::bind(&Transmitter::fdt_send_tick, this, boost::placeholders::_1));
898 }
899 
900 static void create_udp_pkt(char *udp_buffer, const boost::asio::ip::udp::endpoint &endpoint, const char *data, size_t data_len, const boost::asio::ip::address &local_address)
901 {
902  auto *udp_bytes = reinterpret_cast<uint8_t*>(udp_buffer);
903  const auto udp_length = static_cast<uint16_t>(data_len + 8);
904  const auto source_address = local_address.to_v4().to_uint();
905  const auto destination_address = endpoint.address().to_v4().to_uint();
906 
907  write_uint16_be(udp_bytes, endpoint.port());
908  write_uint16_be(udp_bytes + 2, endpoint.port());
909  write_uint16_be(udp_bytes + 4, udp_length);
910  write_uint16_be(udp_bytes + 6, 0);
911  memcpy(udp_buffer + 8, data, data_len);
912 
913  std::vector<uint8_t> checksum_bytes(12 + udp_length);
914  write_uint32_be(checksum_bytes.data(), source_address);
915  write_uint32_be(checksum_bytes.data() + 4, destination_address);
916  checksum_bytes[8] = 0;
917  checksum_bytes[9] = endpoint.protocol().protocol();
918  write_uint16_be(checksum_bytes.data() + 10, udp_length);
919  memcpy(checksum_bytes.data() + 12, udp_bytes, udp_length);
920 
921  write_uint16_be(udp_bytes + 6, calculate_sum(checksum_bytes.data(), checksum_bytes.size()));
922 }
923 
924 static void create_ip_hdr(char *ip_buffer, const boost::asio::ip::udp::endpoint &endpoint, size_t pkt_size, const boost::asio::ip::address &local_address)
925 {
926  auto *ip_bytes = reinterpret_cast<uint8_t*>(ip_buffer);
927 
928  memset(ip_bytes, 0, 20);
929  ip_bytes[0] = 0x45; // IPv4, 20-byte header
930  ip_bytes[1] = 0;
931  write_uint16_be(ip_bytes + 2, static_cast<uint16_t>(pkt_size));
932  write_uint16_be(ip_bytes + 4, 0);
933  write_uint16_be(ip_bytes + 6, 0);
934  ip_bytes[8] = 63;
935  ip_bytes[9] = endpoint.protocol().protocol();
936  write_uint32_be(ip_bytes + 12, local_address.to_v4().to_uint());
937  write_uint32_be(ip_bytes + 16, endpoint.address().to_v4().to_uint());
938  write_uint16_be(ip_bytes + 10, calculate_sum(ip_bytes, 20));
939 }
940 
941 static uint16_t calculate_sum(const uint8_t *buffer, size_t len)
942 {
943  uint32_t cksum = 0;
944 
945  while (len > 1) {
946  cksum += (static_cast<uint32_t>(buffer[0]) << 8) | static_cast<uint32_t>(buffer[1]);
947  len -= 2;
948  buffer += 2;
949  }
950  if (len > 0) {
951  cksum += static_cast<uint32_t>(buffer[0]) << 8;
952  }
953 
954  while (cksum >> 16) {
955  cksum = (cksum & 0xFFFF) + (cksum >> 16);
956  }
957 
958  return static_cast<uint16_t>(~cksum);
959 }
960 
961 static void write_uint16_be(uint8_t *buffer, uint16_t value)
962 {
963  buffer[0] = static_cast<uint8_t>((value >> 8) & 0xFF);
964  buffer[1] = static_cast<uint8_t>(value & 0xFF);
965 }
966 
967 static void write_uint32_be(uint8_t *buffer, uint32_t value)
968 {
969  buffer[0] = static_cast<uint8_t>((value >> 24) & 0xFF);
970  buffer[1] = static_cast<uint8_t>((value >> 16) & 0xFF);
971  buffer[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
972  buffer[3] = static_cast<uint8_t>(value & 0xFF);
973 }
974 
975 } // End namespace LibFlute
FdtNamespace
FDT namespace enumeration.
File Description object.
Definition: Transmitter.h:52
FileDescription & set_content_location(const std::string &location)
Set Content-Location.
size_t data_length()
Get the length in bytes of the data to be transmitted.
FileDescription & set_expiry_time(const date_time_type &expiry_time)
Change the file expiry time.
const char * data()
Get the data to be transmitted.
FileDescription & set_etag(const std::string &etag)
Set the ETag value for the file.
std::chrono::system_clock::time_point date_time_type
Definition: Transmitter.h:54
FileDescription & operator=(const FileDescription &other)
Copy operator.
FileDescription & set_content_type(const std::string &content_type)
Change the file content type.
FileDescription & set_content(const std::string &filename)
Change the file contents using a local file.
FileDescription & merge_fec_oti(const FecOti &fec_oti)
Merge the FecOti values.
FileDescription & set_compression(CompressionAlgorithm compression)
Set the compression algorithm.
bool operator==(const FileDescription &other) const
Equality operator.
date_time_type get_expiry_time() const
Get the currently set expiry time.
const std::string & get_etag() const
Get the current ETag value.
FLUTE transmitter class.
Definition: Transmitter.h:41
uint64_t seconds_since_epoch()
Convenience function to get the current timestamp for expiry calculation.
virtual ~Transmitter()
Default destructor.
const std::optional< boost::asio::ip::udp::endpoint > & udp_tunnel_address() const
Get UDP Tunnel Address.
Definition: Transmitter.h:435
const std::optional< boost::asio::ip::address > & source_address() const
Get the optional source address for the FLUTE session.
Definition: Transmitter.h:550
const boost::asio::ip::udp::endpoint & endpoint() const
Get UDP Address for FLUTE session.
Definition: Transmitter.h:513
uint16_t send(const std::string &content_location, const std::string &content_type, uint32_t expires, char *data, size_t length)
Transmit a file (deprecated).
void enable_ipsec(uint32_t spi, const std::string &aes_key)
Enable IPSEC ESP encryption of FLUTE payloads.
Transmitter(const std::string &destination_address, short port, uint64_t tsi, unsigned short mtu, uint32_t rate_limit, boost::asio::io_context &io_context, const std::optional< boost::asio::ip::udp::endpoint > &tunnel_endpoint=std::nullopt, FdtNamespace fdt_namespace=FileDeliveryTable::FDT_NS_NONE, bool active=true, const std::optional< std::string > &source_address=std::nullopt)
Constructor.
uint32_t rate_limit() const
Get Maximum Bit Rate.
Definition: Transmitter.h:493
void deactivate(bool finish_file_transmissions=false)
Deactivate the FLUTE session.
void activate()
Activate the FLUTE session.
void enable_esp(uint32_t spi, const std::string &dest_address, Direction direction, const std::string &key)
Definition: IpSec.cpp:122
static uint16_t calculate_sum(const uint8_t *buffer, size_t len)
static void create_udp_pkt(char *udp_buffer, const boost::asio::ip::udp::endpoint &endpoint, const char *data, size_t data_len, const boost::asio::ip::address &local_address)
static void write_uint32_be(uint8_t *buffer, uint32_t value)
static const Transmitter::FileDescription::date_time_type & _get_ntp_epoch()
static void write_uint16_be(uint8_t *buffer, uint16_t value)
static void create_ip_hdr(char *ip_buffer, const boost::asio::ip::udp::endpoint &endpoint, size_t pkt_size, const boost::asio::ip::address &local_address)
OTI values struct.
Definition: flute_types.h:51
uint64_t transfer_length
Definition: flute_types.h:54
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