From c7869f8a51d772ad0654df19741ead8bfe50acfe Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 15 Jan 2021 08:22:09 +0100 Subject: Common 6b5db53: Update zmq.hpp, TCPReceiveServer, EDI decoder and output --- Makefile.am | 26 +- contrib/RemoteControl.cpp | 18 +- contrib/Socket.cpp | 12 +- contrib/Socket.h | 23 +- contrib/edi/AFPacket.cpp | 96 -- contrib/edi/AFPacket.h | 61 - contrib/edi/EDIConfig.h | 85 -- contrib/edi/Interleaver.cpp | 122 -- contrib/edi/Interleaver.h | 75 -- contrib/edi/PFT.cpp | 325 ----- contrib/edi/PFT.h | 81 -- contrib/edi/TagItems.cpp | 449 ------- contrib/edi/TagItems.h | 253 ---- contrib/edi/TagPacket.cpp | 80 -- contrib/edi/TagPacket.h | 61 - contrib/edi/Transport.cpp | 202 ---- contrib/edi/Transport.h | 73 -- contrib/edioutput/AFPacket.cpp | 96 ++ contrib/edioutput/AFPacket.h | 61 + contrib/edioutput/EDIConfig.h | 84 ++ contrib/edioutput/Interleaver.cpp | 122 ++ contrib/edioutput/Interleaver.h | 75 ++ contrib/edioutput/PFT.cpp | 324 +++++ contrib/edioutput/PFT.h | 82 ++ contrib/edioutput/TagItems.cpp | 449 +++++++ contrib/edioutput/TagItems.h | 253 ++++ contrib/edioutput/TagPacket.cpp | 80 ++ contrib/edioutput/TagPacket.h | 61 + contrib/edioutput/Transport.cpp | 202 ++++ contrib/edioutput/Transport.h | 73 ++ src/Outputs.cpp | 2 +- src/Outputs.h | 8 +- src/zmq.hpp | 2408 ++++++++++++++++++++++++++++++------- 33 files changed, 3976 insertions(+), 2446 deletions(-) delete mode 100644 contrib/edi/AFPacket.cpp delete mode 100644 contrib/edi/AFPacket.h delete mode 100644 contrib/edi/EDIConfig.h delete mode 100644 contrib/edi/Interleaver.cpp delete mode 100644 contrib/edi/Interleaver.h delete mode 100644 contrib/edi/PFT.cpp delete mode 100644 contrib/edi/PFT.h delete mode 100644 contrib/edi/TagItems.cpp delete mode 100644 contrib/edi/TagItems.h delete mode 100644 contrib/edi/TagPacket.cpp delete mode 100644 contrib/edi/TagPacket.h delete mode 100644 contrib/edi/Transport.cpp delete mode 100644 contrib/edi/Transport.h create mode 100644 contrib/edioutput/AFPacket.cpp create mode 100644 contrib/edioutput/AFPacket.h create mode 100644 contrib/edioutput/EDIConfig.h create mode 100644 contrib/edioutput/Interleaver.cpp create mode 100644 contrib/edioutput/Interleaver.h create mode 100644 contrib/edioutput/PFT.cpp create mode 100644 contrib/edioutput/PFT.h create mode 100644 contrib/edioutput/TagItems.cpp create mode 100644 contrib/edioutput/TagItems.h create mode 100644 contrib/edioutput/TagPacket.cpp create mode 100644 contrib/edioutput/TagPacket.h create mode 100644 contrib/edioutput/Transport.cpp create mode 100644 contrib/edioutput/Transport.h diff --git a/Makefile.am b/Makefile.am index e54ed95..c507f5b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -142,19 +142,19 @@ odr_audioenc_SOURCES = src/odr-audioenc.cpp \ contrib/ReedSolomon.cpp \ contrib/ReedSolomon.h \ contrib/ThreadsafeQueue.h \ - contrib/edi/AFPacket.cpp \ - contrib/edi/AFPacket.h \ - contrib/edi/EDIConfig.h \ - contrib/edi/Interleaver.cpp \ - contrib/edi/Interleaver.h \ - contrib/edi/PFT.cpp \ - contrib/edi/PFT.h \ - contrib/edi/TagItems.cpp \ - contrib/edi/TagItems.h \ - contrib/edi/TagPacket.cpp \ - contrib/edi/TagPacket.h \ - contrib/edi/Transport.cpp \ - contrib/edi/Transport.h \ + contrib/edioutput/AFPacket.cpp \ + contrib/edioutput/AFPacket.h \ + contrib/edioutput/EDIConfig.h \ + contrib/edioutput/Interleaver.cpp \ + contrib/edioutput/Interleaver.h \ + contrib/edioutput/PFT.cpp \ + contrib/edioutput/PFT.h \ + contrib/edioutput/TagItems.cpp \ + contrib/edioutput/TagItems.h \ + contrib/edioutput/TagPacket.cpp \ + contrib/edioutput/TagPacket.h \ + contrib/edioutput/Transport.cpp \ + contrib/edioutput/Transport.h \ $(FEC_SOURCES) bin_PROGRAMS = odr-audioenc$(EXEEXT) diff --git a/contrib/RemoteControl.cpp b/contrib/RemoteControl.cpp index 4adb90c..9ca8d22 100644 --- a/contrib/RemoteControl.cpp +++ b/contrib/RemoteControl.cpp @@ -29,6 +29,7 @@ #include #include "RemoteControl.h" +#include "zmq.hpp" using namespace std; @@ -424,7 +425,7 @@ void RemoteControllerZmq::recv_all(zmq::socket_t& pSocket, std::vector 0) ? ZMQ_SNDMORE : 0; - repSocket.send(zmsg, flag); + repSocket.send(zmsg, (--cohort_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } else if (msg.size() == 2 && command == "show") { @@ -523,8 +523,7 @@ void RemoteControllerZmq::process() zmq::message_t zmsg(ss.str().size()); memcpy(zmsg.data(), ss.str().data(), ss.str().size()); - int flag = (--r_size > 0) ? ZMQ_SNDMORE : 0; - repSocket.send(zmsg, flag); + repSocket.send(zmsg, (--r_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } catch (const ParameterError &err) { @@ -539,7 +538,7 @@ void RemoteControllerZmq::process() std::string value = rcs.get_param(module, parameter); zmq::message_t zmsg(value.size()); memcpy ((void*) zmsg.data(), value.data(), value.size()); - repSocket.send(zmsg, 0); + repSocket.send(zmsg, zmq::send_flags::none); } catch (const ParameterError &err) { send_fail_reply(repSocket, err.what()); @@ -576,4 +575,3 @@ void RemoteControllerZmq::process() } #endif - diff --git a/contrib/Socket.cpp b/contrib/Socket.cpp index d41ed1c..6a20429 100644 --- a/contrib/Socket.cpp +++ b/contrib/Socket.cpp @@ -862,9 +862,9 @@ TCPReceiveServer::~TCPReceiveServer() } } -vector TCPReceiveServer::receive() +shared_ptr TCPReceiveServer::receive() { - vector buffer; + shared_ptr buffer = make_shared(); m_queue.try_pop(buffer); // we can ignore try_pop()'s return value, because @@ -892,11 +892,12 @@ void TCPReceiveServer::process() } else if (r == 0) { sock.close(); + m_queue.push(make_shared()); break; } else { buf.resize(r); - m_queue.push(move(buf)); + m_queue.push(make_shared(move(buf))); } } catch (const TCPSocket::Interrupted&) { @@ -905,6 +906,11 @@ void TCPReceiveServer::process() catch (const TCPSocket::Timeout&) { num_timeouts++; } + catch (const runtime_error& e) { + sock.close(); + // TODO replace fprintf + fprintf(stderr, "TCP Receiver restarted after error: %s\n", e.what()); + } if (num_timeouts > max_num_timeouts) { sock.close(); diff --git a/contrib/Socket.h b/contrib/Socket.h index 8881be3..2291dd5 100644 --- a/contrib/Socket.h +++ b/contrib/Socket.h @@ -30,11 +30,12 @@ #include "ThreadsafeQueue.h" #include -#include -#include #include -#include +#include #include +#include +#include +#include #include #include @@ -265,6 +266,14 @@ class TCPDataDispatcher std::list m_connections; }; +struct TCPReceiveMessage { virtual ~TCPReceiveMessage() {}; }; +struct TCPReceiveMessageDisconnected : public TCPReceiveMessage { }; +struct TCPReceiveMessageEmpty : public TCPReceiveMessage { }; +struct TCPReceiveMessageData : public TCPReceiveMessage { + TCPReceiveMessageData(std::vector d) : data(d) {}; + std::vector data; +}; + /* A TCP Server to receive data, which abstracts the handling of connects and disconnects. */ class TCPReceiveServer { @@ -276,15 +285,15 @@ class TCPReceiveServer { void start(int listen_port, const std::string& address); - // Return a vector that contains up to blocksize bytes of data, or - // and empty vector if no data is available. - std::vector receive(); + // Return an instance of a subclass of TCPReceiveMessage that contains up to blocksize + // bytes of data, or TCPReceiveMessageEmpty if no data is available. + std::shared_ptr receive(); private: void process(); size_t m_blocksize = 0; - ThreadsafeQueue > m_queue; + ThreadsafeQueue > m_queue; std::atomic m_running = ATOMIC_VAR_INIT(false); std::string m_exception_data; std::thread m_listener_thread; diff --git a/contrib/edi/AFPacket.cpp b/contrib/edi/AFPacket.cpp deleted file mode 100644 index b38c38b..0000000 --- a/contrib/edi/AFPacket.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright (C) 2014 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output. - This implements an AF Packet as defined ETSI TS 102 821. - Also see ETSI TS 102 693 - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -#include "config.h" -#include "crc.h" -#include "AFPacket.h" -#include "TagItems.h" -#include "TagPacket.h" -#include -#include -#include -#include -#include -#include - -namespace edi { - -// Header PT field. AF packet contains TAG payload -const uint8_t AFHEADER_PT_TAG = 'T'; - -// AF Packet Major (3 bits) and Minor (4 bits) version -const uint8_t AFHEADER_VERSION = 0x10; // MAJ=1, MIN=0 - -AFPacket AFPacketiser::Assemble(TagPacket tag_packet) -{ - std::vector payload = tag_packet.Assemble(); - - if (m_verbose) - std::cerr << "Assemble AFPacket " << seq << std::endl; - - std::string pack_data("AF"); // SYNC - std::vector packet(pack_data.begin(), pack_data.end()); - - uint32_t taglength = payload.size(); - - if (m_verbose) - std::cerr << " AFPacket payload size " << payload.size() << std::endl; - - // write length into packet - packet.push_back((taglength >> 24) & 0xFF); - packet.push_back((taglength >> 16) & 0xFF); - packet.push_back((taglength >> 8) & 0xFF); - packet.push_back(taglength & 0xFF); - - // fill rest of header - packet.push_back(seq >> 8); - packet.push_back(seq & 0xFF); - seq++; - packet.push_back((have_crc ? 0x80 : 0) | AFHEADER_VERSION); // ar_cf: CRC=1 - packet.push_back(AFHEADER_PT_TAG); - - // insert payload, must have a length multiple of 8 bytes - packet.insert(packet.end(), payload.begin(), payload.end()); - - // calculate CRC over AF Header and payload - uint16_t crc = 0xffff; - crc = crc16(crc, &(packet.front()), packet.size()); - crc ^= 0xffff; - - if (m_verbose) - fprintf(stderr, " AFPacket crc %x\n", crc); - - packet.push_back((crc >> 8) & 0xFF); - packet.push_back(crc & 0xFF); - - if (m_verbose) - std::cerr << " AFPacket length " << packet.size() << std::endl; - - return packet; -} - -} diff --git a/contrib/edi/AFPacket.h b/contrib/edi/AFPacket.h deleted file mode 100644 index f2c4e35..0000000 --- a/contrib/edi/AFPacket.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2014 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output. - This implements an AF Packet as defined ETSI TS 102 821. - Also see ETSI TS 102 693 - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include -#include -#include "TagItems.h" -#include "TagPacket.h" - -namespace edi { - -typedef std::vector AFPacket; - -// ETSI TS 102 821, 6.1 AF packet structure -class AFPacketiser -{ - public: - AFPacketiser() : - m_verbose(false) {}; - AFPacketiser(bool verbose) : - m_verbose(verbose) {}; - - AFPacket Assemble(TagPacket tag_packet); - - private: - static const bool have_crc = true; - - uint16_t seq = 0; //counter that overflows at 0xFFFF - - bool m_verbose; -}; - -} - diff --git a/contrib/edi/EDIConfig.h b/contrib/edi/EDIConfig.h deleted file mode 100644 index 4f1df97..0000000 --- a/contrib/edi/EDIConfig.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - UDP and TCP transports and their configuration - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include -#include -#include -#include - -namespace edi { - -/** Configuration for EDI output */ - -struct destination_t { - virtual ~destination_t() {}; -}; - -// Can represent both unicast and multicast destinations -struct udp_destination_t : public destination_t { - std::string dest_addr; - std::string source_addr; - unsigned int source_port = 0; - unsigned int ttl = 10; -}; - -// TCP server that can accept multiple connections -struct tcp_server_t : public destination_t { - unsigned int listen_port = 0; - size_t max_frames_queued = 1024; -}; - -// TCP client that connects to one endpoint -struct tcp_client_t : public destination_t { - std::string dest_addr; - unsigned int dest_port = 0; - size_t max_frames_queued = 1024; -}; - -struct configuration_t { - unsigned chunk_len = 207; // RSk, data length of each chunk - unsigned fec = 0; // number of fragments that can be recovered - bool dump = false; // dump a file with the EDI packets - bool verbose = false; - bool enable_pft = false; // Enable protection and fragmentation - bool enable_transport_header = true; // Sets Addr, Source and Dest in PFT - unsigned int tagpacket_alignment = 0; - std::vector > destinations; - unsigned int dest_port = 0; // common destination port, because it's encoded in the transport layer - unsigned int latency_frames = 0; // if nonzero, enable interleaver with a latency of latency_frames * 24ms - - bool enabled() const { return destinations.size() > 0; } - bool interleaver_enabled() const { return latency_frames > 0; } - - void print() const; -}; - -} - - diff --git a/contrib/edi/Interleaver.cpp b/contrib/edi/Interleaver.cpp deleted file mode 100644 index f26a50e..0000000 --- a/contrib/edi/Interleaver.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - Interleaving of PFT fragments to increase robustness against - burst packet loss. - - This is possible because EDI has to assume that fragments may reach - the receiver out of order. - - */ -/* - This file is part of ODR-DabMux. - - ODR-DabMux is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - ODR-DabMux is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with ODR-DabMux. If not, see . - */ - -#include "Interleaver.h" -#include - -namespace edi { - -void Interleaver::SetLatency(size_t latency_frames) -{ - m_latency = latency_frames; -} - -Interleaver::fragment_vec Interleaver::Interleave(fragment_vec &fragments) -{ - m_fragment_count = fragments.size(); - - // Create vectors containing Fcount*latency fragments in total - // and store them into the deque - if (m_buffer.empty()) { - m_buffer.emplace_back(); - } - - auto& last_buffer = m_buffer.back(); - - for (auto& fragment : fragments) { - const bool last_buffer_is_complete = - (last_buffer.size() >= m_fragment_count * m_latency); - - if (last_buffer_is_complete) { - m_buffer.emplace_back(); - last_buffer = m_buffer.back(); - } - - last_buffer.push_back(std::move(fragment)); - } - - fragments.clear(); - - while ( not m_buffer.empty() and - (m_buffer.front().size() >= m_fragment_count * m_latency)) { - - auto& first_buffer = m_buffer.front(); - - assert(first_buffer.size() == m_fragment_count * m_latency); - - /* Assume we have 5 fragments per AF frame, and latency of 3. - * This will give the following strides: - * 0 1 2 - * +-------+-------+---+ - * | 0 1 | 2 3 | 4 | - * | | +---+ | - * | 5 6 | 7 | 8 9 | - * | +---+ | | - * |10 |11 12 |13 14 | - * +---+-------+-------+ - * - * ix will be 0, 5, 10, 1, 6 in the first loop - */ - - for (size_t i = 0; i < m_fragment_count; i++) { - const size_t ix = m_interleave_offset + m_fragment_count * m_stride; - m_interleaved_fragments.push_back(first_buffer.at(ix)); - - m_stride += 1; - if (m_stride >= m_latency) { - m_interleave_offset++; - m_stride = 0; - } - } - - if (m_interleave_offset >= m_fragment_count) { - m_interleave_offset = 0; - m_stride = 0; - m_buffer.pop_front(); - } - } - - std::vector interleaved_frags; - - const size_t n = std::min(m_fragment_count, m_interleaved_fragments.size()); - std::move(m_interleaved_fragments.begin(), - m_interleaved_fragments.begin() + n, - std::back_inserter(interleaved_frags)); - m_interleaved_fragments.erase( - m_interleaved_fragments.begin(), - m_interleaved_fragments.begin() + n); - - return interleaved_frags; -} - -} - - diff --git a/contrib/edi/Interleaver.h b/contrib/edi/Interleaver.h deleted file mode 100644 index 3029d5d..0000000 --- a/contrib/edi/Interleaver.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (C) 2017 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - Interleaving of PFT fragments to increase robustness against - burst packet loss. - - This is possible because EDI has to assume that fragments may reach - the receiver out of order. - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include -#include -#include -#include -#include "Log.h" -#include "PFT.h" - -namespace edi { - -class Interleaver { - public: - using fragment_vec = std::vector; - - /* Configure the interleaver to use latency_frames number of AF - * packets for interleaving. Total delay through the interleaver - * will be latency_frames * 24ms - */ - void SetLatency(size_t latency_frames); - - /* Move the fragments for an AF Packet into the interleaver and - * return interleaved fragments to be transmitted. - */ - fragment_vec Interleave(fragment_vec &fragments); - - private: - size_t m_latency = 0; - size_t m_fragment_count = 0; - size_t m_interleave_offset = 0; - size_t m_stride = 0; - - /* Buffer that accumulates enough fragments to interleave */ - std::deque m_buffer; - - /* Buffer that contains fragments that have been interleaved, - * to avoid that the interleaver output is too bursty - */ - std::deque m_interleaved_fragments; -}; - -} - diff --git a/contrib/edi/PFT.cpp b/contrib/edi/PFT.cpp deleted file mode 100644 index 1e3d4da..0000000 --- a/contrib/edi/PFT.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/* - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - Protection, Fragmentation and Transport. (PFT) - - Are supported: - Reed-Solomon and Fragmentation - - This implements part of PFT as defined ETSI TS 102 821. - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "PFT.h" -#include "crc.h" -#include "ReedSolomon.h" - -namespace edi { - -using namespace std; - -// An integer division that rounds up, i.e. ceil(a/b) -#define CEIL_DIV(a, b) (a % b == 0 ? a / b : a / b + 1) - -PFT::PFT() { } - -PFT::PFT(const configuration_t &conf) : - m_k(conf.chunk_len), - m_m(conf.fec), - m_dest_port(conf.dest_port), - m_pseq(0), - m_num_chunks(0), - m_verbose(conf.verbose) - { - if (m_k > 207) { - etiLog.level(warn) << - "EDI PFT: maximum chunk size is 207."; - throw std::out_of_range("EDI PFT Chunk size too large."); - } - - if (m_m > 5) { - etiLog.level(warn) << - "EDI PFT: high number of recoverable fragments" - " may lead to large overhead"; - // See TS 102 821, 7.2.1 Known values, list entry for 'm' - } - } - -RSBlock PFT::Protect(AFPacket af_packet) -{ - RSBlock rs_block; - - // number of chunks is ceil(afpacketsize / m_k) - // TS 102 821 7.2.2: c = ceil(l / k_max) - m_num_chunks = CEIL_DIV(af_packet.size(), m_k); - - if (m_verbose) { - fprintf(stderr, "Protect %zu chunks of size %zu\n", - m_num_chunks, af_packet.size()); - } - - // calculate size of chunk: - // TS 102 821 7.2.2: k = ceil(l / c) - // chunk_len does not include the 48 bytes of protection. - const size_t chunk_len = CEIL_DIV(af_packet.size(), m_num_chunks); - if (chunk_len > 207) { - std::stringstream ss; - ss << "Chunk length " << chunk_len << " too large (>207)"; - throw std::runtime_error(ss.str()); - } - - // The last RS chunk is zero padded - // TS 102 821 7.2.2: z = c*k - l - const size_t zero_pad = m_num_chunks * chunk_len - af_packet.size(); - - // Create the RS(k+p,k) encoder - const int firstRoot = 1; // Discovered by analysing EDI dump - const int gfPoly = 0x11d; - const bool reverse = false; - // The encoding has to be 255, 207 always, because the chunk has to - // be padded at the end, and not at the beginning as libfec would - // do - ReedSolomon rs_encoder(255, 207, reverse, gfPoly, firstRoot); - - // add zero padding to last chunk - for (size_t i = 0; i < zero_pad; i++) { - af_packet.push_back(0); - } - - if (m_verbose) { - fprintf(stderr, " add %zu zero padding\n", zero_pad); - } - - // Calculate RS for each chunk and assemble RS block - for (size_t i = 0; i < af_packet.size(); i+= chunk_len) { - vector chunk(207); - vector protection(PARITYBYTES); - - // copy chunk_len bytes into new chunk - memcpy(&chunk.front(), &af_packet[i], chunk_len); - - // calculate RS for chunk with padding - rs_encoder.encode(&chunk.front(), &protection.front(), 207); - - // Drop the padding - chunk.resize(chunk_len); - - // append new chunk and protection to the RS Packet - rs_block.insert(rs_block.end(), chunk.begin(), chunk.end()); - rs_block.insert(rs_block.end(), protection.begin(), protection.end()); - } - - return rs_block; -} - -vector< vector > PFT::ProtectAndFragment(AFPacket af_packet) -{ - const bool enable_RS = (m_m > 0); - - if (enable_RS) { - RSBlock rs_block = Protect(af_packet); - -#if 0 - fprintf(stderr, " af_packet (%zu):", af_packet.size()); - for (size_t i = 0; i < af_packet.size(); i++) { - fprintf(stderr, "%02x ", af_packet[i]); - } - fprintf(stderr, "\n"); - - fprintf(stderr, " rs_block (%zu):", rs_block.size()); - for (size_t i = 0; i < rs_block.size(); i++) { - fprintf(stderr, "%02x ", rs_block[i]); - } - fprintf(stderr, "\n"); -#endif - - // TS 102 821 7.2.2: s_max = MIN(floor(c*p/(m+1)), MTU - h)) - const size_t max_payload_size = ( m_num_chunks * PARITYBYTES ) / (m_m + 1); - - // Calculate fragment count and size - // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) - // l + c*p + z = length of RS block - const size_t num_fragments = CEIL_DIV(rs_block.size(), max_payload_size); - - // TS 102 821 7.2.2: ceil((l + c*p + z) / f) - const size_t fragment_size = CEIL_DIV(rs_block.size(), num_fragments); - - if (m_verbose) - fprintf(stderr, " PnF fragment_size %zu, num frag %zu\n", - fragment_size, num_fragments); - - vector< vector > fragments(num_fragments); - - for (size_t i = 0; i < num_fragments; i++) { - fragments[i].resize(fragment_size); - for (size_t j = 0; j < fragment_size; j++) { - const size_t ix = j*num_fragments + i; - if (ix < rs_block.size()) { - fragments[i][j] = rs_block[ix]; - } - else { - fragments[i][j] = 0; - } - } - } - - return fragments; - } - else { // No RS, only fragmentation - // TS 102 821 7.2.2: s_max = MTU - h - // Ethernet MTU is 1500, but maybe you are routing over a network which - // has some sort of packet encapsulation. Add some margin. - const size_t max_payload_size = 1400; - - // Calculate fragment count and size - // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) - // l + c*p + z = length of AF packet - const size_t num_fragments = CEIL_DIV(af_packet.size(), max_payload_size); - - // TS 102 821 7.2.2: ceil((l + c*p + z) / f) - const size_t fragment_size = CEIL_DIV(af_packet.size(), num_fragments); - vector< vector > fragments(num_fragments); - - for (size_t i = 0; i < num_fragments; i++) { - fragments[i].reserve(fragment_size); - - for (size_t j = 0; j < fragment_size; j++) { - const size_t ix = i*fragment_size + j; - if (ix < af_packet.size()) { - fragments[i].push_back(af_packet.at(ix)); - } - else { - break; - } - } - } - - return fragments; - } -} - -std::vector< PFTFragment > PFT::Assemble(AFPacket af_packet) -{ - vector< vector > fragments = ProtectAndFragment(af_packet); - vector< vector > pft_fragments; // These contain PF headers - - const bool enable_RS = (m_m > 0); - - unsigned int findex = 0; - - unsigned fcount = fragments.size(); - - // calculate size of chunk: - // TS 102 821 7.2.2: k = ceil(l / c) - // chunk_len does not include the 48 bytes of protection. - const size_t chunk_len = enable_RS ? - CEIL_DIV(af_packet.size(), m_num_chunks) : 0; - - // The last RS chunk is zero padded - // TS 102 821 7.2.2: z = c*k - l - const size_t zero_pad = enable_RS ? - m_num_chunks * chunk_len - af_packet.size() : 0; - - for (const auto &fragment : fragments) { - // Psync - std::string psync("PF"); - std::vector packet(psync.begin(), psync.end()); - - // Pseq - packet.push_back(m_pseq >> 8); - packet.push_back(m_pseq & 0xFF); - - // Findex - packet.push_back(findex >> 16); - packet.push_back(findex >> 8); - packet.push_back(findex & 0xFF); - findex++; - - // Fcount - packet.push_back(fcount >> 16); - packet.push_back(fcount >> 8); - packet.push_back(fcount & 0xFF); - - // RS (1 bit), transport (1 bit) and Plen (14 bits) - unsigned int plen = fragment.size(); - if (enable_RS) { - plen |= 0x8000; // Set FEC bit - } - - if (m_transport_header) { - plen |= 0x4000; // Set ADDR bit - } - - packet.push_back(plen >> 8); - packet.push_back(plen & 0xFF); - - if (enable_RS) { - packet.push_back(chunk_len); // RSk - packet.push_back(zero_pad); // RSz - } - - if (m_transport_header) { - // Source (16 bits) - packet.push_back(m_addr_source >> 8); - packet.push_back(m_addr_source & 0xFF); - - // Dest (16 bits) - packet.push_back(m_dest_port >> 8); - packet.push_back(m_dest_port & 0xFF); - } - - // calculate CRC over AF Header and payload - uint16_t crc = 0xffff; - crc = crc16(crc, &(packet.front()), packet.size()); - crc ^= 0xffff; - - packet.push_back((crc >> 8) & 0xFF); - packet.push_back(crc & 0xFF); - - // insert payload, must have a length multiple of 8 bytes - packet.insert(packet.end(), fragment.begin(), fragment.end()); - - pft_fragments.push_back(packet); - -#if 0 - fprintf(stderr, "* PFT pseq %d, findex %d, fcount %d, plen %d\n", - m_pseq, findex, fcount, plen & ~0xC000); -#endif - } - - m_pseq++; - - return pft_fragments; -} - -} - diff --git a/contrib/edi/PFT.h b/contrib/edi/PFT.h deleted file mode 100644 index 1019915..0000000 --- a/contrib/edi/PFT.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - Protection, Fragmentation and Transport. (PFT) - - Are supported: - Reed-Solomon and Fragmentation - - This implements part of PFT as defined ETSI TS 102 821. - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include -#include -#include -#include -#include "AFPacket.h" -#include "Log.h" -#include "ReedSolomon.h" -#include "EDIConfig.h" - -namespace edi { - -typedef std::vector RSBlock; -typedef std::vector PFTFragment; - -class PFT -{ - public: - static constexpr int PARITYBYTES = 48; - - PFT(); - PFT(const configuration_t& conf); - - // return a list of PFT fragments with the correct - // PFT headers - std::vector< PFTFragment > Assemble(AFPacket af_packet); - - // Apply Reed-Solomon FEC to the AF Packet - RSBlock Protect(AFPacket af_packet); - - // Cut a RSBlock into several fragments that can be transmitted - std::vector< std::vector > ProtectAndFragment(AFPacket af_packet); - - private: - unsigned int m_k = 207; // length of RS data word - unsigned int m_m = 3; // number of fragments that can be recovered if lost - unsigned int m_dest_port = 12000; // Destination port for transport header - uint16_t m_pseq = 0; - size_t m_num_chunks = 0; - bool m_verbose = false; - - bool m_transport_header = false; - const uint16_t m_addr_source = 0; -}; - -} - diff --git a/contrib/edi/TagItems.cpp b/contrib/edi/TagItems.cpp deleted file mode 100644 index 9746469..0000000 --- a/contrib/edi/TagItems.cpp +++ /dev/null @@ -1,449 +0,0 @@ -/* - EDI output. - This defines a few TAG items as defined ETSI TS 102 821 and - ETSI TS 102 693 - - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "config.h" -#include "TagItems.h" -#include -#include -#include -#include -#include - -namespace edi { - -TagStarPTR::TagStarPTR(const std::string& protocol) - : m_protocol(protocol) -{ - if (m_protocol.size() != 4) { - throw std::runtime_error("TagStarPTR protocol invalid length"); - } -} - -std::vector TagStarPTR::Assemble() -{ - //std::cerr << "TagItem *ptr" << std::endl; - std::string pack_data("*ptr"); - std::vector packet(pack_data.begin(), pack_data.end()); - - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - packet.push_back(0x40); - - packet.insert(packet.end(), m_protocol.begin(), m_protocol.end()); - - // Major - packet.push_back(0); - packet.push_back(0); - - // Minor - packet.push_back(0); - packet.push_back(0); - return packet; -} - -std::vector TagDETI::Assemble() -{ - std::string pack_data("deti"); - std::vector packet(pack_data.begin(), pack_data.end()); - packet.reserve(256); - - // Placeholder for length - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - - uint8_t fct = dlfc % 250; - uint8_t fcth = dlfc / 250; - - - uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); - packet.push_back(detiHeader >> 8); - packet.push_back(detiHeader & 0xFF); - - uint32_t etiHeader = mnsc | (rfu << 16) | (rfa << 17) | - (fp << 19) | (mid << 22) | (stat << 24); - packet.push_back((etiHeader >> 24) & 0xFF); - packet.push_back((etiHeader >> 16) & 0xFF); - packet.push_back((etiHeader >> 8) & 0xFF); - packet.push_back(etiHeader & 0xFF); - - if (atstf) { - packet.push_back(utco); - - packet.push_back((seconds >> 24) & 0xFF); - packet.push_back((seconds >> 16) & 0xFF); - packet.push_back((seconds >> 8) & 0xFF); - packet.push_back(seconds & 0xFF); - - packet.push_back((tsta >> 16) & 0xFF); - packet.push_back((tsta >> 8) & 0xFF); - packet.push_back(tsta & 0xFF); - } - - if (ficf) { - for (size_t i = 0; i < fic_length; i++) { - packet.push_back(fic_data[i]); - } - } - - if (rfudf) { - packet.push_back((rfud >> 16) & 0xFF); - packet.push_back((rfud >> 8) & 0xFF); - packet.push_back(rfud & 0xFF); - } - - // calculate and update size - // remove TAG name and TAG length fields and convert to bits - uint32_t taglength = (packet.size() - 8) * 8; - - // write length into packet - packet[4] = (taglength >> 24) & 0xFF; - packet[5] = (taglength >> 16) & 0xFF; - packet[6] = (taglength >> 8) & 0xFF; - packet[7] = taglength & 0xFF; - - dlfc = (dlfc+1) % 5000; - - /* - std::cerr << "TagItem deti, packet.size " << packet.size() << std::endl; - std::cerr << " fic length " << fic_length << std::endl; - std::cerr << " length " << taglength / 8 << std::endl; - */ - return packet; -} - -void TagDETI::set_edi_time(const std::time_t t, int tai_utc_offset) -{ - utco = tai_utc_offset - 32; - - const std::time_t posix_timestamp_1_jan_2000 = 946684800; - - seconds = t - posix_timestamp_1_jan_2000 + utco; -} - -std::vector TagESTn::Assemble() -{ - std::string pack_data("est"); - std::vector packet(pack_data.begin(), pack_data.end()); - packet.reserve(mst_length*8 + 16); - - packet.push_back(id); - - // Placeholder for length - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - - if (tpl > 0x3F) { - throw std::runtime_error("TagESTn: invalid TPL value"); - } - - if (sad > 0x3FF) { - throw std::runtime_error("TagESTn: invalid SAD value"); - } - - if (scid > 0x3F) { - throw std::runtime_error("TagESTn: invalid SCID value"); - } - - uint32_t sstc = (scid << 18) | (sad << 8) | (tpl << 2) | rfa; - packet.push_back((sstc >> 16) & 0xFF); - packet.push_back((sstc >> 8) & 0xFF); - packet.push_back(sstc & 0xFF); - - for (size_t i = 0; i < mst_length * 8; i++) { - packet.push_back(mst_data[i]); - } - - // calculate and update size - // remove TAG name and TAG length fields and convert to bits - uint32_t taglength = (packet.size() - 8) * 8; - - // write length into packet - packet[4] = (taglength >> 24) & 0xFF; - packet[5] = (taglength >> 16) & 0xFF; - packet[6] = (taglength >> 8) & 0xFF; - packet[7] = taglength & 0xFF; - - /* - std::cerr << "TagItem ESTn, length " << packet.size() << std::endl; - std::cerr << " mst_length " << mst_length << std::endl; - */ - return packet; -} - -std::vector TagDSTI::Assemble() -{ - std::string pack_data("dsti"); - std::vector packet(pack_data.begin(), pack_data.end()); - packet.reserve(256); - - // Placeholder for length - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - - uint8_t dfctl = dflc % 250; - uint8_t dfcth = dflc / 250; - - - uint16_t dstiHeader = dfctl | (dfcth << 8) | (rfadf << 13) | (atstf << 14) | (stihf << 15); - packet.push_back(dstiHeader >> 8); - packet.push_back(dstiHeader & 0xFF); - - if (stihf) { - packet.push_back(stat); - packet.push_back((spid >> 8) & 0xFF); - packet.push_back(spid & 0xFF); - } - - if (atstf) { - packet.push_back(utco); - - packet.push_back((seconds >> 24) & 0xFF); - packet.push_back((seconds >> 16) & 0xFF); - packet.push_back((seconds >> 8) & 0xFF); - packet.push_back(seconds & 0xFF); - - packet.push_back((tsta >> 16) & 0xFF); - packet.push_back((tsta >> 8) & 0xFF); - packet.push_back(tsta & 0xFF); - } - - if (rfadf) { - for (size_t i = 0; i < rfad.size(); i++) { - packet.push_back(rfad[i]); - } - } - // calculate and update size - // remove TAG name and TAG length fields and convert to bits - uint32_t taglength = (packet.size() - 8) * 8; - - // write length into packet - packet[4] = (taglength >> 24) & 0xFF; - packet[5] = (taglength >> 16) & 0xFF; - packet[6] = (taglength >> 8) & 0xFF; - packet[7] = taglength & 0xFF; - - dflc = (dflc+1) % 5000; - - /* - std::cerr << "TagItem dsti, packet.size " << packet.size() << std::endl; - std::cerr << " length " << taglength / 8 << std::endl; - */ - return packet; -} - -void TagDSTI::set_edi_time(const std::time_t t, int tai_utc_offset) -{ - utco = tai_utc_offset - 32; - - const std::time_t posix_timestamp_1_jan_2000 = 946684800; - - seconds = t - posix_timestamp_1_jan_2000 + utco; -} - -#if 0 -/* Update the EDI time. t is in UTC, TAI offset is requested from adjtimex */ -void TagDSTI::set_edi_time(const std::time_t t) -{ - if (tai_offset_cache_updated_at == 0 or tai_offset_cache_updated_at + 3600 < t) { - struct timex timex_request; - timex_request.modes = 0; - - int err = adjtimex(&timex_request); - if (err == -1) { - throw std::runtime_error("adjtimex failed"); - } - - if (timex_request.tai == 0) { - throw std::runtime_error("CLOCK_TAI is not properly set up"); - } - tai_offset_cache = timex_request.tai; - tai_offset_cache_updated_at = t; - - fprintf(stderr, "adjtimex: %d, tai %d\n", err, timex_request.tai); - } - - utco = tai_offset_cache - 32; - - const std::time_t posix_timestamp_1_jan_2000 = 946684800; - - seconds = t - posix_timestamp_1_jan_2000 + utco; -} -#endif - -std::vector TagSSm::Assemble() -{ - std::string pack_data("ss"); - std::vector packet(pack_data.begin(), pack_data.end()); - packet.reserve(istd_length + 16); - - packet.push_back((id >> 8) & 0xFF); - packet.push_back(id & 0xFF); - - // Placeholder for length - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - packet.push_back(0); - - if (rfa > 0x1F) { - throw std::runtime_error("TagSSm: invalid RFA value"); - } - - if (tid > 0x7) { - throw std::runtime_error("TagSSm: invalid tid value"); - } - - if (tidext > 0x7) { - throw std::runtime_error("TagSSm: invalid tidext value"); - } - - if (stid > 0x0FFF) { - throw std::runtime_error("TagSSm: invalid stid value"); - } - - uint32_t istc = (rfa << 19) | (tid << 16) | (tidext << 13) | ((crcstf ? 1 : 0) << 12) | stid; - packet.push_back((istc >> 16) & 0xFF); - packet.push_back((istc >> 8) & 0xFF); - packet.push_back(istc & 0xFF); - - for (size_t i = 0; i < istd_length; i++) { - packet.push_back(istd_data[i]); - } - - // calculate and update size - // remove TAG name and TAG length fields and convert to bits - uint32_t taglength = (packet.size() - 8) * 8; - - // write length into packet - packet[4] = (taglength >> 24) & 0xFF; - packet[5] = (taglength >> 16) & 0xFF; - packet[6] = (taglength >> 8) & 0xFF; - packet[7] = taglength & 0xFF; - - /* - std::cerr << "TagItem SSm, length " << packet.size() << std::endl; - std::cerr << " istd_length " << istd_length << std::endl; - */ - return packet; -} - - -std::vector TagStarDMY::Assemble() -{ - std::string pack_data("*dmy"); - std::vector packet(pack_data.begin(), pack_data.end()); - - packet.resize(4 + 4 + length_); - - const uint32_t length_bits = length_ * 8; - - packet[4] = (length_bits >> 24) & 0xFF; - packet[5] = (length_bits >> 16) & 0xFF; - packet[6] = (length_bits >> 8) & 0xFF; - packet[7] = length_bits & 0xFF; - - // The remaining bytes in the packet are "undefined data" - - return packet; -} - -TagODRVersion::TagODRVersion(const std::string& version, uint32_t uptime_s) : - m_version(version), - m_uptime(uptime_s) -{ -} - -std::vector TagODRVersion::Assemble() -{ - std::string pack_data("ODRv"); - std::vector packet(pack_data.begin(), pack_data.end()); - - const size_t length = m_version.size() + sizeof(uint32_t); - - packet.resize(4 + 4 + length); - - const uint32_t length_bits = length * 8; - - size_t i = 4; - packet[i++] = (length_bits >> 24) & 0xFF; - packet[i++] = (length_bits >> 16) & 0xFF; - packet[i++] = (length_bits >> 8) & 0xFF; - packet[i++] = length_bits & 0xFF; - - copy(m_version.cbegin(), m_version.cend(), packet.begin() + i); - i += m_version.size(); - - packet[i++] = (m_uptime >> 24) & 0xFF; - packet[i++] = (m_uptime >> 16) & 0xFF; - packet[i++] = (m_uptime >> 8) & 0xFF; - packet[i++] = m_uptime & 0xFF; - - return packet; -} - -TagODRAudioLevels::TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right) : - m_audio_left(audiolevel_left), - m_audio_right(audiolevel_right) -{ -} - -std::vector TagODRAudioLevels::Assemble() -{ - std::string pack_data("ODRa"); - std::vector packet(pack_data.begin(), pack_data.end()); - - constexpr size_t length = 2*sizeof(int16_t); - - packet.resize(4 + 4 + length); - - const uint32_t length_bits = length * 8; - - size_t i = 4; - packet[i++] = (length_bits >> 24) & 0xFF; - packet[i++] = (length_bits >> 16) & 0xFF; - packet[i++] = (length_bits >> 8) & 0xFF; - packet[i++] = length_bits & 0xFF; - - packet[i++] = (m_audio_left >> 8) & 0xFF; - packet[i++] = m_audio_left & 0xFF; - - packet[i++] = (m_audio_right >> 8) & 0xFF; - packet[i++] = m_audio_right & 0xFF; - - return packet; -} - -} - diff --git a/contrib/edi/TagItems.h b/contrib/edi/TagItems.h deleted file mode 100644 index 5c81b01..0000000 --- a/contrib/edi/TagItems.h +++ /dev/null @@ -1,253 +0,0 @@ -/* - EDI output. - This defines a few TAG items as defined ETSI TS 102 821 and - ETSI TS 102 693 - - Copyright (C) 2019 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include -#include -#include -#include -#include - -namespace edi { - -class TagItem -{ - public: - virtual std::vector Assemble() = 0; -}; - -// ETSI TS 102 693, 5.1.1 Protocol type and revision -class TagStarPTR : public TagItem -{ - public: - TagStarPTR(const std::string& protocol); - std::vector Assemble(); - - private: - std::string m_protocol = ""; -}; - -// ETSI TS 102 693, 5.1.3 DAB ETI(LI) Management (deti) -class TagDETI : public TagItem -{ - public: - std::vector Assemble(); - - /***** DATA in intermediary format ****/ - // For the ETI Header: must be defined ! - uint8_t stat = 0; - uint8_t mid = 0; - uint8_t fp = 0; - uint8_t rfa = 0; - uint8_t rfu = 0; // MNSC is valid - uint16_t mnsc = 0; - uint16_t dlfc = 0; // modulo 5000 frame counter - - // ATST (optional) - bool atstf = false; // presence of atst data - - /* UTCO: Offset (in seconds) between UTC and the Seconds value. The - * value is expressed as an unsigned 8-bit quantity. As of February - * 2009, the value shall be 2 and shall change as a result of each - * modification of the number of leap seconds, as proscribed by - * International Earth Rotation and Reference Systems Service (IERS). - * - * According to Annex F - * EDI = TAI - 32s (constant) - * EDI = UTC + UTCO - * we derive - * UTCO = TAI-UTC - 32 - * where the TAI-UTC offset is given by the USNO bulletin using - * the ClockTAI module. - */ - uint8_t utco = 0; - - /* Update the EDI time. t is in UTC */ - void set_edi_time(const std::time_t t, int tai_utc_offset); - - /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an - * unsigned 32-bit quantity. Contrary to POSIX, this value also - * counts leap seconds. - */ - uint32_t seconds = 0; - - /* TSTA: Shall be the 24 least significant bits of the Time Stamp - * (TIST) field from the STI-D(LI) Frame. The full definition for the - * STI TIST can be found in annex B of EN 300 797 [4]. The most - * significant 8 bits of the TIST field of the incoming STI-D(LI) - * frame, if required, may be carried in the RFAD field. - */ - uint32_t tsta = 0xFFFFFF; - - // the FIC (optional) - bool ficf = false; - const unsigned char* fic_data; - size_t fic_length; - - // rfu - bool rfudf = false; - uint32_t rfud = 0; - - -}; - -// ETSI TS 102 693, 5.1.5 ETI Sub-Channel Stream -class TagESTn : public TagItem -{ - public: - std::vector Assemble(); - - // SSTCn - uint8_t scid; - uint16_t sad; - uint8_t tpl; - uint8_t rfa; - - // Pointer to MSTn data - uint8_t* mst_data; - size_t mst_length; // STLn * 8 bytes - - uint8_t id; -}; - -// ETSI TS 102 693, 5.1.2 DAB STI-D(LI) Management -class TagDSTI : public TagItem -{ - public: - std::vector Assemble(); - - // dsti Header - bool stihf = false; - bool atstf = false; // presence of atst data - bool rfadf = false; - uint16_t dflc = 0; // modulo 5000 frame counter - - // STI Header (optional) - uint8_t stat = 0; - uint16_t spid = 0; - - /* UTCO: Offset (in seconds) between UTC and the Seconds value. The - * value is expressed as an unsigned 8-bit quantity. As of February - * 2009, the value shall be 2 and shall change as a result of each - * modification of the number of leap seconds, as proscribed by - * International Earth Rotation and Reference Systems Service (IERS). - * - * According to Annex F - * EDI = TAI - 32s (constant) - * EDI = UTC + UTCO - * we derive - * UTCO = TAI-UTC - 32 - * where the TAI-UTC offset is given by the USNO bulletin using - * the ClockTAI module. - */ - uint8_t utco = 0; - - /* Update the EDI time. t is in UTC */ - void set_edi_time(const std::time_t t, int tai_utc_offset); - - /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an - * unsigned 32-bit quantity. Contrary to POSIX, this value also - * counts leap seconds. - */ - uint32_t seconds = 0; - - /* TSTA: Shall be the 24 least significant bits of the Time Stamp - * (TIST) field from the STI-D(LI) Frame. The full definition for the - * STI TIST can be found in annex B of EN 300 797 [4]. The most - * significant 8 bits of the TIST field of the incoming STI-D(LI) - * frame, if required, may be carried in the RFAD field. - */ - uint32_t tsta = 0xFFFFFF; - - std::array rfad; - - private: - int tai_offset_cache = 0; - std::time_t tai_offset_cache_updated_at = 0; -}; - -// ETSI TS 102 693, 5.1.4 STI-D Payload Stream -class TagSSm : public TagItem -{ - public: - std::vector Assemble(); - - // SSTCn - uint8_t rfa = 0; - uint8_t tid = 0; // See EN 300 797, 5.4.1.1. Value 0 means "MSC sub-channel" - uint8_t tidext = 0; // EN 300 797, 5.4.1.3, Value 0 means "MSC audio stream" - bool crcstf = false; - uint16_t stid = 0; - - // Pointer to ISTDm data - const uint8_t *istd_data; - size_t istd_length; // bytes - - uint16_t id = 0; -}; - -// ETSI TS 102 821, 5.2.2.2 Dummy padding -class TagStarDMY : public TagItem -{ - public: - /* length is the TAG value length in bytes */ - TagStarDMY(uint32_t length) : length_(length) {} - std::vector Assemble(); - - private: - uint32_t length_; -}; - -// Custom TAG that carries version information of the EDI source -class TagODRVersion : public TagItem -{ - public: - TagODRVersion(const std::string& version, uint32_t uptime_s); - std::vector Assemble(); - - private: - std::string m_version; - uint32_t m_uptime; -}; - -// Custom TAG that carries audio level metadata -class TagODRAudioLevels : public TagItem -{ - public: - TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right); - std::vector Assemble(); - - private: - int16_t m_audio_left; - int16_t m_audio_right; -}; - -} - diff --git a/contrib/edi/TagPacket.cpp b/contrib/edi/TagPacket.cpp deleted file mode 100644 index ec52ad7..0000000 --- a/contrib/edi/TagPacket.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright (C) 2020 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output. - This defines a TAG Packet. - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "config.h" -#include "TagPacket.h" -#include "TagItems.h" -#include -#include -#include -#include -#include -#include - -namespace edi { - -TagPacket::TagPacket(unsigned int alignment) : m_alignment(alignment) -{ } - -std::vector TagPacket::Assemble() -{ - if (raw_tagpacket.size() > 0 and tag_items.size() > 0) { - throw std::logic_error("TagPacket: both raw and items used!"); - } - - if (raw_tagpacket.size() > 0) { - return raw_tagpacket; - } - - std::vector packet; - - for (auto tag : tag_items) { - std::vector tag_data = tag->Assemble(); - packet.insert(packet.end(), tag_data.begin(), tag_data.end()); - } - - if (m_alignment == 0) { /* no padding */ } - else if (m_alignment == 8) { - // Add padding inside TAG packet - while (packet.size() % 8 > 0) { - packet.push_back(0); // TS 102 821, 5.1, "padding shall be undefined" - } - } - else if (m_alignment > 8) { - TagStarDMY dmy(m_alignment - 8); - auto dmy_data = dmy.Assemble(); - packet.insert(packet.end(), dmy_data.begin(), dmy_data.end()); - } - else { - std::cerr << "Invalid alignment requirement " << m_alignment << - " defined in TagPacket" << std::endl; - } - - return packet; -} - -} - diff --git a/contrib/edi/TagPacket.h b/contrib/edi/TagPacket.h deleted file mode 100644 index b53b718..0000000 --- a/contrib/edi/TagPacket.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2020 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output. - This defines a TAG Packet. - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include "TagItems.h" -#include -#include -#include -#include - -namespace edi { - -// A TagPacket is nothing else than a list of tag items, with an -// Assemble function that puts the bytestream together and adds -// padding such that the total length is a multiple of 8 Bytes. -// -// Alternatively, a raw tagpacket can be used instead of the -// items list -// -// ETSI TS 102 821, 5.1 Tag Packet -class TagPacket -{ - public: - TagPacket(unsigned int alignment); - std::vector Assemble(); - - std::list tag_items; - - std::vector raw_tagpacket; - - private: - unsigned int m_alignment; -}; - -} - diff --git a/contrib/edi/Transport.cpp b/contrib/edi/Transport.cpp deleted file mode 100644 index fa7588a..0000000 --- a/contrib/edi/Transport.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - Copyright (C) 2020 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - UDP and TCP transports and their configuration - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -#include "Transport.h" -#include -#include - -using namespace std; - -namespace edi { - -void configuration_t::print() const -{ - etiLog.level(info) << "EDI Output"; - etiLog.level(info) << " verbose " << verbose; - for (auto edi_dest : destinations) { - if (auto udp_dest = dynamic_pointer_cast(edi_dest)) { - etiLog.level(info) << " UDP to " << udp_dest->dest_addr << ":" << dest_port; - if (not udp_dest->source_addr.empty()) { - etiLog.level(info) << " source " << udp_dest->source_addr; - etiLog.level(info) << " ttl " << udp_dest->ttl; - } - etiLog.level(info) << " source port " << udp_dest->source_port; - } - else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { - etiLog.level(info) << " TCP listening on port " << tcp_dest->listen_port; - etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; - } - else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { - etiLog.level(info) << " TCP client connecting to " << tcp_dest->dest_addr << ":" << tcp_dest->dest_port; - etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; - } - else { - throw logic_error("EDI destination not implemented"); - } - } - if (interleaver_enabled()) { - etiLog.level(info) << " interleave " << latency_frames * 24 << " ms"; - } -} - - -Sender::Sender(const configuration_t& conf) : - m_conf(conf), - edi_pft(m_conf) -{ - if (m_conf.verbose) { - etiLog.log(info, "Setup EDI Output"); - } - - for (const auto& edi_dest : m_conf.destinations) { - if (const auto udp_dest = dynamic_pointer_cast(edi_dest)) { - auto udp_socket = std::make_shared(udp_dest->source_port); - - if (not udp_dest->source_addr.empty()) { - udp_socket->setMulticastSource(udp_dest->source_addr.c_str()); - udp_socket->setMulticastTTL(udp_dest->ttl); - } - - udp_sockets.emplace(udp_dest.get(), udp_socket); - } - else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { - auto dispatcher = make_shared(tcp_dest->max_frames_queued); - dispatcher->start(tcp_dest->listen_port, "0.0.0.0"); - tcp_dispatchers.emplace(tcp_dest.get(), dispatcher); - } - else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { - auto tcp_send_client = make_shared(tcp_dest->dest_addr, tcp_dest->dest_port); - tcp_senders.emplace(tcp_dest.get(), tcp_send_client); - } - else { - throw logic_error("EDI destination not implemented"); - } - } - - if (m_conf.interleaver_enabled()) { - edi_interleaver.SetLatency(m_conf.latency_frames); - } - - if (m_conf.dump) { - edi_debug_file.open("./edi.debug"); - } - - if (m_conf.verbose) { - etiLog.log(info, "EDI output set up"); - } -} - -void Sender::write(const TagPacket& tagpacket) -{ - // Assemble into one AF Packet - edi::AFPacket af_packet = edi_afPacketiser.Assemble(tagpacket); - - if (m_conf.enable_pft) { - // Apply PFT layer to AF Packet (Reed Solomon FEC and Fragmentation) - vector edi_fragments = edi_pft.Assemble(af_packet); - - if (m_conf.verbose) { - fprintf(stderr, "EDI Output: Number of PFT fragment before interleaver %zu\n", - edi_fragments.size()); - } - - if (m_conf.interleaver_enabled()) { - edi_fragments = edi_interleaver.Interleave(edi_fragments); - } - - if (m_conf.verbose) { - fprintf(stderr, "EDI Output: Number of PFT fragments %zu\n", - edi_fragments.size()); - } - - /* Spread out the transmission of all fragments over 75% of the 24ms AF packet duration - * to reduce the risk of losing fragments because of congestion. - * - * 75% was chosen so that other outputs still have time to do their thing. */ - const auto inter_fragment_wait_time = std::chrono::microseconds(llrint(0.75 * 24000.0 / edi_fragments.size())); - - // Send over ethernet - for (auto& edi_frag : edi_fragments) { - if (m_conf.dump) { - ostream_iterator debug_iterator(edi_debug_file); - copy(edi_frag.begin(), edi_frag.end(), debug_iterator); - } - - for (auto& dest : m_conf.destinations) { - if (const auto& udp_dest = dynamic_pointer_cast(dest)) { - Socket::InetAddress addr; - addr.resolveUdpDestination(udp_dest->dest_addr, m_conf.dest_port); - - udp_sockets.at(udp_dest.get())->send(edi_frag, addr); - } - else if (auto tcp_dest = dynamic_pointer_cast(dest)) { - tcp_dispatchers.at(tcp_dest.get())->write(edi_frag); - } - else if (auto tcp_dest = dynamic_pointer_cast(dest)) { - tcp_senders.at(tcp_dest.get())->sendall(edi_frag); - } - else { - throw logic_error("EDI destination not implemented"); - } - } - - std::this_thread::sleep_for(inter_fragment_wait_time); - } - } - else { - // Send over ethernet - if (m_conf.dump) { - ostream_iterator debug_iterator(edi_debug_file); - copy(af_packet.begin(), af_packet.end(), debug_iterator); - } - - for (auto& dest : m_conf.destinations) { - if (const auto& udp_dest = dynamic_pointer_cast(dest)) { - Socket::InetAddress addr; - addr.resolveUdpDestination(udp_dest->dest_addr, m_conf.dest_port); - - if (af_packet.size() > 1400 and not m_udp_fragmentation_warning_printed) { - fprintf(stderr, "EDI Output: AF packet larger than 1400," - " consider using PFT to avoid UP fragmentation.\n"); - m_udp_fragmentation_warning_printed = true; - } - - udp_sockets.at(udp_dest.get())->send(af_packet, addr); - } - else if (auto tcp_dest = dynamic_pointer_cast(dest)) { - tcp_dispatchers.at(tcp_dest.get())->write(af_packet); - } - else if (auto tcp_dest = dynamic_pointer_cast(dest)) { - tcp_senders.at(tcp_dest.get())->sendall(af_packet); - } - else { - throw logic_error("EDI destination not implemented"); - } - } - } -} - -} diff --git a/contrib/edi/Transport.h b/contrib/edi/Transport.h deleted file mode 100644 index 56ded3b..0000000 --- a/contrib/edi/Transport.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright (C) 2020 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://www.opendigitalradio.org - - EDI output, - UDP and TCP transports and their configuration - - */ -/* - This file is part of the ODR-mmbTools. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#pragma once - -#include "config.h" -#include "EDIConfig.h" -#include "AFPacket.h" -#include "PFT.h" -#include "Interleaver.h" -#include "Socket.h" -#include -#include -#include -#include -#include - -namespace edi { - -/** Configuration for EDI output */ - -class Sender { - public: - Sender(const configuration_t& conf); - - void write(const TagPacket& tagpacket); - - private: - bool m_udp_fragmentation_warning_printed = false; - - configuration_t m_conf; - std::ofstream edi_debug_file; - - // The TagPacket will then be placed into an AFPacket - edi::AFPacketiser edi_afPacketiser; - - // The AF Packet will be protected with reed-solomon and split in fragments - edi::PFT edi_pft; - - // To mitigate for burst packet loss, PFT fragments can be sent out-of-order - edi::Interleaver edi_interleaver; - - std::unordered_map> udp_sockets; - std::unordered_map> tcp_dispatchers; - std::unordered_map> tcp_senders; -}; - -} - diff --git a/contrib/edioutput/AFPacket.cpp b/contrib/edioutput/AFPacket.cpp new file mode 100644 index 0000000..b38c38b --- /dev/null +++ b/contrib/edioutput/AFPacket.cpp @@ -0,0 +1,96 @@ +/* + Copyright (C) 2014 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output. + This implements an AF Packet as defined ETSI TS 102 821. + Also see ETSI TS 102 693 + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include "config.h" +#include "crc.h" +#include "AFPacket.h" +#include "TagItems.h" +#include "TagPacket.h" +#include +#include +#include +#include +#include +#include + +namespace edi { + +// Header PT field. AF packet contains TAG payload +const uint8_t AFHEADER_PT_TAG = 'T'; + +// AF Packet Major (3 bits) and Minor (4 bits) version +const uint8_t AFHEADER_VERSION = 0x10; // MAJ=1, MIN=0 + +AFPacket AFPacketiser::Assemble(TagPacket tag_packet) +{ + std::vector payload = tag_packet.Assemble(); + + if (m_verbose) + std::cerr << "Assemble AFPacket " << seq << std::endl; + + std::string pack_data("AF"); // SYNC + std::vector packet(pack_data.begin(), pack_data.end()); + + uint32_t taglength = payload.size(); + + if (m_verbose) + std::cerr << " AFPacket payload size " << payload.size() << std::endl; + + // write length into packet + packet.push_back((taglength >> 24) & 0xFF); + packet.push_back((taglength >> 16) & 0xFF); + packet.push_back((taglength >> 8) & 0xFF); + packet.push_back(taglength & 0xFF); + + // fill rest of header + packet.push_back(seq >> 8); + packet.push_back(seq & 0xFF); + seq++; + packet.push_back((have_crc ? 0x80 : 0) | AFHEADER_VERSION); // ar_cf: CRC=1 + packet.push_back(AFHEADER_PT_TAG); + + // insert payload, must have a length multiple of 8 bytes + packet.insert(packet.end(), payload.begin(), payload.end()); + + // calculate CRC over AF Header and payload + uint16_t crc = 0xffff; + crc = crc16(crc, &(packet.front()), packet.size()); + crc ^= 0xffff; + + if (m_verbose) + fprintf(stderr, " AFPacket crc %x\n", crc); + + packet.push_back((crc >> 8) & 0xFF); + packet.push_back(crc & 0xFF); + + if (m_verbose) + std::cerr << " AFPacket length " << packet.size() << std::endl; + + return packet; +} + +} diff --git a/contrib/edioutput/AFPacket.h b/contrib/edioutput/AFPacket.h new file mode 100644 index 0000000..f2c4e35 --- /dev/null +++ b/contrib/edioutput/AFPacket.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2014 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output. + This implements an AF Packet as defined ETSI TS 102 821. + Also see ETSI TS 102 693 + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include +#include +#include "TagItems.h" +#include "TagPacket.h" + +namespace edi { + +typedef std::vector AFPacket; + +// ETSI TS 102 821, 6.1 AF packet structure +class AFPacketiser +{ + public: + AFPacketiser() : + m_verbose(false) {}; + AFPacketiser(bool verbose) : + m_verbose(verbose) {}; + + AFPacket Assemble(TagPacket tag_packet); + + private: + static const bool have_crc = true; + + uint16_t seq = 0; //counter that overflows at 0xFFFF + + bool m_verbose; +}; + +} + diff --git a/contrib/edioutput/EDIConfig.h b/contrib/edioutput/EDIConfig.h new file mode 100644 index 0000000..647d77e --- /dev/null +++ b/contrib/edioutput/EDIConfig.h @@ -0,0 +1,84 @@ +/* + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + UDP and TCP transports and their configuration + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include +#include +#include +#include + +namespace edi { + +/** Configuration for EDI output */ + +struct destination_t { + virtual ~destination_t() {}; +}; + +// Can represent both unicast and multicast destinations +struct udp_destination_t : public destination_t { + std::string dest_addr; + unsigned int dest_port = 0; + std::string source_addr; + unsigned int source_port = 0; + unsigned int ttl = 10; +}; + +// TCP server that can accept multiple connections +struct tcp_server_t : public destination_t { + unsigned int listen_port = 0; + size_t max_frames_queued = 1024; +}; + +// TCP client that connects to one endpoint +struct tcp_client_t : public destination_t { + std::string dest_addr; + unsigned int dest_port = 0; + size_t max_frames_queued = 1024; +}; + +struct configuration_t { + unsigned chunk_len = 207; // RSk, data length of each chunk + unsigned fec = 0; // number of fragments that can be recovered + bool dump = false; // dump a file with the EDI packets + bool verbose = false; + bool enable_pft = false; // Enable protection and fragmentation + unsigned int tagpacket_alignment = 0; + std::vector > destinations; + unsigned int latency_frames = 0; // if nonzero, enable interleaver with a latency of latency_frames * 24ms + + bool enabled() const { return destinations.size() > 0; } + bool interleaver_enabled() const { return latency_frames > 0; } + + void print() const; +}; + +} + + diff --git a/contrib/edioutput/Interleaver.cpp b/contrib/edioutput/Interleaver.cpp new file mode 100644 index 0000000..f26a50e --- /dev/null +++ b/contrib/edioutput/Interleaver.cpp @@ -0,0 +1,122 @@ +/* + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + Interleaving of PFT fragments to increase robustness against + burst packet loss. + + This is possible because EDI has to assume that fragments may reach + the receiver out of order. + + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + ODR-DabMux is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ODR-DabMux. If not, see . + */ + +#include "Interleaver.h" +#include + +namespace edi { + +void Interleaver::SetLatency(size_t latency_frames) +{ + m_latency = latency_frames; +} + +Interleaver::fragment_vec Interleaver::Interleave(fragment_vec &fragments) +{ + m_fragment_count = fragments.size(); + + // Create vectors containing Fcount*latency fragments in total + // and store them into the deque + if (m_buffer.empty()) { + m_buffer.emplace_back(); + } + + auto& last_buffer = m_buffer.back(); + + for (auto& fragment : fragments) { + const bool last_buffer_is_complete = + (last_buffer.size() >= m_fragment_count * m_latency); + + if (last_buffer_is_complete) { + m_buffer.emplace_back(); + last_buffer = m_buffer.back(); + } + + last_buffer.push_back(std::move(fragment)); + } + + fragments.clear(); + + while ( not m_buffer.empty() and + (m_buffer.front().size() >= m_fragment_count * m_latency)) { + + auto& first_buffer = m_buffer.front(); + + assert(first_buffer.size() == m_fragment_count * m_latency); + + /* Assume we have 5 fragments per AF frame, and latency of 3. + * This will give the following strides: + * 0 1 2 + * +-------+-------+---+ + * | 0 1 | 2 3 | 4 | + * | | +---+ | + * | 5 6 | 7 | 8 9 | + * | +---+ | | + * |10 |11 12 |13 14 | + * +---+-------+-------+ + * + * ix will be 0, 5, 10, 1, 6 in the first loop + */ + + for (size_t i = 0; i < m_fragment_count; i++) { + const size_t ix = m_interleave_offset + m_fragment_count * m_stride; + m_interleaved_fragments.push_back(first_buffer.at(ix)); + + m_stride += 1; + if (m_stride >= m_latency) { + m_interleave_offset++; + m_stride = 0; + } + } + + if (m_interleave_offset >= m_fragment_count) { + m_interleave_offset = 0; + m_stride = 0; + m_buffer.pop_front(); + } + } + + std::vector interleaved_frags; + + const size_t n = std::min(m_fragment_count, m_interleaved_fragments.size()); + std::move(m_interleaved_fragments.begin(), + m_interleaved_fragments.begin() + n, + std::back_inserter(interleaved_frags)); + m_interleaved_fragments.erase( + m_interleaved_fragments.begin(), + m_interleaved_fragments.begin() + n); + + return interleaved_frags; +} + +} + + diff --git a/contrib/edioutput/Interleaver.h b/contrib/edioutput/Interleaver.h new file mode 100644 index 0000000..3029d5d --- /dev/null +++ b/contrib/edioutput/Interleaver.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2017 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + Interleaving of PFT fragments to increase robustness against + burst packet loss. + + This is possible because EDI has to assume that fragments may reach + the receiver out of order. + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include +#include +#include +#include +#include "Log.h" +#include "PFT.h" + +namespace edi { + +class Interleaver { + public: + using fragment_vec = std::vector; + + /* Configure the interleaver to use latency_frames number of AF + * packets for interleaving. Total delay through the interleaver + * will be latency_frames * 24ms + */ + void SetLatency(size_t latency_frames); + + /* Move the fragments for an AF Packet into the interleaver and + * return interleaved fragments to be transmitted. + */ + fragment_vec Interleave(fragment_vec &fragments); + + private: + size_t m_latency = 0; + size_t m_fragment_count = 0; + size_t m_interleave_offset = 0; + size_t m_stride = 0; + + /* Buffer that accumulates enough fragments to interleave */ + std::deque m_buffer; + + /* Buffer that contains fragments that have been interleaved, + * to avoid that the interleaver output is too bursty + */ + std::deque m_interleaved_fragments; +}; + +} + diff --git a/contrib/edioutput/PFT.cpp b/contrib/edioutput/PFT.cpp new file mode 100644 index 0000000..b2f07e0 --- /dev/null +++ b/contrib/edioutput/PFT.cpp @@ -0,0 +1,324 @@ +/* + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + Protection, Fragmentation and Transport. (PFT) + + Are supported: + Reed-Solomon and Fragmentation + + This implements part of PFT as defined ETSI TS 102 821. + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "PFT.h" +#include "crc.h" +#include "ReedSolomon.h" + +namespace edi { + +using namespace std; + +// An integer division that rounds up, i.e. ceil(a/b) +#define CEIL_DIV(a, b) (a % b == 0 ? a / b : a / b + 1) + +PFT::PFT() { } + +PFT::PFT(const configuration_t &conf) : + m_k(conf.chunk_len), + m_m(conf.fec), + m_pseq(0), + m_num_chunks(0), + m_verbose(conf.verbose) + { + if (m_k > 207) { + etiLog.level(warn) << + "EDI PFT: maximum chunk size is 207."; + throw std::out_of_range("EDI PFT Chunk size too large."); + } + + if (m_m > 5) { + etiLog.level(warn) << + "EDI PFT: high number of recoverable fragments" + " may lead to large overhead"; + // See TS 102 821, 7.2.1 Known values, list entry for 'm' + } + } + +RSBlock PFT::Protect(AFPacket af_packet) +{ + RSBlock rs_block; + + // number of chunks is ceil(afpacketsize / m_k) + // TS 102 821 7.2.2: c = ceil(l / k_max) + m_num_chunks = CEIL_DIV(af_packet.size(), m_k); + + if (m_verbose) { + fprintf(stderr, "Protect %zu chunks of size %zu\n", + m_num_chunks, af_packet.size()); + } + + // calculate size of chunk: + // TS 102 821 7.2.2: k = ceil(l / c) + // chunk_len does not include the 48 bytes of protection. + const size_t chunk_len = CEIL_DIV(af_packet.size(), m_num_chunks); + if (chunk_len > 207) { + std::stringstream ss; + ss << "Chunk length " << chunk_len << " too large (>207)"; + throw std::runtime_error(ss.str()); + } + + // The last RS chunk is zero padded + // TS 102 821 7.2.2: z = c*k - l + const size_t zero_pad = m_num_chunks * chunk_len - af_packet.size(); + + // Create the RS(k+p,k) encoder + const int firstRoot = 1; // Discovered by analysing EDI dump + const int gfPoly = 0x11d; + const bool reverse = false; + // The encoding has to be 255, 207 always, because the chunk has to + // be padded at the end, and not at the beginning as libfec would + // do + ReedSolomon rs_encoder(255, 207, reverse, gfPoly, firstRoot); + + // add zero padding to last chunk + for (size_t i = 0; i < zero_pad; i++) { + af_packet.push_back(0); + } + + if (m_verbose) { + fprintf(stderr, " add %zu zero padding\n", zero_pad); + } + + // Calculate RS for each chunk and assemble RS block + for (size_t i = 0; i < af_packet.size(); i+= chunk_len) { + vector chunk(207); + vector protection(PARITYBYTES); + + // copy chunk_len bytes into new chunk + memcpy(&chunk.front(), &af_packet[i], chunk_len); + + // calculate RS for chunk with padding + rs_encoder.encode(&chunk.front(), &protection.front(), 207); + + // Drop the padding + chunk.resize(chunk_len); + + // append new chunk and protection to the RS Packet + rs_block.insert(rs_block.end(), chunk.begin(), chunk.end()); + rs_block.insert(rs_block.end(), protection.begin(), protection.end()); + } + + return rs_block; +} + +vector< vector > PFT::ProtectAndFragment(AFPacket af_packet) +{ + const bool enable_RS = (m_m > 0); + + if (enable_RS) { + RSBlock rs_block = Protect(af_packet); + +#if 0 + fprintf(stderr, " af_packet (%zu):", af_packet.size()); + for (size_t i = 0; i < af_packet.size(); i++) { + fprintf(stderr, "%02x ", af_packet[i]); + } + fprintf(stderr, "\n"); + + fprintf(stderr, " rs_block (%zu):", rs_block.size()); + for (size_t i = 0; i < rs_block.size(); i++) { + fprintf(stderr, "%02x ", rs_block[i]); + } + fprintf(stderr, "\n"); +#endif + + // TS 102 821 7.2.2: s_max = MIN(floor(c*p/(m+1)), MTU - h)) + const size_t max_payload_size = ( m_num_chunks * PARITYBYTES ) / (m_m + 1); + + // Calculate fragment count and size + // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) + // l + c*p + z = length of RS block + const size_t num_fragments = CEIL_DIV(rs_block.size(), max_payload_size); + + // TS 102 821 7.2.2: ceil((l + c*p + z) / f) + const size_t fragment_size = CEIL_DIV(rs_block.size(), num_fragments); + + if (m_verbose) + fprintf(stderr, " PnF fragment_size %zu, num frag %zu\n", + fragment_size, num_fragments); + + vector< vector > fragments(num_fragments); + + for (size_t i = 0; i < num_fragments; i++) { + fragments[i].resize(fragment_size); + for (size_t j = 0; j < fragment_size; j++) { + const size_t ix = j*num_fragments + i; + if (ix < rs_block.size()) { + fragments[i][j] = rs_block[ix]; + } + else { + fragments[i][j] = 0; + } + } + } + + return fragments; + } + else { // No RS, only fragmentation + // TS 102 821 7.2.2: s_max = MTU - h + // Ethernet MTU is 1500, but maybe you are routing over a network which + // has some sort of packet encapsulation. Add some margin. + const size_t max_payload_size = 1400; + + // Calculate fragment count and size + // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) + // l + c*p + z = length of AF packet + const size_t num_fragments = CEIL_DIV(af_packet.size(), max_payload_size); + + // TS 102 821 7.2.2: ceil((l + c*p + z) / f) + const size_t fragment_size = CEIL_DIV(af_packet.size(), num_fragments); + vector< vector > fragments(num_fragments); + + for (size_t i = 0; i < num_fragments; i++) { + fragments[i].reserve(fragment_size); + + for (size_t j = 0; j < fragment_size; j++) { + const size_t ix = i*fragment_size + j; + if (ix < af_packet.size()) { + fragments[i].push_back(af_packet.at(ix)); + } + else { + break; + } + } + } + + return fragments; + } +} + +std::vector< PFTFragment > PFT::Assemble(AFPacket af_packet) +{ + vector< vector > fragments = ProtectAndFragment(af_packet); + vector< vector > pft_fragments; // These contain PF headers + + const bool enable_RS = (m_m > 0); + + unsigned int findex = 0; + + unsigned fcount = fragments.size(); + + // calculate size of chunk: + // TS 102 821 7.2.2: k = ceil(l / c) + // chunk_len does not include the 48 bytes of protection. + const size_t chunk_len = enable_RS ? + CEIL_DIV(af_packet.size(), m_num_chunks) : 0; + + // The last RS chunk is zero padded + // TS 102 821 7.2.2: z = c*k - l + const size_t zero_pad = enable_RS ? + m_num_chunks * chunk_len - af_packet.size() : 0; + + for (const auto &fragment : fragments) { + // Psync + std::string psync("PF"); + std::vector packet(psync.begin(), psync.end()); + + // Pseq + packet.push_back(m_pseq >> 8); + packet.push_back(m_pseq & 0xFF); + + // Findex + packet.push_back(findex >> 16); + packet.push_back(findex >> 8); + packet.push_back(findex & 0xFF); + findex++; + + // Fcount + packet.push_back(fcount >> 16); + packet.push_back(fcount >> 8); + packet.push_back(fcount & 0xFF); + + // RS (1 bit), transport (1 bit) and Plen (14 bits) + unsigned int plen = fragment.size(); + if (enable_RS) { + plen |= 0x8000; // Set FEC bit + } + + if (m_transport_header) { + plen |= 0x4000; // Set ADDR bit + } + + packet.push_back(plen >> 8); + packet.push_back(plen & 0xFF); + + if (enable_RS) { + packet.push_back(chunk_len); // RSk + packet.push_back(zero_pad); // RSz + } + + if (m_transport_header) { + // Source (16 bits) + packet.push_back(m_addr_source >> 8); + packet.push_back(m_addr_source & 0xFF); + + // Dest (16 bits) + packet.push_back(m_dest_port >> 8); + packet.push_back(m_dest_port & 0xFF); + } + + // calculate CRC over AF Header and payload + uint16_t crc = 0xffff; + crc = crc16(crc, &(packet.front()), packet.size()); + crc ^= 0xffff; + + packet.push_back((crc >> 8) & 0xFF); + packet.push_back(crc & 0xFF); + + // insert payload, must have a length multiple of 8 bytes + packet.insert(packet.end(), fragment.begin(), fragment.end()); + + pft_fragments.push_back(packet); + +#if 0 + fprintf(stderr, "* PFT pseq %d, findex %d, fcount %d, plen %d\n", + m_pseq, findex, fcount, plen & ~0xC000); +#endif + } + + m_pseq++; + + return pft_fragments; +} + +} + diff --git a/contrib/edioutput/PFT.h b/contrib/edioutput/PFT.h new file mode 100644 index 0000000..4d138c5 --- /dev/null +++ b/contrib/edioutput/PFT.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + Protection, Fragmentation and Transport. (PFT) + + Are supported: + Reed-Solomon and Fragmentation + + This implements part of PFT as defined ETSI TS 102 821. + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include +#include +#include +#include +#include "AFPacket.h" +#include "Log.h" +#include "ReedSolomon.h" +#include "EDIConfig.h" + +namespace edi { + +typedef std::vector RSBlock; +typedef std::vector PFTFragment; + +class PFT +{ + public: + static constexpr int PARITYBYTES = 48; + + PFT(); + PFT(const configuration_t& conf); + + // return a list of PFT fragments with the correct + // PFT headers + std::vector< PFTFragment > Assemble(AFPacket af_packet); + + // Apply Reed-Solomon FEC to the AF Packet + RSBlock Protect(AFPacket af_packet); + + // Cut a RSBlock into several fragments that can be transmitted + std::vector< std::vector > ProtectAndFragment(AFPacket af_packet); + + private: + unsigned int m_k = 207; // length of RS data word + unsigned int m_m = 3; // number of fragments that can be recovered if lost + uint16_t m_pseq = 0; + size_t m_num_chunks = 0; + bool m_verbose = false; + + // Transport header is always deactivated + const bool m_transport_header = false; + const uint16_t m_addr_source = 0; + const unsigned int m_dest_port = 0; +}; + +} + diff --git a/contrib/edioutput/TagItems.cpp b/contrib/edioutput/TagItems.cpp new file mode 100644 index 0000000..739adfa --- /dev/null +++ b/contrib/edioutput/TagItems.cpp @@ -0,0 +1,449 @@ +/* + EDI output. + This defines a few TAG items as defined ETSI TS 102 821 and + ETSI TS 102 693 + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "config.h" +#include "TagItems.h" +#include +#include +#include +#include +#include + +namespace edi { + +TagStarPTR::TagStarPTR(const std::string& protocol) + : m_protocol(protocol) +{ + if (m_protocol.size() != 4) { + throw std::runtime_error("TagStarPTR protocol invalid length"); + } +} + +std::vector TagStarPTR::Assemble() +{ + //std::cerr << "TagItem *ptr" << std::endl; + std::string pack_data("*ptr"); + std::vector packet(pack_data.begin(), pack_data.end()); + + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + packet.push_back(0x40); + + packet.insert(packet.end(), m_protocol.begin(), m_protocol.end()); + + // Major + packet.push_back(0); + packet.push_back(0); + + // Minor + packet.push_back(0); + packet.push_back(0); + return packet; +} + +std::vector TagDETI::Assemble() +{ + std::string pack_data("deti"); + std::vector packet(pack_data.begin(), pack_data.end()); + packet.reserve(256); + + // Placeholder for length + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + + uint8_t fct = dlfc % 250; + uint8_t fcth = dlfc / 250; + + + uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); + packet.push_back(detiHeader >> 8); + packet.push_back(detiHeader & 0xFF); + + uint32_t etiHeader = mnsc | (rfu << 16) | (rfa << 17) | + (fp << 19) | (mid << 22) | (stat << 24); + packet.push_back((etiHeader >> 24) & 0xFF); + packet.push_back((etiHeader >> 16) & 0xFF); + packet.push_back((etiHeader >> 8) & 0xFF); + packet.push_back(etiHeader & 0xFF); + + if (atstf) { + packet.push_back(utco); + + packet.push_back((seconds >> 24) & 0xFF); + packet.push_back((seconds >> 16) & 0xFF); + packet.push_back((seconds >> 8) & 0xFF); + packet.push_back(seconds & 0xFF); + + packet.push_back((tsta >> 16) & 0xFF); + packet.push_back((tsta >> 8) & 0xFF); + packet.push_back(tsta & 0xFF); + } + + if (ficf) { + for (size_t i = 0; i < fic_length; i++) { + packet.push_back(fic_data[i]); + } + } + + if (rfudf) { + packet.push_back((rfud >> 16) & 0xFF); + packet.push_back((rfud >> 8) & 0xFF); + packet.push_back(rfud & 0xFF); + } + + // calculate and update size + // remove TAG name and TAG length fields and convert to bits + uint32_t taglength = (packet.size() - 8) * 8; + + // write length into packet + packet[4] = (taglength >> 24) & 0xFF; + packet[5] = (taglength >> 16) & 0xFF; + packet[6] = (taglength >> 8) & 0xFF; + packet[7] = taglength & 0xFF; + + dlfc = (dlfc+1) % 5000; + + /* + std::cerr << "TagItem deti, packet.size " << packet.size() << std::endl; + std::cerr << " fic length " << fic_length << std::endl; + std::cerr << " length " << taglength / 8 << std::endl; + */ + return packet; +} + +void TagDETI::set_edi_time(const std::time_t t, int tai_utc_offset) +{ + utco = tai_utc_offset - 32; + + const std::time_t posix_timestamp_1_jan_2000 = 946684800; + + seconds = t - posix_timestamp_1_jan_2000 + utco; +} + +std::vector TagESTn::Assemble() +{ + std::string pack_data("est"); + std::vector packet(pack_data.begin(), pack_data.end()); + packet.reserve(mst_length*8 + 16); + + packet.push_back(id); + + // Placeholder for length + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + + if (tpl > 0x3F) { + throw std::runtime_error("TagESTn: invalid TPL value"); + } + + if (sad > 0x3FF) { + throw std::runtime_error("TagESTn: invalid SAD value"); + } + + if (scid > 0x3F) { + throw std::runtime_error("TagESTn: invalid SCID value"); + } + + uint32_t sstc = (scid << 18) | (sad << 8) | (tpl << 2) | rfa; + packet.push_back((sstc >> 16) & 0xFF); + packet.push_back((sstc >> 8) & 0xFF); + packet.push_back(sstc & 0xFF); + + for (size_t i = 0; i < mst_length * 8; i++) { + packet.push_back(mst_data[i]); + } + + // calculate and update size + // remove TAG name and TAG length fields and convert to bits + uint32_t taglength = (packet.size() - 8) * 8; + + // write length into packet + packet[4] = (taglength >> 24) & 0xFF; + packet[5] = (taglength >> 16) & 0xFF; + packet[6] = (taglength >> 8) & 0xFF; + packet[7] = taglength & 0xFF; + + /* + std::cerr << "TagItem ESTn, length " << packet.size() << std::endl; + std::cerr << " mst_length " << mst_length << std::endl; + */ + return packet; +} + +std::vector TagDSTI::Assemble() +{ + std::string pack_data("dsti"); + std::vector packet(pack_data.begin(), pack_data.end()); + packet.reserve(256); + + // Placeholder for length + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + + uint8_t dfctl = dlfc % 250; + uint8_t dfcth = dlfc / 250; + + + uint16_t dstiHeader = dfctl | (dfcth << 8) | (rfadf << 13) | (atstf << 14) | (stihf << 15); + packet.push_back(dstiHeader >> 8); + packet.push_back(dstiHeader & 0xFF); + + if (stihf) { + packet.push_back(stat); + packet.push_back((spid >> 8) & 0xFF); + packet.push_back(spid & 0xFF); + } + + if (atstf) { + packet.push_back(utco); + + packet.push_back((seconds >> 24) & 0xFF); + packet.push_back((seconds >> 16) & 0xFF); + packet.push_back((seconds >> 8) & 0xFF); + packet.push_back(seconds & 0xFF); + + packet.push_back((tsta >> 16) & 0xFF); + packet.push_back((tsta >> 8) & 0xFF); + packet.push_back(tsta & 0xFF); + } + + if (rfadf) { + for (size_t i = 0; i < rfad.size(); i++) { + packet.push_back(rfad[i]); + } + } + // calculate and update size + // remove TAG name and TAG length fields and convert to bits + uint32_t taglength = (packet.size() - 8) * 8; + + // write length into packet + packet[4] = (taglength >> 24) & 0xFF; + packet[5] = (taglength >> 16) & 0xFF; + packet[6] = (taglength >> 8) & 0xFF; + packet[7] = taglength & 0xFF; + + dlfc = (dlfc+1) % 5000; + + /* + std::cerr << "TagItem dsti, packet.size " << packet.size() << std::endl; + std::cerr << " length " << taglength / 8 << std::endl; + */ + return packet; +} + +void TagDSTI::set_edi_time(const std::time_t t, int tai_utc_offset) +{ + utco = tai_utc_offset - 32; + + const std::time_t posix_timestamp_1_jan_2000 = 946684800; + + seconds = t - posix_timestamp_1_jan_2000 + utco; +} + +#if 0 +/* Update the EDI time. t is in UTC, TAI offset is requested from adjtimex */ +void TagDSTI::set_edi_time(const std::time_t t) +{ + if (tai_offset_cache_updated_at == 0 or tai_offset_cache_updated_at + 3600 < t) { + struct timex timex_request; + timex_request.modes = 0; + + int err = adjtimex(&timex_request); + if (err == -1) { + throw std::runtime_error("adjtimex failed"); + } + + if (timex_request.tai == 0) { + throw std::runtime_error("CLOCK_TAI is not properly set up"); + } + tai_offset_cache = timex_request.tai; + tai_offset_cache_updated_at = t; + + fprintf(stderr, "adjtimex: %d, tai %d\n", err, timex_request.tai); + } + + utco = tai_offset_cache - 32; + + const std::time_t posix_timestamp_1_jan_2000 = 946684800; + + seconds = t - posix_timestamp_1_jan_2000 + utco; +} +#endif + +std::vector TagSSm::Assemble() +{ + std::string pack_data("ss"); + std::vector packet(pack_data.begin(), pack_data.end()); + packet.reserve(istd_length + 16); + + packet.push_back((id >> 8) & 0xFF); + packet.push_back(id & 0xFF); + + // Placeholder for length + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + packet.push_back(0); + + if (rfa > 0x1F) { + throw std::runtime_error("TagSSm: invalid RFA value"); + } + + if (tid > 0x7) { + throw std::runtime_error("TagSSm: invalid tid value"); + } + + if (tidext > 0x7) { + throw std::runtime_error("TagSSm: invalid tidext value"); + } + + if (stid > 0x0FFF) { + throw std::runtime_error("TagSSm: invalid stid value"); + } + + uint32_t istc = (rfa << 19) | (tid << 16) | (tidext << 13) | ((crcstf ? 1 : 0) << 12) | stid; + packet.push_back((istc >> 16) & 0xFF); + packet.push_back((istc >> 8) & 0xFF); + packet.push_back(istc & 0xFF); + + for (size_t i = 0; i < istd_length; i++) { + packet.push_back(istd_data[i]); + } + + // calculate and update size + // remove TAG name and TAG length fields and convert to bits + uint32_t taglength = (packet.size() - 8) * 8; + + // write length into packet + packet[4] = (taglength >> 24) & 0xFF; + packet[5] = (taglength >> 16) & 0xFF; + packet[6] = (taglength >> 8) & 0xFF; + packet[7] = taglength & 0xFF; + + /* + std::cerr << "TagItem SSm, length " << packet.size() << std::endl; + std::cerr << " istd_length " << istd_length << std::endl; + */ + return packet; +} + + +std::vector TagStarDMY::Assemble() +{ + std::string pack_data("*dmy"); + std::vector packet(pack_data.begin(), pack_data.end()); + + packet.resize(4 + 4 + length_); + + const uint32_t length_bits = length_ * 8; + + packet[4] = (length_bits >> 24) & 0xFF; + packet[5] = (length_bits >> 16) & 0xFF; + packet[6] = (length_bits >> 8) & 0xFF; + packet[7] = length_bits & 0xFF; + + // The remaining bytes in the packet are "undefined data" + + return packet; +} + +TagODRVersion::TagODRVersion(const std::string& version, uint32_t uptime_s) : + m_version(version), + m_uptime(uptime_s) +{ +} + +std::vector TagODRVersion::Assemble() +{ + std::string pack_data("ODRv"); + std::vector packet(pack_data.begin(), pack_data.end()); + + const size_t length = m_version.size() + sizeof(uint32_t); + + packet.resize(4 + 4 + length); + + const uint32_t length_bits = length * 8; + + size_t i = 4; + packet[i++] = (length_bits >> 24) & 0xFF; + packet[i++] = (length_bits >> 16) & 0xFF; + packet[i++] = (length_bits >> 8) & 0xFF; + packet[i++] = length_bits & 0xFF; + + copy(m_version.cbegin(), m_version.cend(), packet.begin() + i); + i += m_version.size(); + + packet[i++] = (m_uptime >> 24) & 0xFF; + packet[i++] = (m_uptime >> 16) & 0xFF; + packet[i++] = (m_uptime >> 8) & 0xFF; + packet[i++] = m_uptime & 0xFF; + + return packet; +} + +TagODRAudioLevels::TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right) : + m_audio_left(audiolevel_left), + m_audio_right(audiolevel_right) +{ +} + +std::vector TagODRAudioLevels::Assemble() +{ + std::string pack_data("ODRa"); + std::vector packet(pack_data.begin(), pack_data.end()); + + constexpr size_t length = 2*sizeof(int16_t); + + packet.resize(4 + 4 + length); + + const uint32_t length_bits = length * 8; + + size_t i = 4; + packet[i++] = (length_bits >> 24) & 0xFF; + packet[i++] = (length_bits >> 16) & 0xFF; + packet[i++] = (length_bits >> 8) & 0xFF; + packet[i++] = length_bits & 0xFF; + + packet[i++] = (m_audio_left >> 8) & 0xFF; + packet[i++] = m_audio_left & 0xFF; + + packet[i++] = (m_audio_right >> 8) & 0xFF; + packet[i++] = m_audio_right & 0xFF; + + return packet; +} + +} + diff --git a/contrib/edioutput/TagItems.h b/contrib/edioutput/TagItems.h new file mode 100644 index 0000000..f24dc44 --- /dev/null +++ b/contrib/edioutput/TagItems.h @@ -0,0 +1,253 @@ +/* + EDI output. + This defines a few TAG items as defined ETSI TS 102 821 and + ETSI TS 102 693 + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include +#include +#include +#include +#include + +namespace edi { + +class TagItem +{ + public: + virtual std::vector Assemble() = 0; +}; + +// ETSI TS 102 693, 5.1.1 Protocol type and revision +class TagStarPTR : public TagItem +{ + public: + TagStarPTR(const std::string& protocol); + std::vector Assemble(); + + private: + std::string m_protocol = ""; +}; + +// ETSI TS 102 693, 5.1.3 DAB ETI(LI) Management (deti) +class TagDETI : public TagItem +{ + public: + std::vector Assemble(); + + /***** DATA in intermediary format ****/ + // For the ETI Header: must be defined ! + uint8_t stat = 0; + uint8_t mid = 0; + uint8_t fp = 0; + uint8_t rfa = 0; + uint8_t rfu = 0; // MNSC is valid + uint16_t mnsc = 0; + uint16_t dlfc = 0; // modulo 5000 frame counter + + // ATST (optional) + bool atstf = false; // presence of atst data + + /* UTCO: Offset (in seconds) between UTC and the Seconds value. The + * value is expressed as an unsigned 8-bit quantity. As of February + * 2009, the value shall be 2 and shall change as a result of each + * modification of the number of leap seconds, as proscribed by + * International Earth Rotation and Reference Systems Service (IERS). + * + * According to Annex F + * EDI = TAI - 32s (constant) + * EDI = UTC + UTCO + * we derive + * UTCO = TAI-UTC - 32 + * where the TAI-UTC offset is given by the USNO bulletin using + * the ClockTAI module. + */ + uint8_t utco = 0; + + /* Update the EDI time. t is in UTC */ + void set_edi_time(const std::time_t t, int tai_utc_offset); + + /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an + * unsigned 32-bit quantity. Contrary to POSIX, this value also + * counts leap seconds. + */ + uint32_t seconds = 0; + + /* TSTA: Shall be the 24 least significant bits of the Time Stamp + * (TIST) field from the STI-D(LI) Frame. The full definition for the + * STI TIST can be found in annex B of EN 300 797 [4]. The most + * significant 8 bits of the TIST field of the incoming STI-D(LI) + * frame, if required, may be carried in the RFAD field. + */ + uint32_t tsta = 0xFFFFFF; + + // the FIC (optional) + bool ficf = false; + const unsigned char* fic_data; + size_t fic_length; + + // rfu + bool rfudf = false; + uint32_t rfud = 0; + + +}; + +// ETSI TS 102 693, 5.1.5 ETI Sub-Channel Stream +class TagESTn : public TagItem +{ + public: + std::vector Assemble(); + + // SSTCn + uint8_t scid; + uint16_t sad; + uint8_t tpl; + uint8_t rfa; + + // Pointer to MSTn data + uint8_t* mst_data; + size_t mst_length; // STLn * 8 bytes + + uint8_t id; +}; + +// ETSI TS 102 693, 5.1.2 DAB STI-D(LI) Management +class TagDSTI : public TagItem +{ + public: + std::vector Assemble(); + + // dsti Header + bool stihf = false; + bool atstf = false; // presence of atst data + bool rfadf = false; + uint16_t dlfc = 0; // modulo 5000 frame counter + + // STI Header (optional) + uint8_t stat = 0; + uint16_t spid = 0; + + /* UTCO: Offset (in seconds) between UTC and the Seconds value. The + * value is expressed as an unsigned 8-bit quantity. As of February + * 2009, the value shall be 2 and shall change as a result of each + * modification of the number of leap seconds, as proscribed by + * International Earth Rotation and Reference Systems Service (IERS). + * + * According to Annex F + * EDI = TAI - 32s (constant) + * EDI = UTC + UTCO + * we derive + * UTCO = TAI-UTC - 32 + * where the TAI-UTC offset is given by the USNO bulletin using + * the ClockTAI module. + */ + uint8_t utco = 0; + + /* Update the EDI time. t is in UTC */ + void set_edi_time(const std::time_t t, int tai_utc_offset); + + /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an + * unsigned 32-bit quantity. Contrary to POSIX, this value also + * counts leap seconds. + */ + uint32_t seconds = 0; + + /* TSTA: Shall be the 24 least significant bits of the Time Stamp + * (TIST) field from the STI-D(LI) Frame. The full definition for the + * STI TIST can be found in annex B of EN 300 797 [4]. The most + * significant 8 bits of the TIST field of the incoming STI-D(LI) + * frame, if required, may be carried in the RFAD field. + */ + uint32_t tsta = 0xFFFFFF; + + std::array rfad; + + private: + int tai_offset_cache = 0; + std::time_t tai_offset_cache_updated_at = 0; +}; + +// ETSI TS 102 693, 5.1.4 STI-D Payload Stream +class TagSSm : public TagItem +{ + public: + std::vector Assemble(); + + // SSTCn + uint8_t rfa = 0; + uint8_t tid = 0; // See EN 300 797, 5.4.1.1. Value 0 means "MSC sub-channel" + uint8_t tidext = 0; // EN 300 797, 5.4.1.3, Value 0 means "MSC audio stream" + bool crcstf = false; + uint16_t stid = 0; + + // Pointer to ISTDm data + const uint8_t *istd_data; + size_t istd_length; // bytes + + uint16_t id = 0; +}; + +// ETSI TS 102 821, 5.2.2.2 Dummy padding +class TagStarDMY : public TagItem +{ + public: + /* length is the TAG value length in bytes */ + TagStarDMY(uint32_t length) : length_(length) {} + std::vector Assemble(); + + private: + uint32_t length_; +}; + +// Custom TAG that carries version information of the EDI source +class TagODRVersion : public TagItem +{ + public: + TagODRVersion(const std::string& version, uint32_t uptime_s); + std::vector Assemble(); + + private: + std::string m_version; + uint32_t m_uptime; +}; + +// Custom TAG that carries audio level metadata +class TagODRAudioLevels : public TagItem +{ + public: + TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right); + std::vector Assemble(); + + private: + int16_t m_audio_left; + int16_t m_audio_right; +}; + +} + diff --git a/contrib/edioutput/TagPacket.cpp b/contrib/edioutput/TagPacket.cpp new file mode 100644 index 0000000..ec52ad7 --- /dev/null +++ b/contrib/edioutput/TagPacket.cpp @@ -0,0 +1,80 @@ +/* + Copyright (C) 2020 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output. + This defines a TAG Packet. + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "config.h" +#include "TagPacket.h" +#include "TagItems.h" +#include +#include +#include +#include +#include +#include + +namespace edi { + +TagPacket::TagPacket(unsigned int alignment) : m_alignment(alignment) +{ } + +std::vector TagPacket::Assemble() +{ + if (raw_tagpacket.size() > 0 and tag_items.size() > 0) { + throw std::logic_error("TagPacket: both raw and items used!"); + } + + if (raw_tagpacket.size() > 0) { + return raw_tagpacket; + } + + std::vector packet; + + for (auto tag : tag_items) { + std::vector tag_data = tag->Assemble(); + packet.insert(packet.end(), tag_data.begin(), tag_data.end()); + } + + if (m_alignment == 0) { /* no padding */ } + else if (m_alignment == 8) { + // Add padding inside TAG packet + while (packet.size() % 8 > 0) { + packet.push_back(0); // TS 102 821, 5.1, "padding shall be undefined" + } + } + else if (m_alignment > 8) { + TagStarDMY dmy(m_alignment - 8); + auto dmy_data = dmy.Assemble(); + packet.insert(packet.end(), dmy_data.begin(), dmy_data.end()); + } + else { + std::cerr << "Invalid alignment requirement " << m_alignment << + " defined in TagPacket" << std::endl; + } + + return packet; +} + +} + diff --git a/contrib/edioutput/TagPacket.h b/contrib/edioutput/TagPacket.h new file mode 100644 index 0000000..b53b718 --- /dev/null +++ b/contrib/edioutput/TagPacket.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2020 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output. + This defines a TAG Packet. + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include "TagItems.h" +#include +#include +#include +#include + +namespace edi { + +// A TagPacket is nothing else than a list of tag items, with an +// Assemble function that puts the bytestream together and adds +// padding such that the total length is a multiple of 8 Bytes. +// +// Alternatively, a raw tagpacket can be used instead of the +// items list +// +// ETSI TS 102 821, 5.1 Tag Packet +class TagPacket +{ + public: + TagPacket(unsigned int alignment); + std::vector Assemble(); + + std::list tag_items; + + std::vector raw_tagpacket; + + private: + unsigned int m_alignment; +}; + +} + diff --git a/contrib/edioutput/Transport.cpp b/contrib/edioutput/Transport.cpp new file mode 100644 index 0000000..cfed9ec --- /dev/null +++ b/contrib/edioutput/Transport.cpp @@ -0,0 +1,202 @@ +/* + Copyright (C) 2020 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + UDP and TCP transports and their configuration + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include "Transport.h" +#include +#include + +using namespace std; + +namespace edi { + +void configuration_t::print() const +{ + etiLog.level(info) << "EDI Output"; + etiLog.level(info) << " verbose " << verbose; + for (auto edi_dest : destinations) { + if (auto udp_dest = dynamic_pointer_cast(edi_dest)) { + etiLog.level(info) << " UDP to " << udp_dest->dest_addr << ":" << udp_dest->dest_port; + if (not udp_dest->source_addr.empty()) { + etiLog.level(info) << " source " << udp_dest->source_addr; + etiLog.level(info) << " ttl " << udp_dest->ttl; + } + etiLog.level(info) << " source port " << udp_dest->source_port; + } + else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { + etiLog.level(info) << " TCP listening on port " << tcp_dest->listen_port; + etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; + } + else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { + etiLog.level(info) << " TCP client connecting to " << tcp_dest->dest_addr << ":" << tcp_dest->dest_port; + etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; + } + else { + throw logic_error("EDI destination not implemented"); + } + } + if (interleaver_enabled()) { + etiLog.level(info) << " interleave " << latency_frames * 24 << " ms"; + } +} + + +Sender::Sender(const configuration_t& conf) : + m_conf(conf), + edi_pft(m_conf) +{ + if (m_conf.verbose) { + etiLog.log(info, "Setup EDI Output"); + } + + for (const auto& edi_dest : m_conf.destinations) { + if (const auto udp_dest = dynamic_pointer_cast(edi_dest)) { + auto udp_socket = std::make_shared(udp_dest->source_port); + + if (not udp_dest->source_addr.empty()) { + udp_socket->setMulticastSource(udp_dest->source_addr.c_str()); + udp_socket->setMulticastTTL(udp_dest->ttl); + } + + udp_sockets.emplace(udp_dest.get(), udp_socket); + } + else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { + auto dispatcher = make_shared(tcp_dest->max_frames_queued); + dispatcher->start(tcp_dest->listen_port, "0.0.0.0"); + tcp_dispatchers.emplace(tcp_dest.get(), dispatcher); + } + else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { + auto tcp_send_client = make_shared(tcp_dest->dest_addr, tcp_dest->dest_port); + tcp_senders.emplace(tcp_dest.get(), tcp_send_client); + } + else { + throw logic_error("EDI destination not implemented"); + } + } + + if (m_conf.interleaver_enabled()) { + edi_interleaver.SetLatency(m_conf.latency_frames); + } + + if (m_conf.dump) { + edi_debug_file.open("./edi.debug"); + } + + if (m_conf.verbose) { + etiLog.log(info, "EDI output set up"); + } +} + +void Sender::write(const TagPacket& tagpacket) +{ + // Assemble into one AF Packet + edi::AFPacket af_packet = edi_afPacketiser.Assemble(tagpacket); + + if (m_conf.enable_pft) { + // Apply PFT layer to AF Packet (Reed Solomon FEC and Fragmentation) + vector edi_fragments = edi_pft.Assemble(af_packet); + + if (m_conf.verbose) { + fprintf(stderr, "EDI Output: Number of PFT fragment before interleaver %zu\n", + edi_fragments.size()); + } + + if (m_conf.interleaver_enabled()) { + edi_fragments = edi_interleaver.Interleave(edi_fragments); + } + + if (m_conf.verbose) { + fprintf(stderr, "EDI Output: Number of PFT fragments %zu\n", + edi_fragments.size()); + } + + /* Spread out the transmission of all fragments over 75% of the 24ms AF packet duration + * to reduce the risk of losing fragments because of congestion. + * + * 75% was chosen so that other outputs still have time to do their thing. */ + const auto inter_fragment_wait_time = std::chrono::microseconds(llrint(0.75 * 24000.0 / edi_fragments.size())); + + // Send over ethernet + for (auto& edi_frag : edi_fragments) { + if (m_conf.dump) { + ostream_iterator debug_iterator(edi_debug_file); + copy(edi_frag.begin(), edi_frag.end(), debug_iterator); + } + + for (auto& dest : m_conf.destinations) { + if (const auto& udp_dest = dynamic_pointer_cast(dest)) { + Socket::InetAddress addr; + addr.resolveUdpDestination(udp_dest->dest_addr, udp_dest->dest_port); + + udp_sockets.at(udp_dest.get())->send(edi_frag, addr); + } + else if (auto tcp_dest = dynamic_pointer_cast(dest)) { + tcp_dispatchers.at(tcp_dest.get())->write(edi_frag); + } + else if (auto tcp_dest = dynamic_pointer_cast(dest)) { + tcp_senders.at(tcp_dest.get())->sendall(edi_frag); + } + else { + throw logic_error("EDI destination not implemented"); + } + } + + std::this_thread::sleep_for(inter_fragment_wait_time); + } + } + else { + // Send over ethernet + if (m_conf.dump) { + ostream_iterator debug_iterator(edi_debug_file); + copy(af_packet.begin(), af_packet.end(), debug_iterator); + } + + for (auto& dest : m_conf.destinations) { + if (const auto& udp_dest = dynamic_pointer_cast(dest)) { + Socket::InetAddress addr; + addr.resolveUdpDestination(udp_dest->dest_addr, udp_dest->dest_port); + + if (af_packet.size() > 1400 and not m_udp_fragmentation_warning_printed) { + fprintf(stderr, "EDI Output: AF packet larger than 1400," + " consider using PFT to avoid UP fragmentation.\n"); + m_udp_fragmentation_warning_printed = true; + } + + udp_sockets.at(udp_dest.get())->send(af_packet, addr); + } + else if (auto tcp_dest = dynamic_pointer_cast(dest)) { + tcp_dispatchers.at(tcp_dest.get())->write(af_packet); + } + else if (auto tcp_dest = dynamic_pointer_cast(dest)) { + tcp_senders.at(tcp_dest.get())->sendall(af_packet); + } + else { + throw logic_error("EDI destination not implemented"); + } + } + } +} + +} diff --git a/contrib/edioutput/Transport.h b/contrib/edioutput/Transport.h new file mode 100644 index 0000000..56ded3b --- /dev/null +++ b/contrib/edioutput/Transport.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2020 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + EDI output, + UDP and TCP transports and their configuration + + */ +/* + This file is part of the ODR-mmbTools. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include "config.h" +#include "EDIConfig.h" +#include "AFPacket.h" +#include "PFT.h" +#include "Interleaver.h" +#include "Socket.h" +#include +#include +#include +#include +#include + +namespace edi { + +/** Configuration for EDI output */ + +class Sender { + public: + Sender(const configuration_t& conf); + + void write(const TagPacket& tagpacket); + + private: + bool m_udp_fragmentation_warning_printed = false; + + configuration_t m_conf; + std::ofstream edi_debug_file; + + // The TagPacket will then be placed into an AFPacket + edi::AFPacketiser edi_afPacketiser; + + // The AF Packet will be protected with reed-solomon and split in fragments + edi::PFT edi_pft; + + // To mitigate for burst packet loss, PFT fragments can be sent out-of-order + edi::Interleaver edi_interleaver; + + std::unordered_map> udp_sockets; + std::unordered_map> tcp_dispatchers; + std::unordered_map> tcp_senders; +}; + +} + diff --git a/src/Outputs.cpp b/src/Outputs.cpp index 66335cb..713ac1a 100644 --- a/src/Outputs.cpp +++ b/src/Outputs.cpp @@ -150,7 +150,7 @@ void EDI::add_udp_destination(const std::string& host, unsigned int port) { auto dest = make_shared(); dest->dest_addr = host; - m_edi_conf.dest_port = port; + dest->dest_port = port; m_edi_conf.destinations.push_back(dest); // We cannot carry AF packets over UDP, because they would be too large. diff --git a/src/Outputs.h b/src/Outputs.h index 7184206..20932d6 100644 --- a/src/Outputs.h +++ b/src/Outputs.h @@ -27,10 +27,10 @@ #include "common.h" #include "zmq.hpp" #include "ClockTAI.h" -#include "edi/TagItems.h" -#include "edi/TagPacket.h" -#include "edi/AFPacket.h" -#include "edi/Transport.h" +#include "edioutput/TagItems.h" +#include "edioutput/TagPacket.h" +#include "edioutput/AFPacket.h" +#include "edioutput/Transport.h" extern "C" { #include "encryption.h" } diff --git a/src/zmq.hpp b/src/zmq.hpp index eb5416e..74a0574 100644 --- a/src/zmq.hpp +++ b/src/zmq.hpp @@ -1,4 +1,5 @@ /* + Copyright (c) 2016-2017 ZeroMQ community Copyright (c) 2009-2011 250bpm s.r.o. Copyright (c) 2011 Botond Ballo Copyright (c) 2007-2009 iMatix Corporation @@ -25,35 +26,130 @@ #ifndef __ZMQ_HPP_INCLUDED__ #define __ZMQ_HPP_INCLUDED__ +// macros defined if has a specific standard or greater +#if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1900) + #define ZMQ_CPP11 +#endif +#if (defined(__cplusplus) && __cplusplus >= 201402L) || \ + (defined(_HAS_CXX14) && _HAS_CXX14 == 1) || \ + (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // _HAS_CXX14 might not be defined when using C++17 on MSVC + #define ZMQ_CPP14 +#endif +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) + #define ZMQ_CPP17 +#endif + +#if defined(ZMQ_CPP14) +#define ZMQ_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(_MSC_VER) +#define ZMQ_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) +#define ZMQ_DEPRECATED(msg) __attribute__((deprecated(msg))) +#endif + +#if defined(ZMQ_CPP17) +#define ZMQ_NODISCARD [[nodiscard]] +#else +#define ZMQ_NODISCARD +#endif + +#if defined(ZMQ_CPP11) +#define ZMQ_NOTHROW noexcept +#define ZMQ_EXPLICIT explicit +#define ZMQ_OVERRIDE override +#define ZMQ_NULLPTR nullptr +#define ZMQ_CONSTEXPR_FN constexpr +#define ZMQ_CONSTEXPR_VAR constexpr +#else +#define ZMQ_NOTHROW throw() +#define ZMQ_EXPLICIT +#define ZMQ_OVERRIDE +#define ZMQ_NULLPTR 0 +#define ZMQ_CONSTEXPR_FN +#define ZMQ_CONSTEXPR_VAR const +#endif + #include -#include #include #include -#include + +#include #include +#include +#include +#include +#include +#ifdef ZMQ_CPP11 +#include +#include +#include +#include +#endif +#ifdef ZMQ_CPP17 +#ifdef __has_include +#if __has_include() +#include +#define ZMQ_HAS_OPTIONAL 1 +#endif +#if __has_include() +#include +#define ZMQ_HAS_STRING_VIEW 1 +#endif +#endif + +#endif + +/* Version macros for compile-time API version detection */ +#define CPPZMQ_VERSION_MAJOR 4 +#define CPPZMQ_VERSION_MINOR 6 +#define CPPZMQ_VERSION_PATCH 0 + +#define CPPZMQ_VERSION \ + ZMQ_MAKE_VERSION(CPPZMQ_VERSION_MAJOR, CPPZMQ_VERSION_MINOR, \ + CPPZMQ_VERSION_PATCH) // Detect whether the compiler supports C++11 rvalue references. -#if (defined(__GNUC__) && (__GNUC__ > 4 || \ - (__GNUC__ == 4 && __GNUC_MINOR__ > 2)) && \ - defined(__GXX_EXPERIMENTAL_CXX0X__)) - #define ZMQ_HAS_RVALUE_REFS - #define ZMQ_DELETED_FUNCTION = delete +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2)) \ + && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete #elif defined(__clang__) - #if __has_feature(cxx_rvalue_references) - #define ZMQ_HAS_RVALUE_REFS - #endif - - #if __has_feature(cxx_deleted_functions) - #define ZMQ_DELETED_FUNCTION = delete - #else - #define ZMQ_DELETED_FUNCTION - #endif +#if __has_feature(cxx_rvalue_references) +#define ZMQ_HAS_RVALUE_REFS +#endif + +#if __has_feature(cxx_deleted_functions) +#define ZMQ_DELETED_FUNCTION = delete +#else +#define ZMQ_DELETED_FUNCTION +#endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1900) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete #elif defined(_MSC_VER) && (_MSC_VER >= 1600) - #define ZMQ_HAS_RVALUE_REFS - #define ZMQ_DELETED_FUNCTION +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION +#else +#define ZMQ_DELETED_FUNCTION +#endif + +#if defined(ZMQ_CPP11) && !defined(__llvm__) && !defined(__INTEL_COMPILER) \ + && defined(__GNUC__) && __GNUC__ < 5 +#define ZMQ_CPP11_PARTIAL +#elif defined(__GLIBCXX__) && __GLIBCXX__ < 20160805 +//the date here is the last date of gcc 4.9.4, which +// effectively means libstdc++ from gcc 5.5 and higher won't trigger this branch +#define ZMQ_CPP11_PARTIAL +#endif + +#ifdef ZMQ_CPP11 +#ifdef ZMQ_CPP11_PARTIAL +#define ZMQ_IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) #else - #define ZMQ_DELETED_FUNCTION +#include +#define ZMQ_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value +#endif #endif #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 3, 0) @@ -63,540 +159,1962 @@ #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) #define ZMQ_HAS_PROXY_STEERABLE /* Socket event data */ -typedef struct { - uint16_t event; // id of the event as bitfield - int32_t value ; // value is either error code, fd or reconnect interval +typedef struct +{ + uint16_t event; // id of the event as bitfield + int32_t value; // value is either error code, fd or reconnect interval } zmq_event_t; #endif +// Avoid using deprecated message receive function when possible +#if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) +#define zmq_msg_recv(msg, socket, flags) zmq_recvmsg(socket, msg, flags) +#endif + + // In order to prevent unused variable warnings when building in non-debug // mode use this macro to make assertions. #ifndef NDEBUG -# define ZMQ_ASSERT(expression) assert(expression) +#define ZMQ_ASSERT(expression) assert(expression) #else -# define ZMQ_ASSERT(expression) (void)(expression) +#define ZMQ_ASSERT(expression) (void) (expression) #endif namespace zmq { - typedef zmq_free_fn free_fn; - typedef zmq_pollitem_t pollitem_t; +#ifdef ZMQ_CPP11 +namespace detail +{ +namespace ranges +{ +using std::begin; +using std::end; +template +auto begin(T&& r) -> decltype(begin(std::forward(r))) +{ + return begin(std::forward(r)); +} +template +auto end(T&& r) -> decltype(end(std::forward(r))) +{ + return end(std::forward(r)); +} +} // namespace ranges - class error_t : public std::exception - { - public: +template using void_t = void; - error_t () : errnum (zmq_errno ()) {} +template +using iter_value_t = typename std::iterator_traits::value_type; - virtual const char *what () const throw () - { - return zmq_strerror (errnum); - } +template +using range_iter_t = decltype( + ranges::begin(std::declval::type &>())); - int num () const - { - return errnum; - } +template +using range_value_t = iter_value_t>; + +template struct is_range : std::false_type +{ +}; + +template +struct is_range< + T, + void_t::type &>()) + == ranges::end(std::declval::type &>()))>> + : std::true_type +{ +}; + +} // namespace detail +#endif - private: +typedef zmq_free_fn free_fn; +typedef zmq_pollitem_t pollitem_t; - int errnum; - }; +class error_t : public std::exception +{ + public: + error_t() : errnum(zmq_errno()) {} + virtual const char *what() const ZMQ_NOTHROW ZMQ_OVERRIDE { return zmq_strerror(errnum); } + int num() const { return errnum; } + + private: + int errnum; +}; + +inline int poll(zmq_pollitem_t *items_, size_t nitems_, long timeout_ = -1) +{ + int rc = zmq_poll(items_, static_cast(nitems_), timeout_); + if (rc < 0) + throw error_t(); + return rc; +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(zmq_pollitem_t const *items_, size_t nitems_, long timeout_ = -1) +{ + return poll(const_cast(items_), nitems_, timeout_); +} - inline int poll (zmq_pollitem_t *items_, int nitems_, long timeout_ = -1) +#ifdef ZMQ_CPP11 +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int +poll(zmq_pollitem_t const *items, size_t nitems, std::chrono::milliseconds timeout) +{ + return poll(const_cast(items), nitems, static_cast(timeout.count())); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(std::vector const &items, + std::chrono::milliseconds timeout) +{ + return poll(const_cast(items.data()), items.size(), static_cast(timeout.count())); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(std::vector const &items, long timeout_ = -1) +{ + return poll(const_cast(items.data()), items.size(), timeout_); +} + +inline int +poll(zmq_pollitem_t *items, size_t nitems, std::chrono::milliseconds timeout) +{ + return poll(items, nitems, static_cast(timeout.count())); +} + +inline int poll(std::vector &items, + std::chrono::milliseconds timeout) +{ + return poll(items.data(), items.size(), static_cast(timeout.count())); +} + +inline int poll(std::vector &items, long timeout_ = -1) +{ + return poll(items.data(), items.size(), timeout_); +} +#endif + + +inline void version(int *major_, int *minor_, int *patch_) +{ + zmq_version(major_, minor_, patch_); +} + +#ifdef ZMQ_CPP11 +inline std::tuple version() +{ + std::tuple v; + zmq_version(&std::get<0>(v), &std::get<1>(v), &std::get<2>(v)); + return v; +} +#endif + +class message_t +{ + public: + message_t() ZMQ_NOTHROW { - int rc = zmq_poll (items_, nitems_, timeout_); - if (rc < 0) - throw error_t (); - return rc; + int rc = zmq_msg_init(&msg); + ZMQ_ASSERT(rc == 0); } - inline void proxy (void *frontend, void *backend, void *capture) + explicit message_t(size_t size_) { - int rc = zmq_proxy (frontend, backend, capture); + int rc = zmq_msg_init_size(&msg, size_); if (rc != 0) - throw error_t (); + throw error_t(); } - -#ifdef ZMQ_HAS_PROXY_STEERABLE - inline void proxy_steerable (void *frontend, void *backend, void *capture, void *control) + + template message_t(ForwardIter first, ForwardIter last) + { + typedef typename std::iterator_traits::value_type value_t; + + assert(std::distance(first, last) >= 0); + size_t const size_ = + static_cast(std::distance(first, last)) * sizeof(value_t); + int const rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + std::copy(first, last, data()); + } + + message_t(const void *data_, size_t size_) + { + int rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + memcpy(data(), data_, size_); + } + + message_t(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) { - int rc = zmq_proxy_steerable (frontend, backend, capture, control); + int rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); if (rc != 0) - throw error_t (); + throw error_t(); + } + +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) + template::value + && ZMQ_IS_TRIVIALLY_COPYABLE(detail::range_value_t) + && !std::is_same::value>::type> + explicit message_t(const Range &rng) : + message_t(detail::ranges::begin(rng), detail::ranges::end(rng)) + { } #endif - - inline void version (int *major_, int *minor_, int *patch_) + +#ifdef ZMQ_HAS_RVALUE_REFS + message_t(message_t &&rhs) ZMQ_NOTHROW : msg(rhs.msg) { - zmq_version (major_, minor_, patch_); + int rc = zmq_msg_init(&rhs.msg); + ZMQ_ASSERT(rc == 0); } - class message_t + message_t &operator=(message_t &&rhs) ZMQ_NOTHROW { - friend class socket_t; + std::swap(msg, rhs.msg); + return *this; + } +#endif - public: + ~message_t() ZMQ_NOTHROW + { + int rc = zmq_msg_close(&msg); + ZMQ_ASSERT(rc == 0); + } - inline message_t () - { - int rc = zmq_msg_init (&msg); - if (rc != 0) - throw error_t (); - } + void rebuild() + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init(&msg); + ZMQ_ASSERT(rc == 0); + } - inline explicit message_t (size_t size_) - { - int rc = zmq_msg_init_size (&msg, size_); - if (rc != 0) - throw error_t (); - } + void rebuild(size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + } - inline message_t (void *data_, size_t size_, free_fn *ffn_, - void *hint_ = NULL) - { - int rc = zmq_msg_init_data (&msg, data_, size_, ffn_, hint_); - if (rc != 0) - throw error_t (); - } + void rebuild(const void *data_, size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + memcpy(data(), data_, size_); + } -#ifdef ZMQ_HAS_RVALUE_REFS - inline message_t (message_t &&rhs) : msg (rhs.msg) - { - int rc = zmq_msg_init (&rhs.msg); - if (rc != 0) - throw error_t (); - } + void rebuild(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); + if (rc != 0) + throw error_t(); + } - inline message_t &operator = (message_t &&rhs) - { - std::swap (msg, rhs.msg); - return *this; - } -#endif + ZMQ_DEPRECATED("from 4.3.1, use move taking non-const reference instead") + void move(message_t const *msg_) + { + int rc = zmq_msg_move(&msg, const_cast(msg_->handle())); + if (rc != 0) + throw error_t(); + } - inline ~message_t () - { - int rc = zmq_msg_close (&msg); - ZMQ_ASSERT (rc == 0); - } + void move(message_t &msg_) + { + int rc = zmq_msg_move(&msg, msg_.handle()); + if (rc != 0) + throw error_t(); + } - inline void rebuild () - { - int rc = zmq_msg_close (&msg); - if (rc != 0) - throw error_t (); - rc = zmq_msg_init (&msg); - if (rc != 0) - throw error_t (); - } + ZMQ_DEPRECATED("from 4.3.1, use copy taking non-const reference instead") + void copy(message_t const *msg_) + { + int rc = zmq_msg_copy(&msg, const_cast(msg_->handle())); + if (rc != 0) + throw error_t(); + } - inline void rebuild (size_t size_) - { - int rc = zmq_msg_close (&msg); - if (rc != 0) - throw error_t (); - rc = zmq_msg_init_size (&msg, size_); - if (rc != 0) - throw error_t (); - } + void copy(message_t &msg_) + { + int rc = zmq_msg_copy(&msg, msg_.handle()); + if (rc != 0) + throw error_t(); + } - inline void rebuild (void *data_, size_t size_, free_fn *ffn_, - void *hint_ = NULL) - { - int rc = zmq_msg_close (&msg); - if (rc != 0) - throw error_t (); - rc = zmq_msg_init_data (&msg, data_, size_, ffn_, hint_); - if (rc != 0) - throw error_t (); - } + bool more() const ZMQ_NOTHROW + { + int rc = zmq_msg_more(const_cast(&msg)); + return rc != 0; + } - inline void move (message_t *msg_) - { - int rc = zmq_msg_move (&msg, &(msg_->msg)); - if (rc != 0) - throw error_t (); - } + void *data() ZMQ_NOTHROW { return zmq_msg_data(&msg); } - inline void copy (message_t *msg_) - { - int rc = zmq_msg_copy (&msg, &(msg_->msg)); - if (rc != 0) - throw error_t (); - } + const void *data() const ZMQ_NOTHROW + { + return zmq_msg_data(const_cast(&msg)); + } - inline bool more () - { - int rc = zmq_msg_more (&msg); - return rc != 0; - } + size_t size() const ZMQ_NOTHROW + { + return zmq_msg_size(const_cast(&msg)); + } - inline void *data () - { - return zmq_msg_data (&msg); - } + ZMQ_NODISCARD bool empty() const ZMQ_NOTHROW + { + return size() == 0u; + } - inline const void* data () const - { - return zmq_msg_data (const_cast(&msg)); - } + template T *data() ZMQ_NOTHROW { return static_cast(data()); } - inline size_t size () const - { - return zmq_msg_size (const_cast(&msg)); - } + template T const *data() const ZMQ_NOTHROW + { + return static_cast(data()); + } - private: + ZMQ_DEPRECATED("from 4.3.0, use operator== instead") + bool equal(const message_t *other) const ZMQ_NOTHROW + { + return *this == *other; + } - // The underlying message - zmq_msg_t msg; + bool operator==(const message_t &other) const ZMQ_NOTHROW + { + const size_t my_size = size(); + return my_size == other.size() && 0 == memcmp(data(), other.data(), my_size); + } - // Disable implicit message copying, so that users won't use shared - // messages (less efficient) without being aware of the fact. - message_t (const message_t&); - void operator = (const message_t&); - }; + bool operator!=(const message_t &other) const ZMQ_NOTHROW + { + return !(*this == other); + } - class context_t +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 2, 0) + int get(int property_) { - friend class socket_t; + int value = zmq_msg_get(&msg, property_); + if (value == -1) + throw error_t(); + return value; + } +#endif - public: - inline context_t () - { - ptr = zmq_ctx_new (); - if (ptr == NULL) - throw error_t (); - } +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) + const char *gets(const char *property_) + { + const char *value = zmq_msg_gets(&msg, property_); + if (value == ZMQ_NULLPTR) + throw error_t(); + return value; + } +#endif +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + uint32_t routing_id() const + { + return zmq_msg_routing_id(const_cast(&msg)); + } - inline explicit context_t (int io_threads_, int max_sockets_ = ZMQ_MAX_SOCKETS_DFLT) - { - ptr = zmq_ctx_new (); - if (ptr == NULL) - throw error_t (); + void set_routing_id(uint32_t routing_id) + { + int rc = zmq_msg_set_routing_id(&msg, routing_id); + if (rc != 0) + throw error_t(); + } - int rc = zmq_ctx_set (ptr, ZMQ_IO_THREADS, io_threads_); - ZMQ_ASSERT (rc == 0); + const char* group() const + { + return zmq_msg_group(const_cast(&msg)); + } - rc = zmq_ctx_set (ptr, ZMQ_MAX_SOCKETS, max_sockets_); - ZMQ_ASSERT (rc == 0); - } + void set_group(const char* group) + { + int rc = zmq_msg_set_group(&msg, group); + if (rc != 0) + throw error_t(); + } +#endif -#ifdef ZMQ_HAS_RVALUE_REFS - inline context_t (context_t &&rhs) : ptr (rhs.ptr) - { - rhs.ptr = NULL; - } - inline context_t &operator = (context_t &&rhs) - { - std::swap (ptr, rhs.ptr); - return *this; - } + // interpret message content as a string + std::string to_string() const + { + return std::string(static_cast(data()), size()); + } +#ifdef ZMQ_CPP17 + // interpret message content as a string + std::string_view to_string_view() const noexcept + { + return std::string_view(static_cast(data()), size()); + } #endif - inline ~context_t () - { - close(); + /** Dump content to string for debugging. + * Ascii chars are readable, the rest is printed as hex. + * Probably ridiculously slow. + * Use to_string() or to_string_view() for + * interpreting the message as a string. + */ + std::string str() const + { + // Partly mutuated from the same method in zmq::multipart_t + std::stringstream os; + + const unsigned char *msg_data = this->data(); + unsigned char byte; + size_t size = this->size(); + int is_ascii[2] = {0, 0}; + + os << "zmq::message_t [size " << std::dec << std::setw(3) + << std::setfill('0') << size << "] ("; + // Totally arbitrary + if (size >= 1000) { + os << "... too big to print)"; + } else { + while (size--) { + byte = *msg_data++; + + is_ascii[1] = (byte >= 32 && byte < 127); + if (is_ascii[1] != is_ascii[0]) + os << " "; // Separate text/non text + + if (is_ascii[1]) { + os << byte; + } else { + os << std::hex << std::uppercase << std::setw(2) + << std::setfill('0') << static_cast(byte); + } + is_ascii[0] = is_ascii[1]; + } + os << ")"; } + return os.str(); + } - inline void close() - { - if (ptr == NULL) - return; - int rc = zmq_ctx_destroy (ptr); - ZMQ_ASSERT (rc == 0); - ptr = NULL; - } + void swap(message_t &other) ZMQ_NOTHROW + { + // this assumes zmq::msg_t from libzmq is trivially relocatable + std::swap(msg, other.msg); + } - // Be careful with this, it's probably only useful for - // using the C api together with an existing C++ api. - // Normally you should never need to use this. - inline operator void* () - { - return ptr; - } + ZMQ_NODISCARD zmq_msg_t *handle() ZMQ_NOTHROW { return &msg; } + ZMQ_NODISCARD const zmq_msg_t *handle() const ZMQ_NOTHROW { return &msg; } - private: + private: + // The underlying message + zmq_msg_t msg; - void *ptr; + // Disable implicit message copying, so that users won't use shared + // messages (less efficient) without being aware of the fact. + message_t(const message_t &) ZMQ_DELETED_FUNCTION; + void operator=(const message_t &) ZMQ_DELETED_FUNCTION; +}; - context_t (const context_t&); - void operator = (const context_t&); - }; +inline void swap(message_t &a, message_t &b) ZMQ_NOTHROW +{ + a.swap(b); +} - class socket_t +class context_t +{ + public: + context_t() { - friend class monitor_t; - public: + ptr = zmq_ctx_new(); + if (ptr == ZMQ_NULLPTR) + throw error_t(); + } - inline socket_t (context_t &context_, int type_) - { - ctxptr = context_.ptr; - ptr = zmq_socket (context_.ptr, type_); - if (ptr == NULL) - throw error_t (); - } + + explicit context_t(int io_threads_, + int max_sockets_ = ZMQ_MAX_SOCKETS_DFLT) + { + ptr = zmq_ctx_new(); + if (ptr == ZMQ_NULLPTR) + throw error_t(); + + int rc = zmq_ctx_set(ptr, ZMQ_IO_THREADS, io_threads_); + ZMQ_ASSERT(rc == 0); + + rc = zmq_ctx_set(ptr, ZMQ_MAX_SOCKETS, max_sockets_); + ZMQ_ASSERT(rc == 0); + } #ifdef ZMQ_HAS_RVALUE_REFS - inline socket_t(socket_t&& rhs) : ptr(rhs.ptr) - { - rhs.ptr = NULL; - } - inline socket_t& operator=(socket_t&& rhs) - { - std::swap(ptr, rhs.ptr); - return *this; - } + context_t(context_t &&rhs) ZMQ_NOTHROW : ptr(rhs.ptr) { rhs.ptr = ZMQ_NULLPTR; } + context_t &operator=(context_t &&rhs) ZMQ_NOTHROW + { + close(); + std::swap(ptr, rhs.ptr); + return *this; + } #endif - inline ~socket_t () - { - close(); - } + int setctxopt(int option_, int optval_) + { + int rc = zmq_ctx_set(ptr, option_, optval_); + ZMQ_ASSERT(rc == 0); + return rc; + } - inline operator void* () - { - return ptr; - } + int getctxopt(int option_) { return zmq_ctx_get(ptr, option_); } - inline void close() - { - if(ptr == NULL) - // already closed - return ; - int rc = zmq_close (ptr); - ZMQ_ASSERT (rc == 0); - ptr = 0 ; + ~context_t() ZMQ_NOTHROW { close(); } + + void close() ZMQ_NOTHROW + { + if (ptr == ZMQ_NULLPTR) + return; + + int rc; + do { + rc = zmq_ctx_destroy(ptr); + } while (rc == -1 && errno == EINTR); + + ZMQ_ASSERT(rc == 0); + ptr = ZMQ_NULLPTR; + } + + // Be careful with this, it's probably only useful for + // using the C api together with an existing C++ api. + // Normally you should never need to use this. + ZMQ_EXPLICIT operator void *() ZMQ_NOTHROW { return ptr; } + + ZMQ_EXPLICIT operator void const *() const ZMQ_NOTHROW { return ptr; } + + operator bool() const ZMQ_NOTHROW { return ptr != ZMQ_NULLPTR; } + + void swap(context_t &other) ZMQ_NOTHROW + { + std::swap(ptr, other.ptr); + } + + private: + void *ptr; + + context_t(const context_t &) ZMQ_DELETED_FUNCTION; + void operator=(const context_t &) ZMQ_DELETED_FUNCTION; +}; + +inline void swap(context_t &a, context_t &b) ZMQ_NOTHROW { + a.swap(b); +} + +#ifdef ZMQ_CPP11 + +struct recv_buffer_size +{ + size_t size; // number of bytes written to buffer + size_t untruncated_size; // untruncated message size in bytes + + ZMQ_NODISCARD bool truncated() const noexcept + { + return size != untruncated_size; + } +}; + +#if defined(ZMQ_HAS_OPTIONAL) && (ZMQ_HAS_OPTIONAL > 0) + +using send_result_t = std::optional; +using recv_result_t = std::optional; +using recv_buffer_result_t = std::optional; + +#else + +namespace detail +{ +// A C++11 type emulating the most basic +// operations of std::optional for trivial types +template class trivial_optional +{ + public: + static_assert(std::is_trivial::value, "T must be trivial"); + using value_type = T; + + trivial_optional() = default; + trivial_optional(T value) noexcept : _value(value), _has_value(true) {} + + const T *operator->() const noexcept + { + assert(_has_value); + return &_value; + } + T *operator->() noexcept + { + assert(_has_value); + return &_value; + } + + const T &operator*() const noexcept + { + assert(_has_value); + return _value; + } + T &operator*() noexcept + { + assert(_has_value); + return _value; + } + + T &value() + { + if (!_has_value) + throw std::exception(); + return _value; + } + const T &value() const + { + if (!_has_value) + throw std::exception(); + return _value; + } + + explicit operator bool() const noexcept { return _has_value; } + bool has_value() const noexcept { return _has_value; } + + private: + T _value{}; + bool _has_value{false}; +}; +} // namespace detail + +using send_result_t = detail::trivial_optional; +using recv_result_t = detail::trivial_optional; +using recv_buffer_result_t = detail::trivial_optional; + +#endif + +namespace detail +{ + +template +constexpr T enum_bit_or(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) | static_cast(b)); +} +template +constexpr T enum_bit_and(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) & static_cast(b)); +} +template +constexpr T enum_bit_xor(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) ^ static_cast(b)); +} +template +constexpr T enum_bit_not(T a) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(~static_cast(a)); +} +} // namespace detail + +// partially satisfies named requirement BitmaskType +enum class send_flags : int +{ + none = 0, + dontwait = ZMQ_DONTWAIT, + sndmore = ZMQ_SNDMORE +}; + +constexpr send_flags operator|(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr send_flags operator&(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr send_flags operator^(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr send_flags operator~(send_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + +// partially satisfies named requirement BitmaskType +enum class recv_flags : int +{ + none = 0, + dontwait = ZMQ_DONTWAIT +}; + +constexpr recv_flags operator|(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr recv_flags operator&(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr recv_flags operator^(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr recv_flags operator~(recv_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + + +// mutable_buffer, const_buffer and buffer are based on +// the Networking TS specification, draft: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf + +class mutable_buffer +{ + public: + constexpr mutable_buffer() noexcept : _data(nullptr), _size(0) {} + constexpr mutable_buffer(void *p, size_t n) noexcept : _data(p), _size(n) + { +#ifdef ZMQ_CPP14 + assert(p != nullptr || n == 0); +#endif + } + + constexpr void *data() const noexcept { return _data; } + constexpr size_t size() const noexcept { return _size; } + mutable_buffer &operator+=(size_t n) noexcept + { + // (std::min) is a workaround for when a min macro is defined + const auto shift = (std::min)(n, _size); + _data = static_cast(_data) + shift; + _size -= shift; + return *this; + } + + private: + void *_data; + size_t _size; +}; + +inline mutable_buffer operator+(const mutable_buffer &mb, size_t n) noexcept +{ + return mutable_buffer(static_cast(mb.data()) + (std::min)(n, mb.size()), + mb.size() - (std::min)(n, mb.size())); +} +inline mutable_buffer operator+(size_t n, const mutable_buffer &mb) noexcept +{ + return mb + n; +} + +class const_buffer +{ + public: + constexpr const_buffer() noexcept : _data(nullptr), _size(0) {} + constexpr const_buffer(const void *p, size_t n) noexcept : _data(p), _size(n) + { +#ifdef ZMQ_CPP14 + assert(p != nullptr || n == 0); +#endif + } + constexpr const_buffer(const mutable_buffer &mb) noexcept : + _data(mb.data()), + _size(mb.size()) + { + } + + constexpr const void *data() const noexcept { return _data; } + constexpr size_t size() const noexcept { return _size; } + const_buffer &operator+=(size_t n) noexcept + { + const auto shift = (std::min)(n, _size); + _data = static_cast(_data) + shift; + _size -= shift; + return *this; + } + + private: + const void *_data; + size_t _size; +}; + +inline const_buffer operator+(const const_buffer &cb, size_t n) noexcept +{ + return const_buffer(static_cast(cb.data()) + + (std::min)(n, cb.size()), + cb.size() - (std::min)(n, cb.size())); +} +inline const_buffer operator+(size_t n, const const_buffer &cb) noexcept +{ + return cb + n; +} + +// buffer creation + +constexpr mutable_buffer buffer(void* p, size_t n) noexcept +{ + return mutable_buffer(p, n); +} +constexpr const_buffer buffer(const void* p, size_t n) noexcept +{ + return const_buffer(p, n); +} +constexpr mutable_buffer buffer(const mutable_buffer& mb) noexcept +{ + return mb; +} +inline mutable_buffer buffer(const mutable_buffer& mb, size_t n) noexcept +{ + return mutable_buffer(mb.data(), (std::min)(mb.size(), n)); +} +constexpr const_buffer buffer(const const_buffer& cb) noexcept +{ + return cb; +} +inline const_buffer buffer(const const_buffer& cb, size_t n) noexcept +{ + return const_buffer(cb.data(), (std::min)(cb.size(), n)); +} + +namespace detail +{ + +template +struct is_buffer +{ + static constexpr bool value = + std::is_same::value || + std::is_same::value; +}; + +template struct is_pod_like +{ + // NOTE: The networking draft N4771 section 16.11 requires + // T in the buffer functions below to be + // trivially copyable OR standard layout. + // Here we decide to be conservative and require both. + static constexpr bool value = + ZMQ_IS_TRIVIALLY_COPYABLE(T) && std::is_standard_layout::value; +}; + +template constexpr auto seq_size(const C &c) noexcept -> decltype(c.size()) +{ + return c.size(); +} +template +constexpr size_t seq_size(const T (&/*array*/)[N]) noexcept +{ + return N; +} + +template +auto buffer_contiguous_sequence(Seq &&seq) noexcept + -> decltype(buffer(std::addressof(*std::begin(seq)), size_t{})) +{ + using T = typename std::remove_cv< + typename std::remove_reference::type>::type; + static_assert(detail::is_pod_like::value, "T must be POD"); + + const auto size = seq_size(seq); + return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, + size * sizeof(T)); +} +template +auto buffer_contiguous_sequence(Seq &&seq, size_t n_bytes) noexcept + -> decltype(buffer_contiguous_sequence(seq)) +{ + using T = typename std::remove_cv< + typename std::remove_reference::type>::type; + static_assert(detail::is_pod_like::value, "T must be POD"); + + const auto size = seq_size(seq); + return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, + (std::min)(size * sizeof(T), n_bytes)); +} + +} // namespace detail + +// C array +template mutable_buffer buffer(T (&data)[N]) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(T (&data)[N], size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template const_buffer buffer(const T (&data)[N]) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const T (&data)[N], size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::array +template mutable_buffer buffer(std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::vector +template +mutable_buffer buffer(std::vector &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::vector &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::vector &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::vector &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::basic_string +template +mutable_buffer buffer(std::basic_string &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::basic_string &data, + size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::basic_string &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::basic_string &data, + size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} + +#if defined(ZMQ_HAS_STRING_VIEW) && (ZMQ_HAS_STRING_VIEW > 0) +// std::basic_string_view +template +const_buffer buffer(std::basic_string_view data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(std::basic_string_view data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +#endif + +// Buffer for a string literal (null terminated) +// where the buffer size excludes the terminating character. +// Equivalent to zmq::buffer(std::string_view("...")). +template +constexpr const_buffer str_buffer(const Char (&data)[N]) noexcept +{ + static_assert(detail::is_pod_like::value, "Char must be POD"); +#ifdef ZMQ_CPP14 + assert(data[N - 1] == Char{0}); +#endif + return const_buffer(static_cast(data), + (N - 1) * sizeof(Char)); +} + +namespace literals +{ + constexpr const_buffer operator"" _zbuf(const char* str, size_t len) noexcept + { + return const_buffer(str, len * sizeof(char)); + } + constexpr const_buffer operator"" _zbuf(const wchar_t* str, size_t len) noexcept + { + return const_buffer(str, len * sizeof(wchar_t)); + } + constexpr const_buffer operator"" _zbuf(const char16_t* str, size_t len) noexcept + { + return const_buffer(str, len * sizeof(char16_t)); + } + constexpr const_buffer operator"" _zbuf(const char32_t* str, size_t len) noexcept + { + return const_buffer(str, len * sizeof(char32_t)); + } +} + +#endif // ZMQ_CPP11 + +namespace detail +{ + +class socket_base +{ +public: + socket_base() ZMQ_NOTHROW : _handle(ZMQ_NULLPTR) {} + ZMQ_EXPLICIT socket_base(void *handle) ZMQ_NOTHROW : _handle(handle) {} + + template void setsockopt(int option_, T const &optval) + { + setsockopt(option_, &optval, sizeof(T)); + } + + void setsockopt(int option_, const void *optval_, size_t optvallen_) + { + int rc = zmq_setsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + void getsockopt(int option_, void *optval_, size_t *optvallen_) const + { + int rc = zmq_getsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + template T getsockopt(int option_) const + { + T optval; + size_t optlen = sizeof(T); + getsockopt(option_, &optval, &optlen); + return optval; + } + + void bind(std::string const &addr) { bind(addr.c_str()); } + + void bind(const char *addr_) + { + int rc = zmq_bind(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void unbind(std::string const &addr) { unbind(addr.c_str()); } + + void unbind(const char *addr_) + { + int rc = zmq_unbind(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void connect(std::string const &addr) { connect(addr.c_str()); } + + void connect(const char *addr_) + { + int rc = zmq_connect(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void disconnect(std::string const &addr) { disconnect(addr.c_str()); } + + void disconnect(const char *addr_) + { + int rc = zmq_disconnect(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + bool connected() const ZMQ_NOTHROW { return (_handle != ZMQ_NULLPTR); } + +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.3.1, use send taking a const_buffer and send_flags") +#endif + size_t send(const void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_send(_handle, buf_, len_, flags_); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") +#endif + bool send(message_t &msg_, + int flags_ = 0) // default until removed + { + int nbytes = zmq_msg_send(msg_.handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + + template +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.4.1, use send taking message_t or buffer (for contiguous ranges), and send_flags") +#endif + bool send(T first, T last, int flags_ = 0) + { + zmq::message_t msg(first, last); + int nbytes = zmq_msg_send(msg.handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + +#ifdef ZMQ_HAS_RVALUE_REFS +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") +#endif + bool send(message_t &&msg_, + int flags_ = 0) // default until removed + { + #ifdef ZMQ_CPP11 + return send(msg_, static_cast(flags_)).has_value(); + #else + return send(msg_, flags_); + #endif + } +#endif + +#ifdef ZMQ_CPP11 + send_result_t send(const_buffer buf, send_flags flags = send_flags::none) + { + const int nbytes = + zmq_send(_handle, buf.data(), buf.size(), static_cast(flags)); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + send_result_t send(message_t &msg, send_flags flags) + { + int nbytes = zmq_msg_send(msg.handle(), _handle, static_cast(flags)); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + send_result_t send(message_t &&msg, send_flags flags) + { + return send(msg, flags); + } +#endif + +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.3.1, use recv taking a mutable_buffer and recv_flags") +#endif + size_t recv(void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_recv(_handle, buf_, len_, flags_); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + +#ifdef ZMQ_CPP11 + ZMQ_DEPRECATED("from 4.3.1, use recv taking a reference to message_t and recv_flags") +#endif + bool recv(message_t *msg_, int flags_ = 0) + { + int nbytes = zmq_msg_recv(msg_->handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + +#ifdef ZMQ_CPP11 + ZMQ_NODISCARD + recv_buffer_result_t recv(mutable_buffer buf, + recv_flags flags = recv_flags::none) + { + const int nbytes = + zmq_recv(_handle, buf.data(), buf.size(), static_cast(flags)); + if (nbytes >= 0) { + return recv_buffer_size{(std::min)(static_cast(nbytes), buf.size()), + static_cast(nbytes)}; } + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + ZMQ_NODISCARD + recv_result_t recv(message_t &msg, recv_flags flags = recv_flags::none) + { + const int nbytes = zmq_msg_recv(msg.handle(), _handle, static_cast(flags)); + if (nbytes >= 0) { + assert(msg.size() == static_cast(nbytes)); + return static_cast(nbytes); + } + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } +#endif + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + void join(const char* group) + { + int rc = zmq_join(_handle, group); + if (rc != 0) + throw error_t(); + } + + void leave(const char* group) + { + int rc = zmq_leave(_handle, group); + if (rc != 0) + throw error_t(); + } +#endif + + ZMQ_NODISCARD void *handle() ZMQ_NOTHROW { return _handle; } + ZMQ_NODISCARD const void *handle() const ZMQ_NOTHROW { return _handle; } + + ZMQ_EXPLICIT operator bool() const ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } + // note: non-const operator bool can be removed once + // operator void* is removed from socket_t + ZMQ_EXPLICIT operator bool() ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } + +protected: + void *_handle; +}; +} // namespace detail + +#ifdef ZMQ_CPP11 +enum class socket_type : int +{ + req = ZMQ_REQ, + rep = ZMQ_REP, + dealer = ZMQ_DEALER, + router = ZMQ_ROUTER, + pub = ZMQ_PUB, + sub = ZMQ_SUB, + xpub = ZMQ_XPUB, + xsub = ZMQ_XSUB, + push = ZMQ_PUSH, + pull = ZMQ_PULL, +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + server = ZMQ_SERVER, + client = ZMQ_CLIENT, + radio = ZMQ_RADIO, + dish = ZMQ_DISH, +#endif +#if ZMQ_VERSION_MAJOR >= 4 + stream = ZMQ_STREAM, +#endif + pair = ZMQ_PAIR +}; +#endif - inline void setsockopt (int option_, const void *optval_, - size_t optvallen_) - { - int rc = zmq_setsockopt (ptr, option_, optval_, optvallen_); - if (rc != 0) - throw error_t (); - } +struct from_handle_t +{ + struct _private {}; // disabling use other than with from_handle + ZMQ_CONSTEXPR_FN ZMQ_EXPLICIT from_handle_t(_private /*p*/) ZMQ_NOTHROW {} +}; - inline void getsockopt (int option_, void *optval_, - size_t *optvallen_) - { - int rc = zmq_getsockopt (ptr, option_, optval_, optvallen_); - if (rc != 0) - throw error_t (); - } - - inline void bind (const char *addr_) - { - int rc = zmq_bind (ptr, addr_); - if (rc != 0) - throw error_t (); - } +ZMQ_CONSTEXPR_VAR from_handle_t from_handle = from_handle_t(from_handle_t::_private()); - inline void unbind (const char *addr_) - { - int rc = zmq_unbind (ptr, addr_); - if (rc != 0) - throw error_t (); - } +// A non-owning nullable reference to a socket. +// The reference is invalidated on socket close or destruction. +class socket_ref : public detail::socket_base +{ + public: + socket_ref() ZMQ_NOTHROW : detail::socket_base() {} +#ifdef ZMQ_CPP11 + socket_ref(std::nullptr_t) ZMQ_NOTHROW : detail::socket_base() {} +#endif + socket_ref(from_handle_t /*fh*/, void *handle) ZMQ_NOTHROW + : detail::socket_base(handle) {} +}; - inline void connect (const char *addr_) - { - int rc = zmq_connect (ptr, addr_); - if (rc != 0) - throw error_t (); - } +#ifdef ZMQ_CPP11 +inline bool operator==(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW +{ + return sr.handle() == nullptr; +} +inline bool operator==(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW +{ + return sr.handle() == nullptr; +} +inline bool operator!=(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW +{ + return !(sr == nullptr); +} +inline bool operator!=(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW +{ + return !(sr == nullptr); +} +#endif - inline void disconnect (const char *addr_) - { - int rc = zmq_disconnect (ptr, addr_); - if (rc != 0) - throw error_t (); - } +inline bool operator==(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return std::equal_to()(a.handle(), b.handle()); +} +inline bool operator!=(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return !(a == b); +} +inline bool operator<(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return std::less()(a.handle(), b.handle()); +} +inline bool operator>(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return b < a; +} +inline bool operator<=(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return !(a > b); +} +inline bool operator>=(socket_ref a, socket_ref b) ZMQ_NOTHROW +{ + return !(a < b); +} - inline bool connected() - { - return(ptr != NULL); - } - - inline size_t send (const void *buf_, size_t len_, int flags_ = 0) - { - int nbytes = zmq_send (ptr, buf_, len_, flags_); - if (nbytes >= 0) - return (size_t) nbytes; - if (zmq_errno () == EAGAIN) - return 0; - throw error_t (); - } +} // namespace zmq - inline bool send (message_t &msg_, int flags_ = 0) - { - int nbytes = zmq_msg_send (&(msg_.msg), ptr, flags_); - if (nbytes >= 0) - return true; - if (zmq_errno () == EAGAIN) - return false; - throw error_t (); - } +#ifdef ZMQ_CPP11 +namespace std +{ +template<> +struct hash +{ + size_t operator()(zmq::socket_ref sr) const ZMQ_NOTHROW + { + return hash()(sr.handle()); + } +}; +} // namespace std +#endif + +namespace zmq +{ + +class socket_t : public detail::socket_base +{ + friend class monitor_t; + + public: + socket_t() ZMQ_NOTHROW + : detail::socket_base(ZMQ_NULLPTR) + , ctxptr(ZMQ_NULLPTR) + { + } + + socket_t(context_t &context_, int type_) + : detail::socket_base(zmq_socket(static_cast(context_), type_)) + , ctxptr(static_cast(context_)) + { + if (_handle == ZMQ_NULLPTR) + throw error_t(); + } + +#ifdef ZMQ_CPP11 + socket_t(context_t &context_, socket_type type_) + : socket_t(context_, static_cast(type_)) + { + } +#endif #ifdef ZMQ_HAS_RVALUE_REFS - inline bool send (message_t &&msg_, int flags_ = 0) - { - return send(msg_, flags_); - } + socket_t(socket_t &&rhs) ZMQ_NOTHROW : detail::socket_base(rhs._handle), ctxptr(rhs.ctxptr) + { + rhs._handle = ZMQ_NULLPTR; + rhs.ctxptr = ZMQ_NULLPTR; + } + socket_t &operator=(socket_t &&rhs) ZMQ_NOTHROW + { + close(); + std::swap(_handle, rhs._handle); + return *this; + } #endif - inline size_t recv (void *buf_, size_t len_, int flags_ = 0) - { - int nbytes = zmq_recv (ptr, buf_, len_, flags_); - if (nbytes >= 0) - return (size_t) nbytes; - if (zmq_errno () == EAGAIN) - return 0; - throw error_t (); - } + ~socket_t() ZMQ_NOTHROW { close(); } - inline bool recv (message_t *msg_, int flags_ = 0) - { - int nbytes = zmq_msg_recv (&(msg_->msg), ptr, flags_); - if (nbytes >= 0) - return true; - if (zmq_errno () == EAGAIN) - return false; - throw error_t (); + operator void *() ZMQ_NOTHROW { return _handle; } + + operator void const *() const ZMQ_NOTHROW { return _handle; } + + void close() ZMQ_NOTHROW + { + if (_handle == ZMQ_NULLPTR) + // already closed + return; + int rc = zmq_close(_handle); + ZMQ_ASSERT(rc == 0); + _handle = ZMQ_NULLPTR; + } + + void swap(socket_t &other) ZMQ_NOTHROW + { + std::swap(_handle, other._handle); + std::swap(ctxptr, other.ctxptr); + } + + operator socket_ref() ZMQ_NOTHROW + { + return socket_ref(from_handle, _handle); + } + + private: + void *ctxptr; + + socket_t(const socket_t &) ZMQ_DELETED_FUNCTION; + void operator=(const socket_t &) ZMQ_DELETED_FUNCTION; + + // used by monitor_t + socket_t(void *context_, int type_) + : detail::socket_base(zmq_socket(context_, type_)) + , ctxptr(context_) + { + if (_handle == ZMQ_NULLPTR) + throw error_t(); + } +}; + +inline void swap(socket_t &a, socket_t &b) ZMQ_NOTHROW { + a.swap(b); +} + +ZMQ_DEPRECATED("from 4.3.1, use proxy taking socket_t objects") +inline void proxy(void *frontend, void *backend, void *capture) +{ + int rc = zmq_proxy(frontend, backend, capture); + if (rc != 0) + throw error_t(); +} + +inline void +proxy(socket_ref frontend, socket_ref backend, socket_ref capture = socket_ref()) +{ + int rc = zmq_proxy(frontend.handle(), backend.handle(), capture.handle()); + if (rc != 0) + throw error_t(); +} + +#ifdef ZMQ_HAS_PROXY_STEERABLE +ZMQ_DEPRECATED("from 4.3.1, use proxy_steerable taking socket_t objects") +inline void +proxy_steerable(void *frontend, void *backend, void *capture, void *control) +{ + int rc = zmq_proxy_steerable(frontend, backend, capture, control); + if (rc != 0) + throw error_t(); +} + +inline void proxy_steerable(socket_ref frontend, + socket_ref backend, + socket_ref capture, + socket_ref control) +{ + int rc = zmq_proxy_steerable(frontend.handle(), backend.handle(), + capture.handle(), control.handle()); + if (rc != 0) + throw error_t(); +} +#endif + +class monitor_t +{ + public: + monitor_t() : _socket(), _monitor_socket() {} + + virtual ~monitor_t() + { + close(); + } + +#ifdef ZMQ_HAS_RVALUE_REFS + monitor_t(monitor_t &&rhs) ZMQ_NOTHROW : _socket(), _monitor_socket() + { + std::swap(_socket, rhs._socket); + std::swap(_monitor_socket, rhs._monitor_socket); + } + + monitor_t &operator=(monitor_t &&rhs) ZMQ_NOTHROW + { + close(); + _socket = socket_ref(); + std::swap(_socket, rhs._socket); + std::swap(_monitor_socket, rhs._monitor_socket); + return *this; + } +#endif + + + void + monitor(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + monitor(socket, addr.c_str(), events); + } + + void monitor(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) + { + init(socket, addr_, events); + while (true) { + check_event(-1); } - - private: - void *ptr; - void *ctxptr; + } - socket_t (const socket_t&) ZMQ_DELETED_FUNCTION; - void operator = (const socket_t&) ZMQ_DELETED_FUNCTION; - }; + void init(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + init(socket, addr.c_str(), events); + } - class monitor_t + void init(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) { - public: - monitor_t() : socketPtr(NULL) {} - virtual ~monitor_t() {} + int rc = zmq_socket_monitor(socket.handle(), addr_, events); + if (rc != 0) + throw error_t(); + + _socket = socket; + _monitor_socket = socket_t(socket.ctxptr, ZMQ_PAIR); + _monitor_socket.connect(addr_); + + on_monitor_started(); + } + + bool check_event(int timeout = 0) + { + assert(_monitor_socket); + + zmq_msg_t eventMsg; + zmq_msg_init(&eventMsg); + + zmq::pollitem_t items[] = { + {_monitor_socket.handle(), 0, ZMQ_POLLIN, 0}, + }; + + zmq::poll(&items[0], 1, timeout); + + if (items[0].revents & ZMQ_POLLIN) { + int rc = zmq_msg_recv(&eventMsg, _monitor_socket.handle(), 0); + if (rc == -1 && zmq_errno() == ETERM) + return false; + assert(rc != -1); + + } else { + zmq_msg_close(&eventMsg); + return false; + } - void monitor(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) - { - int rc = zmq_socket_monitor(socket.ptr, addr_, events); - if (rc != 0) - throw error_t (); - - socketPtr = socket.ptr; - void *s = zmq_socket (socket.ctxptr, ZMQ_PAIR); - assert (s); - - rc = zmq_connect (s, addr_); - assert (rc == 0); - - on_monitor_started(); - - while (true) { - zmq_msg_t eventMsg; - zmq_msg_init (&eventMsg); - rc = zmq_recvmsg (s, &eventMsg, 0); - if (rc == -1 && zmq_errno() == ETERM) - break; - assert (rc != -1); #if ZMQ_VERSION_MAJOR >= 4 - const char* data = static_cast(zmq_msg_data(&eventMsg)); - zmq_event_t msgEvent; - memcpy(&msgEvent.event, data, sizeof(uint16_t)); data += sizeof(uint16_t); - memcpy(&msgEvent.value, data, sizeof(int32_t)); - zmq_event_t* event = &msgEvent; + const char *data = static_cast(zmq_msg_data(&eventMsg)); + zmq_event_t msgEvent; + memcpy(&msgEvent.event, data, sizeof(uint16_t)); + data += sizeof(uint16_t); + memcpy(&msgEvent.value, data, sizeof(int32_t)); + zmq_event_t *event = &msgEvent; #else - zmq_event_t* event = static_cast(zmq_msg_data(&eventMsg)); + zmq_event_t *event = static_cast(zmq_msg_data(&eventMsg)); #endif - + #ifdef ZMQ_NEW_MONITOR_EVENT_LAYOUT - zmq_msg_t addrMsg; - zmq_msg_init (&addrMsg); - rc = zmq_recvmsg (s, &addrMsg, 0); - if (rc == -1 && zmq_errno() == ETERM) - break; - assert (rc != -1); - const char* str = static_cast(zmq_msg_data (&addrMsg)); - std::string address(str, str + zmq_msg_size(&addrMsg)); - zmq_msg_close (&addrMsg); + zmq_msg_t addrMsg; + zmq_msg_init(&addrMsg); + int rc = zmq_msg_recv(&addrMsg, _monitor_socket.handle(), 0); + if (rc == -1 && zmq_errno() == ETERM) { + zmq_msg_close(&eventMsg); + return false; + } + + assert(rc != -1); + const char *str = static_cast(zmq_msg_data(&addrMsg)); + std::string address(str, str + zmq_msg_size(&addrMsg)); + zmq_msg_close(&addrMsg); #else - // Bit of a hack, but all events in the zmq_event_t union have the same layout so this will work for all event types. - std::string address = event->data.connected.addr; + // Bit of a hack, but all events in the zmq_event_t union have the same layout so this will work for all event types. + std::string address = event->data.connected.addr; #endif #ifdef ZMQ_EVENT_MONITOR_STOPPED - if (event->event == ZMQ_EVENT_MONITOR_STOPPED) - break; -#endif - - switch (event->event) { - case ZMQ_EVENT_CONNECTED: - on_event_connected(*event, address.c_str()); - break; - case ZMQ_EVENT_CONNECT_DELAYED: - on_event_connect_delayed(*event, address.c_str()); - break; - case ZMQ_EVENT_CONNECT_RETRIED: - on_event_connect_retried(*event, address.c_str()); - break; - case ZMQ_EVENT_LISTENING: - on_event_listening(*event, address.c_str()); - break; - case ZMQ_EVENT_BIND_FAILED: - on_event_bind_failed(*event, address.c_str()); - break; - case ZMQ_EVENT_ACCEPTED: - on_event_accepted(*event, address.c_str()); - break; - case ZMQ_EVENT_ACCEPT_FAILED: - on_event_accept_failed(*event, address.c_str()); - break; - case ZMQ_EVENT_CLOSED: - on_event_closed(*event, address.c_str()); - break; - case ZMQ_EVENT_CLOSE_FAILED: - on_event_close_failed(*event, address.c_str()); - break; - case ZMQ_EVENT_DISCONNECTED: - on_event_disconnected(*event, address.c_str()); - break; - default: - on_event_unknown(*event, address.c_str()); - break; - } - zmq_msg_close (&eventMsg); - } - zmq_close (s); - socketPtr = NULL; + if (event->event == ZMQ_EVENT_MONITOR_STOPPED) { + zmq_msg_close(&eventMsg); + return false; + } + +#endif + + switch (event->event) { + case ZMQ_EVENT_CONNECTED: + on_event_connected(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_DELAYED: + on_event_connect_delayed(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_RETRIED: + on_event_connect_retried(*event, address.c_str()); + break; + case ZMQ_EVENT_LISTENING: + on_event_listening(*event, address.c_str()); + break; + case ZMQ_EVENT_BIND_FAILED: + on_event_bind_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPTED: + on_event_accepted(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPT_FAILED: + on_event_accept_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSED: + on_event_closed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSE_FAILED: + on_event_close_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_DISCONNECTED: + on_event_disconnected(*event, address.c_str()); + break; +#ifdef ZMQ_BUILD_DRAFT_API +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + case ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL: + on_event_handshake_failed_no_detail(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL: + on_event_handshake_failed_protocol(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_AUTH: + on_event_handshake_failed_auth(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEEDED: + on_event_handshake_succeeded(*event, address.c_str()); + break; +#elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + case ZMQ_EVENT_HANDSHAKE_FAILED: + on_event_handshake_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEED: + on_event_handshake_succeed(*event, address.c_str()); + break; +#endif +#endif + default: + on_event_unknown(*event, address.c_str()); + break; } + zmq_msg_close(&eventMsg); + + return true; + } #ifdef ZMQ_EVENT_MONITOR_STOPPED - void abort() - { - if (socketPtr) - zmq_socket_monitor(socketPtr, NULL, 0); + void abort() + { + if (_socket) + zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); + + _socket = socket_ref(); + } +#endif + virtual void on_monitor_started() {} + virtual void on_event_connected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_delayed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_retried(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_listening(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_bind_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accepted(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accept_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_closed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_close_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_disconnected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + virtual void on_event_handshake_failed_no_detail(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_protocol(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_auth(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeeded(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + virtual void on_event_handshake_failed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#endif + virtual void on_event_unknown(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + + private: + monitor_t(const monitor_t &) ZMQ_DELETED_FUNCTION; + void operator=(const monitor_t &) ZMQ_DELETED_FUNCTION; + + socket_ref _socket; + socket_t _monitor_socket; + + void close() ZMQ_NOTHROW + { + if (_socket) + zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); + _monitor_socket.close(); + } +}; + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + +// polling events +enum class event_flags : short +{ + none = 0, + pollin = ZMQ_POLLIN, + pollout = ZMQ_POLLOUT, + pollerr = ZMQ_POLLERR, + pollpri = ZMQ_POLLPRI +}; + +constexpr event_flags operator|(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr event_flags operator&(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr event_flags operator^(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr event_flags operator~(event_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + +struct no_user_data; + +// layout compatible with zmq_poller_event_t +template +struct poller_event +{ + socket_ref socket; +#ifdef _WIN32 + SOCKET fd; +#else + int fd; +#endif + T *user_data; + event_flags events; +}; + +template class poller_t +{ + public: + using event_type = poller_event; + + poller_t() : poller_ptr(zmq_poller_new()) + { + if (!poller_ptr) + throw error_t(); + } + + template< + typename Dummy = void, + typename = + typename std::enable_if::value, Dummy>::type> + void add(zmq::socket_ref socket, event_flags events, T *user_data) + { + add_impl(socket, events, user_data); + } + + void add(zmq::socket_ref socket, event_flags events) + { + add_impl(socket, events, nullptr); + } + + void remove(zmq::socket_ref socket) + { + if (0 != zmq_poller_remove(poller_ptr.get(), socket.handle())) { + throw error_t(); + } + } + + void modify(zmq::socket_ref socket, event_flags events) + { + if (0 + != zmq_poller_modify(poller_ptr.get(), socket.handle(), + static_cast(events))) { + throw error_t(); } + } + + size_t wait_all(std::vector &poller_events, + const std::chrono::milliseconds timeout) + { + int rc = zmq_poller_wait_all( + poller_ptr.get(), + reinterpret_cast(poller_events.data()), + static_cast(poller_events.size()), + static_cast(timeout.count())); + if (rc > 0) + return static_cast(rc); + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + if (zmq_errno() == EAGAIN) +#else + if (zmq_errno() == ETIMEDOUT) #endif - virtual void on_monitor_started() {} - virtual void on_event_connected(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_connect_delayed(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_connect_retried(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_listening(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_bind_failed(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_accepted(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_accept_failed(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_closed(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_close_failed(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_disconnected(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - virtual void on_event_unknown(const zmq_event_t &event_, const char* addr_) { (void)event_; (void)addr_; } - private: - void* socketPtr; + return 0; + + throw error_t(); + } + + private: + struct destroy_poller_t + { + void operator()(void *ptr) noexcept + { + int rc = zmq_poller_destroy(&ptr); + ZMQ_ASSERT(rc == 0); + } }; + + std::unique_ptr poller_ptr; + + void add_impl(zmq::socket_ref socket, event_flags events, T *user_data) + { + if (0 + != zmq_poller_add(poller_ptr.get(), socket.handle(), + user_data, static_cast(events))) { + throw error_t(); + } + } +}; +#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + +inline std::ostream &operator<<(std::ostream &os, const message_t &msg) +{ + return os << msg.str(); } -#endif +} // namespace zmq + +#endif // __ZMQ_HPP_INCLUDED__ -- cgit v1.2.3