19 #include <netinet/ip.h>
20 #include <netinet/udp.h>
27 #define OPENSSL_SUPPRESS_DEPRECATED 1
28 #include <openssl/md5.h>
29 #include "../utils/base64.h"
41 #include <system_error>
43 #include "spdlog/spdlog.h"
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 );
63 , _file_entry({ .toi=0, .content_location=content_location})
70 _attach_file(filename);
71 _calculate_file_entry();
76 , _file_entry({ .toi=0, .content_location=content_location})
81 , _data_length(data.size())
83 _calculate_file_entry();
88 , _file_entry({ .toi=0, .content_location=content_location})
92 , _data(
reinterpret_cast<const char*
>(data.data()))
93 , _data_length(data.size())
95 _calculate_file_entry();
100 , _file_entry({ .toi=0, .content_location=content_location})
105 , _data_length(data?length:0)
107 _calculate_file_entry();
112 , _file_entry({ .toi=0, .content_location=content_location})
119 _calculate_file_entry();
124 , _file_entry(other._file_entry)
125 , _compression_type(other._compression_type)
126 , _filename(other._filename)
129 , _data_length(other._data_length)
131 if (!_filename.empty()) {
132 if (other._file_handle >= 0) {
133 _file_handle = dup(other._file_handle);
137 _data =
reinterpret_cast<char*
>(mmap(
nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
140 char *
data =
new char[_data_length];
142 memcpy(_data, other._data, _data_length);
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)
154 , _data_length(other._data_length)
156 other._data =
nullptr;
157 other._data_length = 0;
158 other._file_handle = -1;
169 _file_entry = other._file_entry;
170 _compression_type = other._compression_type;
171 _filename = other._filename;
174 _data_length = other._data_length;
176 if (!_filename.empty()) {
177 if (other._file_handle >= 0) {
178 _file_handle = dup(other._file_handle);
182 _data =
reinterpret_cast<char*
>(mmap(
nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
185 char *data =
new char[_data_length];
187 memcpy(_data, other._data, _data_length);
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;
204 other._data =
nullptr;
205 _data_length = other._data_length;
206 other._data_length = 0;
213 if (_tsi != other._tsi)
return false;
214 if (_compression_type != other._compression_type)
return false;
217 if (_file_entry != other._file_entry)
return false;
221 if (_data_length != other._data_length)
return false;
223 if (_data == other._data)
return true;
224 return memcmp(_data, other._data, _data_length) == 0;
240 if (compression != _compression_type) {
241 _compression_type = compression;
242 switch (_compression_type) {
243 case COMPRESSION_GZIP:
244 _file_entry.content_encoding =
"gzip";
246 case COMPRESSION_DEFLATE:
247 _file_entry.content_encoding =
"deflate";
250 _file_entry.content_encoding.clear();
255 _calculate_file_entry();
263 _file_entry.content_location = location;
270 if (filename != _filename) {
272 _attach_file(filename);
275 _calculate_file_entry();
283 if (!data) data_length=0;
284 if (data != _data || _data_length != data_length) {
286 if (_data_length != data_length) {
295 }
else if (data_length) {
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))) {
311 _data_length = data_length;
312 _calculate_file_entry();
320 return set_content(data.data(), data.size());
325 return set_content(
reinterpret_cast<const char*
>(data.data()), data.size());
330 _file_entry.content_type = content_type;
336 static bool is_set =
false;
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));
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;
358 auto durn = std::chrono::duration_cast<date_time_type::duration>(std::chrono::seconds(_file_entry.expires));
364 _file_entry.etag = etag;
370 return _file_entry.etag;
375 if (
static_cast<unsigned>(_file_entry.fec_oti.encoding_id) == 0) {
376 _file_entry.fec_oti.encoding_id = fec_oti.
encoding_id;
378 if (!_file_entry.fec_oti.instance_id) {
379 _file_entry.fec_oti.instance_id = fec_oti.
instance_id;
381 if (!_file_entry.fec_oti.transfer_length) {
384 if (!_file_entry.fec_oti.encoding_symbol_length) {
387 if (!_file_entry.fec_oti.max_source_block_length) {
390 if (!_file_entry.fec_oti.max_number_of_encoding_symbols) {
396 void Transmitter::FileDescription::_attach_file(
const std::string &filename)
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");
404 off_t pos = lseek(_file_handle, 0, SEEK_END);
406 throw std::system_error(errno, std::generic_category(),
"Could not find the file length");
408 _data_length =
static_cast<size_t>(pos);
409 lseek(_file_handle, 0, SEEK_SET);
413 _data =
reinterpret_cast<char*
>(mmap(
nullptr, _data_length, PROT_READ, MAP_SHARED, _file_handle, 0));
416 char *data =
new char[_data_length];
418 read(_file_handle, data, _data_length);
424 void Transmitter::FileDescription::_free_file_data()
426 if (!_filename.empty()) {
428 if (_data) munmap(
const_cast<char*
>(_data), _data_length);
429 if (_file_handle >= 0) close(_file_handle);
431 delete[]
const_cast<char*
>(_data);
437 void Transmitter::FileDescription::_calculate_file_entry()
440 _file_entry.content_length = _data_length;
443 _file_entry.fec_oti.transfer_length = _data_length;
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));
451 _file_entry.content_md5.clear();
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,
465 : _endpoint(boost::asio::ip::make_address(destination_address), port)
467 , _socket(io_context, _endpoint.protocol())
468 , _io_context(io_context)
469 , _send_timer(io_context)
470 , _fdt_timer(io_context)
475 , _mcast_address(destination_address)
477 , _tunnel_endpoint(tunnel_endpoint)
478 , _tunnel_local_address()
482 _source_address = boost::asio::ip::make_address(
source_address.value());
489 if (_tunnel_endpoint.has_value()) {
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();
497 uint32_t max_source_block_length = 64;
499 _socket.set_option(boost::asio::ip::multicast::enable_loopback(
true));
500 _socket.set_option(boost::asio::ip::udp::socket::reuse_address(
true));
502 if (_source_address && !_tunnel_endpoint) {
503 _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
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);
513 start_fdt_repeat_timer();
522 return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint));
527 return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::move(new_tunnel_endpoint)));
532 return udp_tunnel_address(std::move(std::optional<boost::asio::ip::udp::endpoint>(new_tunnel_endpoint)));
537 if (!!_tunnel_endpoint == !!new_tunnel_endpoint) {
539 if (_tunnel_endpoint) _tunnel_endpoint = new_tunnel_endpoint;
540 }
else if (_tunnel_endpoint) {
544 _tunnel_endpoint = std::nullopt;
547 _tunnel_endpoint = std::move(new_tunnel_endpoint);
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();
562 return udp_tunnel_address(std::optional<boost::asio::ip::udp::endpoint>(std::nullopt));
567 return endpoint(boost::asio::ip::udp::endpoint(boost::asio::ip::make_address(address), port));
572 _endpoint = destination;
578 _endpoint = std::move(destination);
584 _source_address = source_address;
585 if (_source_address && !_tunnel_endpoint) {
586 _socket.bind(boost::asio::ip::udp::endpoint(_source_address.value(),0));
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));
605 auto Transmitter::handle_send_to(
const boost::system::error_code& error) ->
void
613 return std::chrono::duration_cast<std::chrono::seconds>(
614 std::chrono::system_clock::now().time_since_epoch()).count() +
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>(
628 seconds_since_epoch() + _fdt_repeat_interval * 2,
633 file->set_fdt_instance_id( _fdt->instance_id() );
634 spdlog::debug(
"Sending FDT instance {}:\n{}", _fdt->instance_id(), _fdt->to_string());
636 std::lock_guard<std::mutex> guard(_files_mutex);
637 _files.insert_or_assign(0, file);
644 const std::string& content_location,
645 const std::string& content_type,
648 size_t length) -> uint16_t
652 if (_toi == 0) _toi = 1;
654 auto file = std::make_shared<File>(
663 _fdt->add(file->meta());
666 std::lock_guard<std::mutex> guard(_files_mutex);
667 _files.insert({toi, file});
672 auto Transmitter::send(
const std::shared_ptr<Transmitter::FileDescription> &file_description) -> uint16_t
674 if (file_description->has_tsi() && file_description->tsi() != _tsi) {
676 file_description->toi(0);
677 spdlog::debug(
"Reset TOI for FileDescription");
681 file_description->tsi(_tsi);
682 if (file_description->toi() == 0) {
683 file_description->toi(_toi);
685 if (_toi == 0) _toi = 1;
686 spdlog::debug(
"Assigned new TOI {}", file_description->toi());
690 file_description->merge_fec_oti(_fec_oti);
692 auto file = std::make_shared<File>(file_description);
694 std::lock_guard<std::mutex> guard(_files_mutex);
695 _files.insert({file_description->toi(), file});
697 _fdt->add(file->meta());
699 return file_description->toi();
702 auto Transmitter::fdt_send_tick(
const boost::system::error_code& error) ->
void
704 if (error == boost::asio::error::operation_aborted)
return;
707 start_fdt_repeat_timer();
711 auto Transmitter::file_transmitted(uint32_t toi) ->
void
714 std::lock_guard<std::mutex> guard(_files_mutex);
721 if (_completion_cb) {
727 auto Transmitter::send_next_packet() ->
void
729 uint32_t bytes_queued = 0;
731 if (!_active)
return;
732 std::shared_ptr<File> file;
734 std::lock_guard<std::mutex> guard(_files_mutex);
735 for (
auto& file_m : _files) {
736 auto &next_file = file_m.second;
738 if (next_file && !next_file->complete()) {
745 auto symbols = file->get_next_symbols(_max_payload);
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() );
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();
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 + 8 ;
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);
764 send_endpoint = _endpoint;
765 data = packet->data();
766 data_size = packet->size();
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)
775 spdlog::debug(
"sent_to error: {}", error.message());
777 file->mark_completed(symbols, !error);
778 if (file->complete()) {
779 file_transmitted(file->meta().toi);
783 if (_tunnel_endpoint) {
790 _send_timer.expires_from_now(boost::posix_time::milliseconds(10));
791 _send_timer.async_wait( boost::bind(&Transmitter::send_next_packet,
this));
793 if (_rate_limit == 0) {
794 boost::asio::post(_io_context, boost::bind(&Transmitter::send_next_packet,
this));
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));
811 start_fdt_repeat_timer();
821 _send_timer.cancel();
825 auto Transmitter::start_fdt_repeat_timer() ->
void
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));
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)
833 struct udp_pseudo_hdr {
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);
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);
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;
852 memcpy(udp_buffer+8, data, data_len);
854 udp_hdr->uh_sum =
calculate_sum(
reinterpret_cast<uint16_t*
>(pseudo_hdr), data_len + 8 + 12);
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)
859 struct iphdr *ip_hdr =
reinterpret_cast<struct iphdr*
>(ip_buffer);
861 ip_hdr->version = IPVERSION;
864 ip_hdr->tot_len = htons(pkt_size);
866 ip_hdr->frag_off = 0;
868 ip_hdr->protocol = endpoint.protocol().protocol();
870 ip_hdr->saddr = htonl(local_address.to_v4().to_uint());
871 ip_hdr->daddr = htonl(endpoint.address().to_v4().to_uint());
873 ip_hdr->check =
calculate_sum(
reinterpret_cast<uint16_t*
>(ip_hdr), 20);
881 cksum += ntohs(*buffer);
886 cksum += (*
reinterpret_cast<uint8_t*
>(buffer)) << 8;
889 while (cksum >> 16) {
890 cksum = (cksum & 0xFFFF) + (cksum >> 16);
893 uint16_t result = htons(
static_cast<uint16_t
>(~cksum));
FdtNamespace
FDT namespace enumeration.
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
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.
virtual ~FileDescription()
Destructor.
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.
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.
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.
const boost::asio::ip::udp::endpoint & endpoint() const
Get UDP Address for FLUTE session.
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.
void activate()
Activate the FLUTE session.
void enable_esp(uint32_t spi, const std::string &dest_address, Direction direction, const std::string &key)
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)
uint32_t max_source_block_length
uint32_t max_number_of_encoding_symbols
uint32_t encoding_symbol_length