diff options
Diffstat (limited to 'lib/edi')
-rw-r--r-- | lib/edi/ETIDecoder.cpp | 253 | ||||
-rw-r--r-- | lib/edi/ETIDecoder.hpp | 49 | ||||
-rw-r--r-- | lib/edi/PFT.cpp | 2 | ||||
-rw-r--r-- | lib/edi/PFT.hpp | 5 | ||||
-rw-r--r-- | lib/edi/buffer_unpack.hpp | 2 | ||||
-rw-r--r-- | lib/edi/common.cpp | 355 | ||||
-rw-r--r-- | lib/edi/common.hpp | 105 | ||||
-rw-r--r-- | lib/edi/eti.hpp | 4 |
8 files changed, 513 insertions, 262 deletions
diff --git a/lib/edi/ETIDecoder.cpp b/lib/edi/ETIDecoder.cpp index a5d817e..1fa9c3c 100644 --- a/lib/edi/ETIDecoder.cpp +++ b/lib/edi/ETIDecoder.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2017 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -22,7 +22,7 @@ #include "buffer_unpack.hpp" #include "crc.h" #include "Log.h" -#include <stdio.h> +#include <cstdio> #include <cassert> #include <sstream> @@ -30,242 +30,40 @@ namespace EdiDecoder { using namespace std; -ETIDecoder::ETIDecoder(DataCollector& data_collector, bool verbose) : +ETIDecoder::ETIDecoder(ETIDataCollector& data_collector, bool verbose) : m_data_collector(data_collector), - m_last_seq(0) + m_dispatcher(std::bind(&ETIDecoder::packet_completed, this), verbose) { - m_pft.setVerbose(verbose); + using std::placeholders::_1; + using std::placeholders::_2; + m_dispatcher.register_tag("*ptr", + std::bind(&ETIDecoder::decode_starptr, this, _1, _2)); + m_dispatcher.register_tag("deti", + std::bind(&ETIDecoder::decode_deti, this, _1, _2)); + m_dispatcher.register_tag("est", + std::bind(&ETIDecoder::decode_estn, this, _1, _2)); + m_dispatcher.register_tag("*dmy", + std::bind(&ETIDecoder::decode_stardmy, this, _1, _2)); } void ETIDecoder::push_bytes(const vector<uint8_t> &buf) { - copy(buf.begin(), buf.end(), back_inserter(m_input_data)); - - while (m_input_data.size() > 2) { - if (m_input_data[0] == 'A' and m_input_data[1] == 'F') { - const decode_state_t st = decode_afpacket(m_input_data); - - if (st.num_bytes_consumed == 0 and not st.complete) { - // We need to refill our buffer - break; - } - - if (st.num_bytes_consumed) { - vector<uint8_t> remaining_data; - copy(m_input_data.begin() + st.num_bytes_consumed, - m_input_data.end(), - back_inserter(remaining_data)); - m_input_data = remaining_data; - } - - if (st.complete) { - m_data_collector.assemble(); - } - - } - else if (m_input_data[0] == 'P' and m_input_data[1] == 'F') { - PFT::Fragment fragment; - const size_t fragment_bytes = fragment.loadData(m_input_data); - - if (fragment_bytes == 0) { - // We need to refill our buffer - break; - } - - vector<uint8_t> remaining_data; - copy(m_input_data.begin() + fragment_bytes, - m_input_data.end(), - back_inserter(remaining_data)); - m_input_data = remaining_data; - - if (fragment.isValid()) { - m_pft.pushPFTFrag(fragment); - } - - auto af = m_pft.getNextAFPacket(); - if (not af.empty()) { - decode_state_t st = decode_afpacket(af); - - if (st.complete) { - m_data_collector.assemble(); - } - } - - } - else { - etiLog.log(warn,"Unknown %c!", *m_input_data.data()); - m_input_data.erase(m_input_data.begin()); - } - } + m_dispatcher.push_bytes(buf); } void ETIDecoder::push_packet(const vector<uint8_t> &buf) { - if (buf.size() < 2) { - throw std::invalid_argument("Not enough bytes to read EDI packet header"); - } - - if (buf[0] == 'A' and buf[1] == 'F') { - const decode_state_t st = decode_afpacket(buf); - - if (st.complete) { - m_data_collector.assemble(); - } - - } - else if (buf[0] == 'P' and buf[1] == 'F') { - PFT::Fragment fragment; - fragment.loadData(buf); - - if (fragment.isValid()) { - m_pft.pushPFTFrag(fragment); - } - - auto af = m_pft.getNextAFPacket(); - if (not af.empty()) { - const decode_state_t st = decode_afpacket(af); - - if (st.complete) { - m_data_collector.assemble(); - } - } - } - else { - const char packettype[3] = {(char)buf[0], (char)buf[1], '\0'}; - std::stringstream ss; - ss << "Unknown EDI packet "; - ss << packettype; - throw std::invalid_argument(ss.str()); - } + m_dispatcher.push_packet(buf); } void ETIDecoder::setMaxDelay(int num_af_packets) { - m_pft.setMaxDelay(num_af_packets); + m_dispatcher.setMaxDelay(num_af_packets); } #define AFPACKET_HEADER_LEN 10 // includes SYNC -ETIDecoder::decode_state_t ETIDecoder::decode_afpacket( - const std::vector<uint8_t> &input_data) -{ - if (input_data.size() < AFPACKET_HEADER_LEN) { - return {false, 0}; - } - - // read length from packet - uint32_t taglength = read_32b(input_data.begin() + 2); - uint16_t seq = read_16b(input_data.begin() + 6); - - const size_t crclength = 2; - if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { - return {false, 0}; - } - - if (m_last_seq + 1 != seq) { - etiLog.level(warn) << "EDI AF Packet sequence error, " << seq; - } - m_last_seq = seq; - - bool has_crc = (input_data[8] & 0x80) ? true : false; - uint8_t major_revision = (input_data[8] & 0x70) >> 4; - uint8_t minor_revision = input_data[8] & 0x0F; - if (major_revision != 1 or minor_revision != 0) { - throw invalid_argument("EDI AF Packet has wrong revision " + - to_string(major_revision) + "." + to_string(minor_revision)); - } - uint8_t pt = input_data[9]; - if (pt != 'T') { - // only support Tag - return {false, 0}; - } - - - if (not has_crc) { - throw invalid_argument("AF packet not supported, has no CRC"); - } - - uint16_t crc = 0xffff; - for (size_t i = 0; i < AFPACKET_HEADER_LEN + taglength; i++) { - crc = crc16(crc, &input_data[i], 1); - } - crc ^= 0xffff; - - uint16_t packet_crc = read_16b(input_data.begin() + AFPACKET_HEADER_LEN + taglength); - - if (packet_crc != crc) { - throw invalid_argument( - "AF Packet crc wrong"); - } - else { - vector<uint8_t> payload(taglength); - copy(input_data.begin() + AFPACKET_HEADER_LEN, - input_data.begin() + AFPACKET_HEADER_LEN + taglength, - payload.begin()); - - return {decode_tagpacket(payload), - AFPACKET_HEADER_LEN + taglength + 2}; - } -} - -bool ETIDecoder::decode_tagpacket(const vector<uint8_t> &payload) -{ - size_t length = 0; - - bool success = true; - - for (size_t i = 0; i + 8 < payload.size(); i += 8 + length) { - char tag_sz[5]; - tag_sz[4] = '\0'; - copy(payload.begin() + i, payload.begin() + i + 4, tag_sz); - - string tag(tag_sz); - - uint32_t taglength = read_32b(payload.begin() + i + 4); - - if (taglength % 8 != 0) { - etiLog.log(warn, "Invalid tag length!"); - break; - } - taglength /= 8; - - length = taglength; - - vector<uint8_t> tag_value(taglength); - copy( payload.begin() + i+8, - payload.begin() + i+8+taglength, - tag_value.begin()); - - bool tagsuccess = false; - if (tag == "*ptr") { - tagsuccess = decode_starptr(tag_value); - } - else if (tag == "deti") { - tagsuccess = decode_deti(tag_value); - } - else if (tag.substr(0, 3) == "est") { - uint8_t n = tag_sz[3]; - tagsuccess = decode_estn(tag_value, n); - } - else if (tag == "*dmy") { - tagsuccess = decode_stardmy(tag_value); - } - else { - etiLog.log(warn, "Unknown TAG %s", tag.c_str()); - break; - } - - if (not tagsuccess) { - etiLog.log(warn, "Error decoding TAG %s", tag.c_str()); - success = tagsuccess; - break; - } - } - - return success; -} - -bool ETIDecoder::decode_starptr(const vector<uint8_t> &value) +bool ETIDecoder::decode_starptr(const vector<uint8_t> &value, uint16_t) { if (value.size() != 0x40 / 8) { etiLog.log(warn, "Incorrect length %02lx for *PTR", value.size()); @@ -285,7 +83,7 @@ bool ETIDecoder::decode_starptr(const vector<uint8_t> &value) return true; } -bool ETIDecoder::decode_deti(const vector<uint8_t> &value) +bool ETIDecoder::decode_deti(const vector<uint8_t> &value, uint16_t) { /* uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); @@ -364,7 +162,7 @@ bool ETIDecoder::decode_deti(const vector<uint8_t> &value) fic.begin()); i += fic_length; - m_data_collector.update_fic(fic); + m_data_collector.update_fic(move(fic)); } if (rfudf) { @@ -385,7 +183,7 @@ bool ETIDecoder::decode_deti(const vector<uint8_t> &value) return true; } -bool ETIDecoder::decode_estn(const vector<uint8_t> &value, uint8_t n) +bool ETIDecoder::decode_estn(const vector<uint8_t> &value, uint16_t n) { uint32_t sstc = read_24b(value.begin()); @@ -404,14 +202,19 @@ bool ETIDecoder::decode_estn(const vector<uint8_t> &value, uint8_t n) value.end(), back_inserter(stc.mst)); - m_data_collector.add_subchannel(stc); + m_data_collector.add_subchannel(move(stc)); return true; } -bool ETIDecoder::decode_stardmy(const vector<uint8_t>& /*value*/) +bool ETIDecoder::decode_stardmy(const vector<uint8_t>& /*value*/, uint16_t) { return true; } +void ETIDecoder::packet_completed() +{ + m_data_collector.assemble(); +} + } diff --git a/lib/edi/ETIDecoder.hpp b/lib/edi/ETIDecoder.hpp index 37a564f..f5d0b81 100644 --- a/lib/edi/ETIDecoder.hpp +++ b/lib/edi/ETIDecoder.hpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2017 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -20,12 +20,12 @@ */ #pragma once -#include <stdint.h> +#include "eti.hpp" +#include "common.hpp" +#include <cstdint> #include <deque> #include <string> #include <vector> -#include "PFT.hpp" -#include "eti.hpp" namespace EdiDecoder { @@ -33,7 +33,7 @@ namespace EdiDecoder { // EDI. // // Number of streams is given separately, and frame length -// is calculated in the DataCollector +// is calculated in the ETIDataCollector struct eti_fc_data { bool atstf; uint32_t tsta; @@ -58,10 +58,10 @@ struct eti_stc_data { }; /* A class that receives multiplex data must implement the interface described - * in the DataCollector. This can be e.g. a converter to ETI, or something that + * in the ETIDataCollector. This can be e.g. a converter to ETI, or something that * prepares data structures for a modulator. */ -class DataCollector { +class ETIDataCollector { public: // Tell the ETIWriter what EDI protocol we receive in *ptr. // This is not part of the ETI data, but is used as check @@ -73,21 +73,19 @@ class DataCollector { // Update the data for the frame characterisation virtual void update_fc_data(const eti_fc_data& fc_data) = 0; - virtual void update_fic(const std::vector<uint8_t>& fic) = 0; + virtual void update_fic(std::vector<uint8_t>&& fic) = 0; virtual void update_err(uint8_t err) = 0; // In addition to TSTA in ETI, EDI also transports more time // stamp information. - virtual void update_edi_time( - uint32_t utco, - uint32_t seconds) = 0; + virtual void update_edi_time(uint32_t utco, uint32_t seconds) = 0; virtual void update_mnsc(uint16_t mnsc) = 0; virtual void update_rfu(uint16_t rfu) = 0; - virtual void add_subchannel(const eti_stc_data& stc) = 0; + virtual void add_subchannel(eti_stc_data&& stc) = 0; // Tell the ETIWriter that the AFPacket is complete virtual void assemble(void) = 0; @@ -101,7 +99,7 @@ class DataCollector { */ class ETIDecoder { public: - ETIDecoder(DataCollector& data_collector, bool verbose); + ETIDecoder(ETIDataCollector& data_collector, bool verbose); /* Push bytes into the decoder. The buf can contain more * than a single packet. This is useful when reading from streams @@ -120,27 +118,16 @@ class ETIDecoder { void setMaxDelay(int num_af_packets); private: - struct decode_state_t { - decode_state_t(bool _complete, size_t _num_bytes_consumed) : - complete(_complete), num_bytes_consumed(_num_bytes_consumed) {} - bool complete; - size_t num_bytes_consumed; - }; - - decode_state_t decode_afpacket(const std::vector<uint8_t> &input_data); - bool decode_tagpacket(const std::vector<uint8_t> &payload); - bool decode_starptr(const std::vector<uint8_t> &value); - bool decode_deti(const std::vector<uint8_t> &value); - bool decode_estn(const std::vector<uint8_t> &value, uint8_t n); - bool decode_stardmy(const std::vector<uint8_t> &value); - - DataCollector& m_data_collector; + bool decode_starptr(const std::vector<uint8_t> &value, uint16_t); + bool decode_deti(const std::vector<uint8_t> &value, uint16_t); + bool decode_estn(const std::vector<uint8_t> &value, uint16_t n); + bool decode_stardmy(const std::vector<uint8_t> &value, uint16_t); - PFT::PFT m_pft; + void packet_completed(); - uint16_t m_last_seq; + ETIDataCollector& m_data_collector; + TagDispatcher m_dispatcher; - std::vector<uint8_t> m_input_data; }; } diff --git a/lib/edi/PFT.cpp b/lib/edi/PFT.cpp index aff7929..158b206 100644 --- a/lib/edi/PFT.cpp +++ b/lib/edi/PFT.cpp @@ -20,7 +20,7 @@ * ------------------------------------------------------------------- */ -#include <stdio.h> +#include <cstdio> #include <cassert> #include <cstring> #include <sstream> diff --git a/lib/edi/PFT.hpp b/lib/edi/PFT.hpp index 779509b..208fd70 100644 --- a/lib/edi/PFT.hpp +++ b/lib/edi/PFT.hpp @@ -21,10 +21,11 @@ */ #pragma once -#include <stdio.h> +#include <cstdio> +#include <cstdint> #include <vector> #include <map> -#include <stdint.h> +#include <string> namespace EdiDecoder { namespace PFT { diff --git a/lib/edi/buffer_unpack.hpp b/lib/edi/buffer_unpack.hpp index 05a1534..a996017 100644 --- a/lib/edi/buffer_unpack.hpp +++ b/lib/edi/buffer_unpack.hpp @@ -20,7 +20,7 @@ */ #pragma once -#include <stdint.h> +#include <cstdint> namespace EdiDecoder { diff --git a/lib/edi/common.cpp b/lib/edi/common.cpp new file mode 100644 index 0000000..ac8ec0c --- /dev/null +++ b/lib/edi/common.cpp @@ -0,0 +1,355 @@ +/* + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "common.hpp" +#include "buffer_unpack.hpp" +#include "Log.h" +#include "crc.h" +#include <iomanip> +#include <sstream> +#include <cassert> +#include <cmath> +#include <cstdio> + +namespace EdiDecoder { + +using namespace std; + +bool frame_timestamp_t::valid() const +{ + return tsta != 0xFFFFFF; +} + +string frame_timestamp_t::to_string() const +{ + const time_t seconds_in_unix_epoch = to_unix_epoch(); + + stringstream ss; + if (valid()) { + ss << "Timestamp: "; + } + else { + ss << "Timestamp not valid: "; + } + ss << std::put_time(std::gmtime(&seconds_in_unix_epoch), "%c %Z") << + " + " << ((double)tsta / 16384000.0); + return ss.str(); +} + +time_t frame_timestamp_t::to_unix_epoch() const +{ + // EDI epoch: 2000-01-01T00:00:00Z + // Convert using + // TZ=UTC python -c 'import datetime; print(datetime.datetime(2000,1,1,0,0,0,0).strftime("%s"))' + return 946684800 + seconds - utco; +} + +double frame_timestamp_t::diff_ms(const frame_timestamp_t& other) const +{ + const double lhs = (double)seconds + (tsta / 16384000.0); + const double rhs = (double)other.seconds + (other.tsta / 16384000.0); + return lhs - rhs; +} + +frame_timestamp_t frame_timestamp_t::from_unix_epoch(std::time_t time, uint32_t tai_utc_offset, uint32_t tsta) +{ + frame_timestamp_t ts; + + const std::time_t posix_timestamp_1_jan_2000 = 946684800; + + ts.utco = tai_utc_offset - 32; + ts.seconds = time - posix_timestamp_1_jan_2000 + ts.utco; + ts.tsta = tsta; + return ts; +} + +std::chrono::system_clock::time_point frame_timestamp_t::to_system_clock() const +{ + auto ts = chrono::system_clock::from_time_t(to_unix_epoch()); + + // PPS offset in seconds = tsta / 16384000 + ts += chrono::nanoseconds(std::lrint(tsta / 0.016384)); + + return ts; +} + + +TagDispatcher::TagDispatcher( + std::function<void()>&& af_packet_completed, bool verbose) : + m_af_packet_completed(move(af_packet_completed)) +{ + m_pft.setVerbose(verbose); +} + +void TagDispatcher::push_bytes(const vector<uint8_t> &buf) +{ + copy(buf.begin(), buf.end(), back_inserter(m_input_data)); + + while (m_input_data.size() > 2) { + if (m_input_data[0] == 'A' and m_input_data[1] == 'F') { + const decode_state_t st = decode_afpacket(m_input_data); + + if (st.num_bytes_consumed == 0 and not st.complete) { + // We need to refill our buffer + break; + } + + if (st.num_bytes_consumed) { + vector<uint8_t> remaining_data; + copy(m_input_data.begin() + st.num_bytes_consumed, + m_input_data.end(), + back_inserter(remaining_data)); + m_input_data = remaining_data; + } + + if (st.complete) { + m_af_packet_completed(); + } + } + else if (m_input_data[0] == 'P' and m_input_data[1] == 'F') { + PFT::Fragment fragment; + const size_t fragment_bytes = fragment.loadData(m_input_data); + + if (fragment_bytes == 0) { + // We need to refill our buffer + break; + } + + vector<uint8_t> remaining_data; + copy(m_input_data.begin() + fragment_bytes, + m_input_data.end(), + back_inserter(remaining_data)); + m_input_data = remaining_data; + + if (fragment.isValid()) { + m_pft.pushPFTFrag(fragment); + } + + auto af = m_pft.getNextAFPacket(); + if (not af.empty()) { + decode_state_t st = decode_afpacket(af); + + if (st.complete) { + m_af_packet_completed(); + } + } + } + else { + etiLog.log(warn,"Unknown %c!", *m_input_data.data()); + m_input_data.erase(m_input_data.begin()); + } + } +} + +void TagDispatcher::push_packet(const vector<uint8_t> &buf) +{ + if (buf.size() < 2) { + throw std::invalid_argument("Not enough bytes to read EDI packet header"); + } + + if (buf[0] == 'A' and buf[1] == 'F') { + const decode_state_t st = decode_afpacket(buf); + + if (st.complete) { + m_af_packet_completed(); + } + + } + else if (buf[0] == 'P' and buf[1] == 'F') { + PFT::Fragment fragment; + fragment.loadData(buf); + + if (fragment.isValid()) { + m_pft.pushPFTFrag(fragment); + } + + auto af = m_pft.getNextAFPacket(); + if (not af.empty()) { + const decode_state_t st = decode_afpacket(af); + + if (st.complete) { + m_af_packet_completed(); + } + } + } + else { + const char packettype[3] = {(char)buf[0], (char)buf[1], '\0'}; + std::stringstream ss; + ss << "Unknown EDI packet "; + ss << packettype; + throw std::invalid_argument(ss.str()); + } +} + +void TagDispatcher::setMaxDelay(int num_af_packets) +{ + m_pft.setMaxDelay(num_af_packets); +} + + +#define AFPACKET_HEADER_LEN 10 // includes SYNC +decode_state_t TagDispatcher::decode_afpacket( + const std::vector<uint8_t> &input_data) +{ + if (input_data.size() < AFPACKET_HEADER_LEN) { + return {false, 0}; + } + + // read length from packet + uint32_t taglength = read_32b(input_data.begin() + 2); + uint16_t seq = read_16b(input_data.begin() + 6); + + const size_t crclength = 2; + if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { + return {false, 0}; + } + + if (m_last_seq + (uint16_t)1 != seq) { + etiLog.level(warn) << "EDI AF Packet sequence error, " << seq; + } + m_last_seq = seq; + + bool has_crc = (input_data[8] & 0x80) ? true : false; + uint8_t major_revision = (input_data[8] & 0x70) >> 4; + uint8_t minor_revision = input_data[8] & 0x0F; + if (major_revision != 1 or minor_revision != 0) { + throw invalid_argument("EDI AF Packet has wrong revision " + + to_string(major_revision) + "." + to_string(minor_revision)); + } + uint8_t pt = input_data[9]; + if (pt != 'T') { + // only support Tag + return {false, 0}; + } + + + if (not has_crc) { + throw invalid_argument("AF packet not supported, has no CRC"); + } + + uint16_t crc = 0xffff; + for (size_t i = 0; i < AFPACKET_HEADER_LEN + taglength; i++) { + crc = crc16(crc, &input_data[i], 1); + } + crc ^= 0xffff; + + uint16_t packet_crc = read_16b(input_data.begin() + AFPACKET_HEADER_LEN + taglength); + + if (packet_crc != crc) { + throw invalid_argument( + "AF Packet crc wrong"); + } + else { + vector<uint8_t> payload(taglength); + copy(input_data.begin() + AFPACKET_HEADER_LEN, + input_data.begin() + AFPACKET_HEADER_LEN + taglength, + payload.begin()); + + return {decode_tagpacket(payload), + AFPACKET_HEADER_LEN + taglength + 2}; + } +} + +void TagDispatcher::register_tag(const std::string& tag, tag_handler&& h) +{ + m_handlers[tag] = move(h); +} + + +bool TagDispatcher::decode_tagpacket(const vector<uint8_t> &payload) +{ + size_t length = 0; + + bool success = true; + + for (size_t i = 0; i + 8 < payload.size(); i += 8 + length) { + char tag_sz[5]; + tag_sz[4] = '\0'; + copy(payload.begin() + i, payload.begin() + i + 4, tag_sz); + + string tag(tag_sz); + + uint32_t taglength = read_32b(payload.begin() + i + 4); + + if (taglength % 8 != 0) { + etiLog.log(warn, "Invalid tag length!"); + break; + } + taglength /= 8; + + length = taglength; + + vector<uint8_t> tag_value(taglength); + copy( payload.begin() + i+8, + payload.begin() + i+8+taglength, + tag_value.begin()); + + bool tagsuccess = false; + bool found = false; + for (auto tag_handler : m_handlers) { + if (tag_handler.first.size() == 4 and tag_handler.first == tag) { + found = true; + tagsuccess = tag_handler.second(tag_value, 0); + } + else if (tag_handler.first.size() == 3 and + tag.substr(0, 3) == tag_handler.first) { + found = true; + uint8_t n = tag_sz[3]; + tagsuccess = tag_handler.second(tag_value, n); + } + else if (tag_handler.first.size() == 2 and + tag.substr(0, 2) == tag_handler.first) { + found = true; + uint16_t n = 0; + n = (uint16_t)(tag_sz[2]) << 8; + n |= (uint16_t)(tag_sz[3]); + tagsuccess = tag_handler.second(tag_value, n); + } + } + + if (not found) { + etiLog.log(warn, "Ignoring unknown TAG %s", tag.c_str()); + break; + } + + if (not tagsuccess) { + etiLog.log(warn, "Error decoding TAG %s", tag.c_str()); + success = tagsuccess; + break; + } + } + + return success; +} + +odr_version_data parse_odr_version_data(const std::vector<uint8_t>& data) +{ + if (data.size() < sizeof(uint32_t)) { + return {}; + } + + const size_t versionstr_length = data.size() - sizeof(uint32_t); + string version(data.begin(), data.begin() + versionstr_length); + uint32_t uptime_s = read_32b(data.begin() + versionstr_length); + + return {version, uptime_s}; +} + +} diff --git a/lib/edi/common.hpp b/lib/edi/common.hpp new file mode 100644 index 0000000..5d15f8d --- /dev/null +++ b/lib/edi/common.hpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +#include "PFT.hpp" +#include <functional> +#include <map> +#include <chrono> +#include <string> +#include <vector> +#include <cstddef> +#include <ctime> + +namespace EdiDecoder { + +struct frame_timestamp_t { + uint32_t seconds = 0; + uint32_t utco = 0; + uint32_t tsta = 0; // According to EN 300 797 Annex B + + bool valid() const; + std::string to_string() const; + std::time_t to_unix_epoch() const; + std::chrono::system_clock::time_point to_system_clock() const; + + double diff_ms(const frame_timestamp_t& other) const; + + static frame_timestamp_t from_unix_epoch(std::time_t time, uint32_t tai_utc_offset, uint32_t tsta); +}; + + +struct decode_state_t { + decode_state_t(bool _complete, size_t _num_bytes_consumed) : + complete(_complete), num_bytes_consumed(_num_bytes_consumed) {} + bool complete; + size_t num_bytes_consumed; +}; + +/* The TagDispatcher takes care of decoding EDI, with or without PFT, and + * will call functions when TAGs are encountered. + * + * PF packets are handed over to the PFT decoder, which will in turn return + * AF packets. AF packets are directly dispatched to the TAG functions. + */ +class TagDispatcher { + public: + TagDispatcher(std::function<void()>&& af_packet_completed, bool verbose); + + /* Push bytes into the decoder. The buf can contain more + * than a single packet. This is useful when reading from streams + * (files, TCP) + */ + void push_bytes(const std::vector<uint8_t> &buf); + + /* Push a complete packet into the decoder. Useful for UDP and other + * datagram-oriented protocols. + */ + void push_packet(const std::vector<uint8_t> &buf); + + /* Set the maximum delay in number of AF Packets before we + * abandon decoding a given pseq. + */ + void setMaxDelay(int num_af_packets); + + using tag_handler = std::function<bool(std::vector<uint8_t>, uint16_t)>; + void register_tag(const std::string& tag, tag_handler&& h); + + private: + decode_state_t decode_afpacket(const std::vector<uint8_t> &input_data); + bool decode_tagpacket(const std::vector<uint8_t> &payload); + + PFT::PFT m_pft; + uint16_t m_last_seq = 0; + std::vector<uint8_t> m_input_data; + std::map<std::string, tag_handler> m_handlers; + std::function<void()> m_af_packet_completed; +}; + +// Data carried inside the ODRv EDI TAG +struct odr_version_data { + std::string version; + uint32_t uptime_s; +}; + +odr_version_data parse_odr_version_data(const std::vector<uint8_t>& data); + +} diff --git a/lib/edi/eti.hpp b/lib/edi/eti.hpp index 451ca48..372f098 100644 --- a/lib/edi/eti.hpp +++ b/lib/edi/eti.hpp @@ -24,10 +24,10 @@ #pragma once -#include <stdint.h> +#include <cstdint> #define PACKED __attribute__ ((packed)) -#include <time.h> +#include <ctime> namespace EdiDecoder { |