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( uint16_t *buffer, size_t len );
56 
57 /*****************************************************************************
58  * Transmitter::FileDescription class
59  *****************************************************************************/
60 
61 Transmitter::FileDescription::FileDescription ( const std::string &content_location, const std::string &filename )
62  : _tsi()
63  , _file_entry({ .toi=0, .content_location=content_location})
65  , _filename()
66  , _file_handle(-1)
67  , _data(nullptr)
68  , _data_length(0)
69 {
70  _attach_file(filename);
71  _calculate_file_entry();
72 }
73 
74 Transmitter::FileDescription::FileDescription(const std::string &content_location, const std::vector<char> &data)
75  : _tsi()
76  , _file_entry({ .toi=0, .content_location=content_location})
78  , _filename()
79  , _file_handle(-1)
80  , _data(data.data())
81  , _data_length(data.size())
82 {
83  _calculate_file_entry();
84 }
85 
86 Transmitter::FileDescription::FileDescription(const std::string &content_location, const std::vector<unsigned char> &data)
87  : _tsi()
88  , _file_entry({ .toi=0, .content_location=content_location})
90  , _filename()
91  , _file_handle(-1)
92  , _data(reinterpret_cast<const char*>(data.data()))
93  , _data_length(data.size())
94 {
95  _calculate_file_entry();
96 }
97 
98 Transmitter::FileDescription::FileDescription(const std::string &content_location, const char *data, size_t length)
99  : _tsi()
100  , _file_entry({ .toi=0, .content_location=content_location})
102  , _filename()
103  , _file_handle(-1)
104  , _data(data)
105  , _data_length(data?length:0)
106 {
107  _calculate_file_entry();
108 }
109 
110 Transmitter::FileDescription::FileDescription(const std::string &content_location)
111  : _tsi()
112  , _file_entry({ .toi=0, .content_location=content_location})
114  , _filename()
115  , _file_handle(-1)
116  , _data(nullptr)
117  , _data_length(0)
118 {
119  _calculate_file_entry();
120 }
121 
123  : _tsi(other._tsi)
124  , _file_entry(other._file_entry)
125  , _compression_type(other._compression_type)
126  , _filename(other._filename)
127  , _file_handle(-1)
128  , _data(other._data)
129  , _data_length(other._data_length)
130 {
131  if (!_filename.empty()) {
132  if (other._file_handle >= 0) {
133  _file_handle = dup(other._file_handle);
134  }
135 #if HAVE_MMAP
136  // Map the file contents into memory
137  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
138 #else
139  // copy the file contents into a new memory block
140  char *data = new char[_data_length];
141  _data = data;
142  memcpy(_data, other._data, _data_length);
143 #endif
144  }
145 }
146 
148  : _tsi(std::move(other._tsi))
149  , _file_entry(other._file_entry)
150  , _compression_type(other._compression_type)
151  , _filename(std::move(other._filename))
152  , _file_handle(other._file_handle)
153  , _data(other._data)
154  , _data_length(other._data_length)
155 {
156  other._data = nullptr;
157  other._data_length = 0;
158  other._file_handle = -1;
159 }
160 
162 {
163  _free_file_data();
164 }
165 
167 {
168  _tsi = other._tsi;
169  _file_entry = other._file_entry;
170  _compression_type = other._compression_type;
171  _filename = other._filename;
172  _file_handle = -1;
173  _data = other._data;
174  _data_length = other._data_length;
175 
176  if (!_filename.empty()) {
177  if (other._file_handle >= 0) {
178  _file_handle = dup(other._file_handle);
179  }
180 #if HAVE_MMAP
181  // Map the file contents into memory
182  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
183 #else
184  // copy the file contents into a new memory block
185  char *data = new char[_data_length];
186  _data = data;
187  memcpy(_data, other._data, _data_length);
188 #endif
189  }
190 
191  return *this;
192 }
193 
195 {
196  _tsi = std::move(other._tsi);
197  _file_entry = other._file_entry;
198  _compression_type = other._compression_type;
199  _filename = std::move(other._filename);
200  _file_handle = other._file_handle;
201  other._file_handle = -1;
202 
203  _data = other._data;
204  other._data = nullptr;
205  _data_length = other._data_length;
206  other._data_length = 0;
207 
208  return *this;
209 }
210 
212 {
213  if (_tsi != other._tsi) return false;
214  if (_compression_type != other._compression_type) return false;
215 
216  // _file_entry
217  if (_file_entry != other._file_entry) return false;
218 
219  //if (_filename != other._filename) return false;
220 
221  if (_data_length != other._data_length) return false;
222 
223  if (_data == other._data) return true;
224  return memcmp(_data, other._data, _data_length) == 0;
225 }
226 
228 {
229  return _data;
230 }
231 
233 {
234  return _data_length;
235 }
236 
239 {
240  if (compression != _compression_type) {
241  _compression_type = compression;
242  switch (_compression_type) {
243  case COMPRESSION_GZIP:
244  _file_entry.content_encoding = "gzip";
245  break;
246  case COMPRESSION_DEFLATE:
247  _file_entry.content_encoding = "deflate";
248  break;
249  default:
250  _file_entry.content_encoding.clear();
251  break;
252  }
253  /* change in compression will change transmitted data, reset the TOI */
254  _file_entry.toi = 0;
255  _calculate_file_entry();
256  }
257 
258  return *this;
259 }
260 
262 {
263  _file_entry.content_location = location;
264 
265  return *this;
266 }
267 
269 {
270  if (filename != _filename) {
271  _free_file_data();
272  _attach_file(filename);
273  /* Assume a change of filename changes the contents too and zero the TOI */
274  _file_entry.toi = 0;
275  _calculate_file_entry();
276  }
277 
278  return *this;
279 }
280 
282 {
283  if (!data) data_length=0;
284  if (data != _data || _data_length != data_length) {
285  /* data area has changed in some way, do we need to reset the TOI? */
286  if (_data_length != data_length) {
287  /* data length has changed, reset the TOI */
288  _file_entry.toi = 0;
289  } else if (data) {
290  if (!_data) {
291  if (data_length) {
292  /* data being added, reset the TOI */
293  _file_entry.toi = 0;
294  }
295  } else if (data_length) {
296  /* had data before and have new data now, but are they the same? */
297  unsigned char md5[MD5_DIGEST_LENGTH];
298  MD5(reinterpret_cast<const unsigned char*>(data), data_length, md5);
299  if (_file_entry.content_md5 != base64_encode(md5, sizeof(md5))) {
300  /* data contents are different, reset TOI */
301  _file_entry.toi = 0;
302  }
303  }
304  } else if (_data) {
305  /* data being removed, reset the TOI */
306  _file_entry.toi = 0;
307  }
308 
309  _free_file_data();
310  _data = data;
311  _data_length = data_length;
312  _calculate_file_entry();
313  }
314 
315  return *this;
316 }
317 
319 {
320  return set_content(data.data(), data.size());
321 }
322 
324 {
325  return set_content(reinterpret_cast<const char*>(data.data()), data.size());
326 }
327 
329 {
330  _file_entry.content_type = content_type;
331  return *this;
332 }
333 
335 {
336  static bool is_set = false;
338  if (!is_set) {
339  std::tm ntp_epoch_tm = {.tm_mday=1, .tm_mon=0, .tm_year=0};
340  ntp_epoch = std::chrono::system_clock::from_time_t(std::mktime(&ntp_epoch_tm));
341  is_set = true;
342  }
343  return ntp_epoch;
344 }
345 
348 {
349  auto diff = std::chrono::duration_cast<std::chrono::seconds>(expiry_time - _get_ntp_epoch());
350  _file_entry.expires = diff.count();
351  _file_entry.cache_control.cache_expires = _file_entry.expires;
352 
353  return *this;
354 }
355 
357 {
358  auto durn = std::chrono::duration_cast<date_time_type::duration>(std::chrono::seconds(_file_entry.expires));
359  return _get_ntp_epoch() + durn;
360 }
361 
363 {
364  _file_entry.etag = etag;
365  return *this;
366 }
367 
368 const std::string &Transmitter::FileDescription::get_etag() const
369 {
370  return _file_entry.etag;
371 }
372 
374 {
375  if (static_cast<unsigned>(_file_entry.fec_oti.encoding_id) == 0) {
376  _file_entry.fec_oti.encoding_id = fec_oti.encoding_id;
377  }
378  if (!_file_entry.fec_oti.instance_id) {
379  _file_entry.fec_oti.instance_id = fec_oti.instance_id;
380  }
381  if (!_file_entry.fec_oti.transfer_length) {
382  _file_entry.fec_oti.transfer_length = fec_oti.transfer_length;
383  }
384  if (!_file_entry.fec_oti.encoding_symbol_length) {
385  _file_entry.fec_oti.encoding_symbol_length = fec_oti.encoding_symbol_length;
386  }
387  if (!_file_entry.fec_oti.max_source_block_length) {
388  _file_entry.fec_oti.max_source_block_length = fec_oti.max_source_block_length;
389  }
390  if (!_file_entry.fec_oti.max_number_of_encoding_symbols) {
391  _file_entry.fec_oti.max_number_of_encoding_symbols = fec_oti.max_number_of_encoding_symbols;
392  }
393  return *this;
394 }
395 
396 void Transmitter::FileDescription::_attach_file(const std::string &filename)
397 {
398  _filename = filename;
399  _file_handle = open(_filename.c_str(), O_RDONLY);
400  if (_file_handle < 0) {
401  throw std::system_error(errno, std::generic_category(), "Could not open the file");
402  }
403  // Get the size
404  off_t pos = lseek(_file_handle, 0, SEEK_END);
405  if (pos < 0) {
406  throw std::system_error(errno, std::generic_category(), "Could not find the file length");
407  }
408  _data_length = static_cast<size_t>(pos);
409  lseek(_file_handle, 0, SEEK_SET);
410 
411 #if HAVE_MMAP
412  // Map the file contents into memory
413  _data = reinterpret_cast<char*>(mmap(nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
414 #else
415  // Load the file contents into memory
416  char *data = new char[_data_length];
417  _data = data;
418  read(_file_handle, data, _data_length);
419  close(_file_handle);
420  _file_handle = -1;
421 #endif
422 }
423 
424 void Transmitter::FileDescription::_free_file_data()
425 {
426  if (!_filename.empty()) {
427 #if HAVE_MMAP
428  if (_data) munmap(const_cast<char*>(_data), _data_length);
429  if (_file_handle >= 0) close(_file_handle);
430 #else
431  delete[] const_cast<char*>(_data);
432 #endif
433  _filename.clear();
434  }
435 }
436 
437 void Transmitter::FileDescription::_calculate_file_entry()
438 {
439  // Content length
440  _file_entry.content_length = _data_length;
441 
442  // Initial transfer length assumes no encoding, this may be changed on transmission
443  _file_entry.fec_oti.transfer_length = _data_length;
444 
445  // MD5 checksum
446  if (_data && _data_length) {
447  unsigned char md5[MD5_DIGEST_LENGTH];
448  MD5(reinterpret_cast<const unsigned char*>(_data), _data_length, md5);
449  _file_entry.content_md5 = base64_encode(md5, sizeof(md5));
450  } else {
451  _file_entry.content_md5.clear();
452  }
453 }
454 
455 /*****************************************************************************
456  * Transmitter class
457  *****************************************************************************/
458 
459 Transmitter::Transmitter ( const std::string& destination_address, short port,
460  uint64_t tsi, unsigned short mtu, uint32_t rate_limit,
461  boost::asio::io_context& io_context,
462  const std::optional<boost::asio::ip::udp::endpoint> &tunnel_endpoint,
463  Transmitter::FdtNamespace fdt_namespace, bool active,
464  const std::optional<std::string> &source_address )
465  : _endpoint(boost::asio::ip::make_address(destination_address), port)
466  , _source_address()
467  , _socket(io_context, _endpoint.protocol())
468  , _io_context(io_context)
469  , _send_timer(io_context)
470  , _fdt_timer(io_context)
471  , _tsi(tsi)
472  , _mtu(mtu)
473  , _files()
474  , _files_mutex()
475  , _mcast_address(destination_address)
476  , _rate_limit(rate_limit)
477  , _tunnel_endpoint(tunnel_endpoint)
478  , _tunnel_local_address()
479  , _active(active)
480 {
481  if (source_address) {
482  _source_address = boost::asio::ip::make_address(source_address.value());
483  }
484  _max_payload = mtu -
485  20 - // IPv4 header
486  8 - // UDP header
487  32 - // ALC Header with EXT_FDT and EXT_FTI
488  4; // SBN and ESI for compact no-code FEC
489  if (_tunnel_endpoint.has_value()) {
490  // Remove extra overhead for UDP tunnelling, if set
491  _max_payload -= 20 + // IPv4 header
492  8; // UDP header
493  boost::asio::ip::udp::socket local_socket(_io_context, _tunnel_endpoint.value().protocol());
494  local_socket.connect(_tunnel_endpoint.value());
495  _tunnel_local_address = local_socket.local_endpoint().address();
496  }
497  uint32_t max_source_block_length = 64;
498 
499  _socket.set_option(boost::asio::ip::multicast::enable_loopback(true));
500  _socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
501 
502  if (_source_address && !_tunnel_endpoint) {
503  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
504  }
505 
506  _fec_oti = FecOti{
508  .encoding_symbol_length = _max_payload,
509  .max_source_block_length = max_source_block_length};
510  _fdt = std::make_unique<FileDeliveryTable>(1, _fec_oti, fdt_namespace);
511 
512  if (_active) {
513  start_fdt_repeat_timer();
514  send_next_packet();
515  }
516 }
517 
518 Transmitter::~Transmitter() = default;
519 
520 auto Transmitter::udp_tunnel_address(const boost::asio::ip::udp::endpoint &new_tunnel_endpoint) -> Transmitter&
521 {
522  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint));
523 }
524 
525 auto Transmitter::udp_tunnel_address(boost::asio::ip::udp::endpoint &&new_tunnel_endpoint) -> Transmitter&
526 {
527  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::move(new_tunnel_endpoint)));
528 }
529 
530 auto Transmitter::udp_tunnel_address(const std::optional<boost::asio::ip::udp::endpoint> &new_tunnel_endpoint) -> Transmitter&
531 {
532  return udp_tunnel_address(std::move(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint)));
533 }
534 
535 auto Transmitter::udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint> &&new_tunnel_endpoint) -> Transmitter&
536 {
537  if (!!_tunnel_endpoint == !!new_tunnel_endpoint) {
538  /* change existing tunnel */
539  if (_tunnel_endpoint) _tunnel_endpoint = new_tunnel_endpoint;
540  } else if (_tunnel_endpoint) {
541  /* removing tunnel */
542  _max_payload += 20 + // IPv4 header
543  8; // UDP header
544  _tunnel_endpoint = std::nullopt;
545  } else {
546  /* new tunnel */
547  _tunnel_endpoint = std::move(new_tunnel_endpoint);
548  _max_payload -= 20 + // IPv4 header
549  8; // UDP header
550  }
551 
552  if (_tunnel_endpoint) {
553  boost::asio::ip::udp::socket local_socket(_io_context, _tunnel_endpoint.value().protocol());
554  local_socket.connect(_tunnel_endpoint.value());
555  _tunnel_local_address = local_socket.local_endpoint().address();
556  }
557  return *this;
558 }
559 
560 auto Transmitter::udp_tunnel_address(const std::nullopt_t&) -> Transmitter&
561 {
562  return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::nullopt));
563 }
564 
565 auto Transmitter::endpoint(const std::string &address, uint32_t port) -> Transmitter&
566 {
567  return endpoint(boost::asio::ip::udp::endpoint(boost::asio::ip::make_address(address), port));
568 }
569 
570 auto Transmitter::endpoint(const boost::asio::ip::udp::endpoint &destination) -> Transmitter&
571 {
572  _endpoint = destination;
573  return *this;
574 }
575 
576 auto Transmitter::endpoint(boost::asio::ip::udp::endpoint &&destination) -> Transmitter&
577 {
578  _endpoint = std::move(destination);
579  return *this;
580 }
581 
582 auto Transmitter::source_address(const std::optional<boost::asio::ip::address> &source_address) -> Transmitter&
583 {
584  _source_address = source_address;
585  if (_source_address && !_tunnel_endpoint) {
586  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
587  }
588  return *this;
589 }
590 
591 auto Transmitter::source_address(std::optional<boost::asio::ip::address> &&source_address) -> Transmitter&
592 {
593  _source_address = std::move(source_address);
594  if (_source_address && !_tunnel_endpoint) {
595  _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
596  }
597  return *this;
598 }
599 
600 auto Transmitter::enable_ipsec(uint32_t spi, const std::string& key) -> void
601 {
602  IpSec::enable_esp(spi, _mcast_address, IpSec::Direction::Out, key);
603 }
604 
605 auto Transmitter::handle_send_to(const boost::system::error_code& error) -> void
606 {
607  if (!error) {
608  }
609 }
610 
612 {
613  return std::chrono::duration_cast<std::chrono::seconds>(
614  std::chrono::system_clock::now().time_since_epoch()).count() +
615  2'208'988'800; /* add the difference in seconds between the Unix epoch (1 January 1970, 00:00:00 UTC)
616  and the NTP epoch (1 January 1900, 00:00:00 UTC) */
617 }
618 
619 auto Transmitter::send_fdt() -> void {
620  if (_fdt->file_entries().empty()) return;
621  _fdt->set_expires(seconds_since_epoch() + _fdt_repeat_interval * 2);
622  auto fdt = _fdt->to_string();
623  auto file = std::make_shared<File>(
624  0,
625  _fec_oti,
626  "",
627  "",
628  seconds_since_epoch() + _fdt_repeat_interval * 2,
629  (char*)fdt.c_str(),
630  fdt.length(),
631  true);
632  if (file) {
633  file->set_fdt_instance_id( _fdt->instance_id() );
634  spdlog::debug("Sending FDT instance {}:\n{}", _fdt->instance_id(), _fdt->to_string());
635  {
636  std::lock_guard<std::mutex> guard(_files_mutex);
637  _files.insert_or_assign(0, file);
638  }
639  _fdt->sent();
640  }
641 }
642 
644  const std::string& content_location,
645  const std::string& content_type,
646  uint32_t expires,
647  char* data,
648  size_t length) -> uint16_t
649 {
650  auto toi = _toi;
651  _toi++;
652  if (_toi == 0) _toi = 1; // clamp to >= 1 in case it wraps
653 
654  auto file = std::make_shared<File>(
655  toi,
656  _fec_oti,
657  content_location,
658  content_type,
659  expires,
660  data,
661  length);
662 
663  _fdt->add(file->meta());
664  send_fdt();
665  {
666  std::lock_guard<std::mutex> guard(_files_mutex);
667  _files.insert({toi, file});
668  }
669  return toi;
670 }
671 
672 auto Transmitter::send(const std::shared_ptr<Transmitter::FileDescription> &file_description) -> uint16_t
673 {
674  if (file_description->has_tsi() && file_description->tsi() != _tsi) {
675  // Reset TOI if the file_description is being used on a new TSI
676  file_description->toi(0);
677  spdlog::debug("Reset TOI for FileDescription");
678  }
679 
680  // Set the TSI and TOI for the FileDescription
681  file_description->tsi(_tsi);
682  if (file_description->toi() == 0) {
683  file_description->toi(_toi);
684  _toi++;
685  if (_toi == 0) _toi = 1; // clamp to >= 1 in case it wraps
686  spdlog::debug("Assigned new TOI {}", file_description->toi());
687  }
688 
689  // Copy in default FEC parameters if not already set
690  file_description->merge_fec_oti(_fec_oti);
691 
692  auto file = std::make_shared<File>(file_description);
693  {
694  std::lock_guard<std::mutex> guard(_files_mutex);
695  _files.insert({file_description->toi(), file});
696  }
697  _fdt->add(file->meta());
698  send_fdt();
699  return file_description->toi();
700 }
701 
702 auto Transmitter::fdt_send_tick(const boost::system::error_code& error) -> void
703 {
704  if (error == boost::asio::error::operation_aborted) return;
705  if (_active) {
706  send_fdt();
707  start_fdt_repeat_timer();
708  }
709 }
710 
711 auto Transmitter::file_transmitted(uint32_t toi) -> void
712 {
713  {
714  std::lock_guard<std::mutex> guard(_files_mutex);
715  _files.erase(toi);
716  }
717  if (toi != 0) {
718  _fdt->remove(toi);
719  send_fdt();
720 
721  if (_completion_cb) {
722  _completion_cb(toi);
723  }
724  }
725 }
726 
727 auto Transmitter::send_next_packet() -> void
728 {
729  uint32_t bytes_queued = 0;
730 
731  if (!_active) return;
732  std::shared_ptr<File> file;
733  {
734  std::lock_guard<std::mutex> guard(_files_mutex);
735  for (auto& file_m : _files) {
736  auto &next_file = file_m.second;
737 
738  if (next_file && !next_file->complete()) {
739  file = next_file;
740  break;
741  }
742  }
743  }
744  if (file) {
745  auto symbols = file->get_next_symbols(_max_payload);
746 
747  if (symbols.size()) {
748  for(const auto& symbol : symbols) {
749  spdlog::debug("sending TOI {} SBN {} ID {}", file->meta().toi, symbol.source_block_number(), symbol.id() );
750  }
751  auto packet = std::make_shared<AlcPacket>(_tsi, file->meta().toi, file->meta().fec_oti, symbols, _max_payload, file->fdt_instance_id());
752  bytes_queued += packet->size();
753 
754  boost::asio::ip::udp::endpoint send_endpoint;
755  char *data = nullptr;
756  size_t data_size = 0;
757  if (_tunnel_endpoint) {
758  send_endpoint = _tunnel_endpoint.value();
759  data_size = packet->size() + 20 /* IP header */ + 8 /* UDP header */;
760  data = new char[data_size];
761  create_udp_pkt(data+20, _endpoint, packet->data(), packet->size(), _source_address?_source_address.value():_tunnel_local_address);
762  create_ip_hdr(data, _endpoint, data_size, _source_address?_source_address.value():_tunnel_local_address);
763  } else {
764  send_endpoint = _endpoint;
765  data = packet->data();
766  data_size = packet->size();
767  }
768  _socket.async_send_to(
769  boost::asio::buffer(data, data_size), send_endpoint,
770  [file, symbols, packet, this](
771  const boost::system::error_code& error,
772  std::size_t bytes_transferred)
773  {
774  if (error) {
775  spdlog::debug("sent_to error: {}", error.message());
776  } else {
777  file->mark_completed(symbols, !error);
778  if (file->complete()) {
779  file_transmitted(file->meta().toi);
780  }
781  }
782  });
783  if (_tunnel_endpoint) {
784  delete[] data;
785  }
786  }
787  }
788  if (_active) {
789  if (!bytes_queued) {
790  _send_timer.expires_from_now(boost::posix_time::milliseconds(10));
791  _send_timer.async_wait( boost::bind(&Transmitter::send_next_packet, this));
792  } else {
793  if (_rate_limit == 0) {
794  boost::asio::post(_io_context, boost::bind(&Transmitter::send_next_packet, this));
795  } else {
796  auto send_duration = ((bytes_queued * 8.0) / (double)_rate_limit/1000.0) * 1000.0 * 1000.0;
797  spdlog::trace("Rate limiter: queued {} bytes, limit {} kbps, next send in {} us",
798  bytes_queued, _rate_limit, send_duration);
799  _send_timer.expires_from_now(boost::posix_time::microseconds(
800  static_cast<int>(ceil(send_duration))));
801  _send_timer.async_wait( boost::bind(&Transmitter::send_next_packet, this));
802  }
803  }
804  }
805 }
806 
807 auto Transmitter::activate() -> void
808 {
809  if (!_active) {
810  _active = true;
811  start_fdt_repeat_timer();
812  send_next_packet();
813  }
814 }
815 
817 {
818  if (_active) {
819  _active = false;
820  _fdt_timer.cancel();
821  _send_timer.cancel();
822  }
823 }
824 
825 auto Transmitter::start_fdt_repeat_timer() -> void
826 {
827  _fdt_timer.expires_from_now(boost::posix_time::seconds(_fdt_repeat_interval));
828  _fdt_timer.async_wait( boost::bind(&Transmitter::fdt_send_tick, this, boost::placeholders::_1));
829 }
830 
831 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)
832 {
833  struct udp_pseudo_hdr {
834  in_addr_t source;
835  in_addr_t dest;
836  uint8_t reserved;
837  uint8_t protocol;
838  uint16_t length;
839  } *pseudo_hdr = reinterpret_cast<struct udp_pseudo_hdr*>(udp_buffer - sizeof(*pseudo_hdr));
840  struct udphdr *udp_hdr = reinterpret_cast<struct udphdr*>(udp_buffer);
841 
842  pseudo_hdr->source = htonl(local_address.to_v4().to_uint());
843  pseudo_hdr->dest = htonl(endpoint.address().to_v4().to_uint());
844  pseudo_hdr->reserved = 0;
845  pseudo_hdr->protocol = endpoint.protocol().protocol();
846  pseudo_hdr->length = htons(data_len + 8);
847 
848  udp_hdr->uh_sport = htons(endpoint.port());
849  udp_hdr->uh_dport = udp_hdr->uh_sport;
850  udp_hdr->uh_ulen = pseudo_hdr->length;
851  udp_hdr->uh_sum = 0;
852  memcpy(udp_buffer+8, data, data_len);
853 
854  udp_hdr->uh_sum = calculate_sum(reinterpret_cast<uint16_t*>(pseudo_hdr), data_len + 8 + 12);
855 }
856 
857 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)
858 {
859  struct iphdr *ip_hdr = reinterpret_cast<struct iphdr*>(ip_buffer);
860 
861  ip_hdr->version = IPVERSION;
862  ip_hdr->ihl = 5; // 20 bytes
863  ip_hdr->tos = 0;
864  ip_hdr->tot_len = htons(pkt_size);
865  ip_hdr->id = 0;
866  ip_hdr->frag_off = 0; // not fragmenting
867  ip_hdr->ttl = 63; // TTL 63 hops
868  ip_hdr->protocol = endpoint.protocol().protocol();
869  ip_hdr->check = 0;
870  ip_hdr->saddr = htonl(local_address.to_v4().to_uint());
871  ip_hdr->daddr = htonl(endpoint.address().to_v4().to_uint());
872 
873  ip_hdr->check = calculate_sum(reinterpret_cast<uint16_t*>(ip_hdr), 20);
874 }
875 
876 static uint16_t calculate_sum(uint16_t *buffer, size_t len)
877 {
878  uint32_t cksum = 0;
879 
880  while (len > 1) {
881  cksum += ntohs(*buffer);
882  len -= 2;
883  buffer++;
884  }
885  if (len > 0) {
886  cksum += (*reinterpret_cast<uint8_t*>(buffer)) << 8;
887  }
888 
889  while (cksum >> 16) {
890  cksum = (cksum & 0xFFFF) + (cksum >> 16);
891  }
892 
893  uint16_t result = htons(static_cast<uint16_t>(~cksum));
894 
895  return result;
896 }
897 
898 } // End namespace LibFlute
899 
FdtNamespace
FDT namespace enumeration.
File Description object.
Definition: Transmitter.h:51
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:53
FileDescription & operator=(const FileDescription &other)
Copy operator.
FileDescription & set_content_type(const std::string &content_type)
Change the file content type.
uint32_t toi() const
Get the TOI associated with this file description.
Definition: Transmitter.h:185
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:40
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:416
void deactivate()
Deactivate the FLUTE session.
const std::optional< boost::asio::ip::address > & source_address() const
Get the optional source address for the FLUTE session.
Definition: Transmitter.h:531
const boost::asio::ip::udp::endpoint & endpoint() const
Get UDP Address for FLUTE session.
Definition: Transmitter.h:494
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:474
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:124
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 uint16_t calculate_sum(uint16_t *buffer, size_t len)
static const Transmitter::FileDescription::date_time_type & _get_ntp_epoch()
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