diff options
Diffstat (limited to 'lib/edi')
-rw-r--r-- | lib/edi/ETIDecoder.cpp | 388 | ||||
-rw-r--r-- | lib/edi/ETIDecoder.hpp | 74 | ||||
-rw-r--r-- | lib/edi/ETIWriter.cpp | 277 | ||||
-rw-r--r-- | lib/edi/ETIWriter.hpp | 114 | ||||
-rw-r--r-- | lib/edi/PFT.cpp | 553 | ||||
-rw-r--r-- | lib/edi/PFT.hpp | 154 | ||||
-rw-r--r-- | lib/edi/README.md | 1 | ||||
-rw-r--r-- | lib/edi/buffer_unpack.hpp | 62 | ||||
-rw-r--r-- | lib/edi/eti.cpp | 64 | ||||
-rw-r--r-- | lib/edi/eti.hpp | 114 |
10 files changed, 1801 insertions, 0 deletions
diff --git a/lib/edi/ETIDecoder.cpp b/lib/edi/ETIDecoder.cpp new file mode 100644 index 0000000..36e49fa --- /dev/null +++ b/lib/edi/ETIDecoder.cpp @@ -0,0 +1,388 @@ +/* + Copyright (C) 2016 + 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 "ETIDecoder.hpp" +#include "buffer_unpack.hpp" +#include "crc.h" +#include "Log.h" +#include <stdio.h> +#include <cassert> +#include <sstream> + +namespace EdiDecoder { + +using namespace std; + +ETIDecoder::ETIDecoder(ETIWriter& eti_writer) : + m_eti_writer(eti_writer) +{ +} + +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') { + 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_eti_writer.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_eti_writer.assemble(); + } + } + + } + else { + etiLog.log(warn,"Unknown %c!", *m_input_data.data()); + m_input_data.erase(m_input_data.begin()); + } + } +} + +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_eti_writer.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()) { + decode_state_t st = decode_afpacket(af); + + if (st.complete) { + m_eti_writer.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()); + } +} + +void ETIDecoder::setMaxDelay(int num_af_packets) +{ + m_pft.setMaxDelay(num_af_packets); +} + +#define AFPACKET_HEADER_LEN 10 // includes SYNC + +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); + + bool has_crc = (input_data[8] & 0x80) ? true : false; + uint8_t revision = input_data[8] & 0x7F; + uint8_t pt = input_data[9]; + if (pt != 'T') { + // only support Tag + return {false, 0}; + } + + const size_t crclength = 2; + if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { + 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) +{ + if (value.size() != 0x40 / 8) { + etiLog.log(warn, "Incorrect length %02lx for *PTR", value.size()); + return false; + } + + char protocol_sz[5]; + protocol_sz[4] = '\0'; + copy(value.begin(), value.begin() + 4, protocol_sz); + string protocol(protocol_sz); + + uint16_t major = read_16b(value.begin() + 4); + uint16_t minor = read_16b(value.begin() + 6); + + m_eti_writer.update_protocol(protocol, major, minor); + + return true; +} + +bool ETIDecoder::decode_deti(const vector<uint8_t> &value) +{ + /* + uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); + packet.push_back(detiHeader >> 8); + packet.push_back(detiHeader & 0xFF); + */ + + uint16_t detiHeader = read_16b(value.begin()); + + eti_fc_data fc; + + fc.atstf = (detiHeader >> 15) & 0x1; + fc.ficf = (detiHeader >> 14) & 0x1; + bool rfudf = (detiHeader >> 13) & 0x1; + uint8_t fcth = (detiHeader >> 8) & 0x1F; + uint8_t fct = detiHeader & 0xFF; + + fc.dflc = fcth * 250 + fct; // modulo 5000 counter + + uint32_t etiHeader = read_32b(value.begin() + 2); + + uint8_t stat = (etiHeader >> 24) & 0xFF; + + fc.mid = (etiHeader >> 22) & 0x03; + fc.fp = (etiHeader >> 19) & 0x07; + bool rfa = (etiHeader >> 17) & 0x1; + bool rfu = (etiHeader >> 16) & 0x1; + uint16_t mnsc = etiHeader & 0xFFFF; + + const size_t fic_length_words = (fc.ficf ? (fc.mid == 3 ? 32 : 24) : 0); + const size_t fic_length = 4 * fic_length_words; + + const size_t expected_length = 2 + 4 + + (fc.atstf ? 1 + 4 + 3 : 0) + + fic_length + + (rfudf ? 3 : 0); + + if (value.size() != expected_length) { + etiLog.log(warn, " Assertion error: value.size() != expected_length: %zu %zu\n", + value.size(), expected_length); + assert(false); + } + + m_eti_writer.update_err(stat); + m_eti_writer.update_mnsc(mnsc); + + size_t i = 2 + 4; + + if (fc.atstf) { + uint8_t utco = value[i]; + i++; + + uint32_t seconds = read_32b(value.begin() + i); + i += 4; + + m_eti_writer.update_edi_time(utco, seconds); + + fc.tsta = read_24b(value.begin() + i); + i += 3; + } + else { + // Null timestamp, ETSI ETS 300 799, C.2.2 + fc.tsta = 0xFFFFFF; + } + + + if (fc.ficf) { + vector<uint8_t> fic(fic_length); + copy( value.begin() + i, + value.begin() + i + fic_length, + fic.begin()); + i += fic_length; + + m_eti_writer.update_fic(fic); + } + + if (rfudf) { + uint32_t rfud = read_24b(value.begin() + i); + + i += 3; + } + + m_eti_writer.update_fc_data(fc); + + return true; +} + +bool ETIDecoder::decode_estn(const vector<uint8_t> &value, uint8_t n) +{ + uint32_t sstc = read_24b(value.begin()); + + eti_stc_data stc; + + stc.scid = (sstc >> 18) & 0x3F; + stc.sad = (sstc >> 8) & 0x3FF; + stc.tpl = (sstc >> 2) & 0x3F; + uint8_t rfa = sstc & 0x3; + + copy( value.begin() + 3, + value.end(), + back_inserter(stc.mst)); + + m_eti_writer.add_subchannel(stc); + + return true; +} + +bool ETIDecoder::decode_stardmy(const vector<uint8_t> &value) +{ + return true; +} + +} diff --git a/lib/edi/ETIDecoder.hpp b/lib/edi/ETIDecoder.hpp new file mode 100644 index 0000000..e0c7218 --- /dev/null +++ b/lib/edi/ETIDecoder.hpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2016 + 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 <stdint.h> +#include <deque> +#include <string> +#include <vector> +#include "ETIWriter.hpp" +#include "PFT.hpp" + +namespace EdiDecoder { + +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; +}; + +class ETIDecoder { + public: + ETIDecoder(ETIWriter& eti_writer); + + /* 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); + + private: + 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); + + ETIWriter& m_eti_writer; + + PFT::PFT m_pft; + + std::vector<uint8_t> m_input_data; +}; + +} diff --git a/lib/edi/ETIWriter.cpp b/lib/edi/ETIWriter.cpp new file mode 100644 index 0000000..0eb3feb --- /dev/null +++ b/lib/edi/ETIWriter.cpp @@ -0,0 +1,277 @@ +/* + Copyright (C) 2016 + 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 "ETIWriter.hpp" +#include "crc.h" +#include "Log.h" +#include <stdio.h> +#include <cassert> +#include <stdexcept> +#include <sstream> + +namespace EdiDecoder { + +using namespace std; + +void ETIWriter::update_protocol( + const std::string& proto, + uint16_t major, + uint16_t minor) +{ + m_proto_valid = (proto == "DETI" and major == 0 and minor == 0); + + if (not m_proto_valid) { + throw std::invalid_argument("Wrong EDI protocol"); + } +} + +void ETIWriter::reinit() +{ + m_proto_valid = false; + m_fc_valid = false; + m_fic.clear(); + m_etiFrame.clear(); + m_subchannels.clear(); +} + +void ETIWriter::update_err(uint8_t err) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot update ERR before protocol"); + } + m_err = err; +} + +void ETIWriter::update_fc_data(const eti_fc_data& fc_data) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot update FC before protocol"); + } + + m_fc_valid = false; + m_fc = fc_data; + + if (not m_fc.ficf) { + throw std::invalid_argument("FIC must be present"); + } + + if (m_fc.mid > 4) { + throw std::invalid_argument("Invalid MID"); + } + + if (m_fc.fp > 7) { + throw std::invalid_argument("Invalid FP"); + } + + m_fc_valid = true; +} + +void ETIWriter::update_fic(const std::vector<uint8_t>& fic) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot update FIC before protocol"); + } + + m_fic = fic; +} + +void ETIWriter::update_edi_time( + uint32_t utco, + uint32_t seconds) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot update time before protocol"); + } + + m_utco = utco; + m_seconds = seconds; + + // TODO check validity + m_time_valid = true; + +} + +void ETIWriter::update_mnsc(uint16_t mnsc) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot update MNSC before protocol"); + } + + m_mnsc = mnsc; +} + +void ETIWriter::add_subchannel(const eti_stc_data& stc) +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot add subchannel before protocol"); + } + + m_subchannels.push_back(stc); + + if (m_subchannels.size() > 64) { + throw std::invalid_argument("Too many subchannels"); + } + +} + +void ETIWriter::assemble() +{ + if (not m_proto_valid) { + throw std::logic_error("Cannot assemble ETI before protocol"); + } + + if (not m_fc_valid) { + throw std::logic_error("Cannot assemble ETI without FC"); + } + + if (m_fic.empty()) { + throw std::logic_error("Cannot assemble ETI without FIC data"); + } + + // Accept zero subchannels, because of an edge-case that can happen + // during reconfiguration. See ETS 300 799 Clause 5.3.3 + + // TODO check time validity + + // ETS 300 799 Clause 5.3.2, but we don't support not having + // a FIC + if ( (m_fc.mid == 3 and m_fic.size() != 32 * 4) or + (m_fc.mid != 3 and m_fic.size() != 24 * 4) ) { + stringstream ss; + ss << "Invalid FIC length " << m_fic.size() << + " for MID " << m_fc.mid; + throw std::invalid_argument(ss.str()); + } + + + std::vector<uint8_t> eti; + eti.reserve(6144); + + eti.push_back(m_err); + + // FSYNC + if (m_fc.fct() % 2 == 1) { + eti.push_back(0xf8); + eti.push_back(0xc5); + eti.push_back(0x49); + } + else { + eti.push_back(0x07); + eti.push_back(0x3a); + eti.push_back(0xb6); + } + + // LIDATA + // FC + eti.push_back(m_fc.fct()); + + const uint8_t NST = m_subchannels.size(); + + eti.push_back((m_fc.ficf << 7) | NST); + + // We need to pack: + // FP 3 bits + // MID 2 bits + // FL 11 bits + + // FL: EN 300 799 5.3.6 + uint16_t FL = NST + 1 + m_fic.size(); + for (const auto& subch : m_subchannels) { + FL += subch.mst.size(); + } + + const uint16_t fp_mid_fl = (m_fc.fp << 13) | (m_fc.mid << 11) | FL; + + eti.push_back(fp_mid_fl >> 8); + eti.push_back(fp_mid_fl & 0xFF); + + // STC + for (const auto& subch : m_subchannels) { + eti.push_back( (subch.scid << 2) | (subch.sad & 0x300) ); + eti.push_back( subch.sad & 0xff ); + eti.push_back( (subch.tpl << 2) | ((subch.stl() & 0x300) >> 8) ); + eti.push_back( subch.stl() & 0xff ); + } + + // EOH + // MNSC + eti.push_back(m_mnsc >> 8); + eti.push_back(m_mnsc & 0xFF); + + // CRC + // Calculate CRC from eti[4] to current position + uint16_t eti_crc = 0xFFFF; + eti_crc = crc16(eti_crc, &eti[4], eti.size() - 4); + eti_crc ^= 0xffff; + eti.push_back(eti_crc >> 8); + eti.push_back(eti_crc & 0xFF); + + const size_t mst_start = eti.size(); + // MST + // FIC data + copy(m_fic.begin(), m_fic.end(), back_inserter(eti)); + + // Data stream + for (const auto& subch : m_subchannels) { + copy(subch.mst.begin(), subch.mst.end(), back_inserter(eti)); + } + + // EOF + // CRC + uint16_t mst_crc = 0xFFFF; + mst_crc = crc16(mst_crc, &eti[mst_start], eti.size() - mst_start); + mst_crc ^= 0xffff; + eti.push_back(mst_crc >> 8); + eti.push_back(mst_crc & 0xFF); + + // RFU + eti.push_back(0xff); + eti.push_back(0xff); + + // TIST + eti.push_back(m_fc.tsta >> 24); + eti.push_back((m_fc.tsta >> 16) & 0xFF); + eti.push_back((m_fc.tsta >> 8) & 0xFF); + eti.push_back(m_fc.tsta & 0xFF); + + if (eti.size() > 6144) { + throw std::logic_error("ETI frame cannot be longer than 6144"); + } + + eti.resize(6144, 0x55); + + m_etiFrame = eti; + +} + +std::vector<uint8_t> ETIWriter::getEtiFrame() +{ + if (m_etiFrame.empty()) { + return {}; + } + + vector<uint8_t> eti(move(m_etiFrame)); + reinit(); + + return eti; +} + +} + diff --git a/lib/edi/ETIWriter.hpp b/lib/edi/ETIWriter.hpp new file mode 100644 index 0000000..001f537 --- /dev/null +++ b/lib/edi/ETIWriter.hpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2016 + 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 <stdint.h> +#include <string> +#include <vector> +#include <list> +#include "eti.hpp" + +namespace EdiDecoder { + +// Information for Frame Characterisation available in +// EDI. +// +// Number of streams is given separately, and frame length +// is calculeted in the ETIWriter +struct eti_fc_data { + bool atstf; + uint32_t tsta; + bool ficf; + uint16_t dflc; + uint8_t mid; + uint8_t fp; + + uint8_t fct(void) const { return dflc % 250; } +}; + +// Information for a subchannel available in EDI +struct eti_stc_data { + uint8_t scid; + uint8_t sad; + uint8_t tpl; + std::vector<uint8_t> mst; + + // Return the length of the MST in multiples of 64 bits + uint16_t stl(void) const { return mst.size() / 8; } +}; + +class ETIWriter { + public: + // Tell the ETIWriter what EDI protocol we receive in *ptr. + // This is not part of the ETI data, but is used as check + void update_protocol( + const std::string& proto, + uint16_t major, + uint16_t minor); + + // Update the data for the frame characterisation + void update_fc_data(const eti_fc_data& fc_data); + + void update_fic(const std::vector<uint8_t>& fic); + + void update_err(uint8_t err); + + // In addition to TSTA in ETI, EDI also transports more time + // stamp information. + void update_edi_time( + uint32_t utco, + uint32_t seconds); + + void update_mnsc(uint16_t mnsc); + + void add_subchannel(const eti_stc_data& stc); + + // Tell the ETIWriter that the AFPacket is complete + void assemble(void); + + // Return the assembled ETI frame or an empty frame if not ready + std::vector<uint8_t> getEtiFrame(void); + + private: + void reinit(void); + + bool m_proto_valid = false; + + uint8_t m_err; + + bool m_fc_valid = false; + eti_fc_data m_fc; + + // m_fic is valid if non-empty + std::vector<uint8_t> m_fic; + + std::vector<uint8_t> m_etiFrame; + + std::list<eti_stc_data> m_subchannels; + + bool m_time_valid = false; + uint32_t m_utco; + uint32_t m_seconds; + + uint16_t m_mnsc = 0xffff; +}; + +} diff --git a/lib/edi/PFT.cpp b/lib/edi/PFT.cpp new file mode 100644 index 0000000..fa5840c --- /dev/null +++ b/lib/edi/PFT.cpp @@ -0,0 +1,553 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2017 AVT GmbH - Fabien Vercasson + * Copyright (C) 2016 Matthias P. Braendli + * matthias.braendli@mpb.li + * + * http://opendigitalradio.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * ------------------------------------------------------------------- + */ + +#include <stdio.h> +#include <cassert> +#include <cstring> +#include <sstream> +#include <stdexcept> +#include <algorithm> +#include "crc.h" +#include "PFT.hpp" +#include "Log.h" +#include "buffer_unpack.hpp" +extern "C" { +#include "fec/fec.h" +} + +namespace EdiDecoder { + +namespace PFT { + +using namespace std; + +const findex_t NUM_AFBUILDERS_TO_KEEP = 10; + +static bool checkCRC(const uint8_t *buf, size_t size) +{ + const uint16_t crc_from_packet = read_16b(buf + size - 2); + uint16_t crc_calc = 0xffff; + crc_calc = crc16(crc_calc, buf, size - 2); + crc_calc ^= 0xffff; + + return crc_from_packet == crc_calc; +} + +class FECDecoder { + public: + FECDecoder() { + m_rs_handler = init_rs_char( + symsize, gfPoly, firstRoot, primElem, nroots, pad); + } + FECDecoder(const FECDecoder& other) = delete; + FECDecoder& operator=(const FECDecoder& other) = delete; + ~FECDecoder() { + free_rs_char(m_rs_handler); + } + + // return -1 in case of failure, non-negative value if errors + // were corrected. + // Known positions of erasures should be given in eras_pos to + // improve decoding probability. After calling this function + // eras_pos will contain the positions of the corrected errors. + int decode(vector<uint8_t> &data, vector<int> &eras_pos) { + assert(data.size() == N); + const size_t no_eras = eras_pos.size(); + + eras_pos.resize(nroots); + int num_err = decode_rs_char(m_rs_handler, data.data(), + eras_pos.data(), no_eras); + if (num_err > 0) { + eras_pos.resize(num_err); + } + return num_err; + } + + // return -1 in case of failure, non-negative value if errors + // were corrected. No known erasures. + int decode(vector<uint8_t> &data) { + assert(data.size() == N); + int num_err = decode_rs_char(m_rs_handler, data.data(), nullptr, 0); + return num_err; + } + + private: + void* m_rs_handler; + + const int firstRoot = 1; // Discovered by analysing EDI dump + const int gfPoly = 0x11d; + + // 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 + const size_t N = 255; + const size_t K = 207; + const int primElem = 1; + const int symsize = 8; + const size_t nroots = N - K; // For EDI PFT, this must be 48 + const size_t pad = ((1 << symsize) - 1) - N; // is 255-N + +}; + +size_t Fragment::loadData(const std::vector<uint8_t> &buf) +{ + const size_t header_len = 14; + if (buf.size() < header_len) { + return 0; + } + + size_t index = 0; + + // Parse PFT Fragment Header (ETSI TS 102 821 V1.4.1 ch7.1) + if (not (buf[0] == 'P' and buf[1] == 'F') ) { + throw invalid_argument("Invalid PFT SYNC bytes"); + } + index += 2; // Psync + + _Pseq = read_16b(buf.begin()+index); index += 2; + _Findex = read_24b(buf.begin()+index); index += 3; + _Fcount = read_24b(buf.begin()+index); index += 3; + _FEC = unpack1bit(buf[index], 0); + _Addr = unpack1bit(buf[index], 1); + _Plen = read_16b(buf.begin()+index) & 0x3FFF; index += 2; + + const size_t required_len = header_len + + (_FEC ? 1 : 0) + + (_Addr ? 2 : 0) + + 2; // CRC + if (buf.size() < required_len) { + return 0; + } + + // Optional RS Header + _RSk = 0; + _RSz = 0; + if (_FEC) { + _RSk = buf[index]; index += 1; + _RSz = buf[index]; index += 1; + } + + // Optional transport header + _Source = 0; + _Dest = 0; + if (_Addr) { + _Source = read_16b(buf.begin()+index); index += 2; + _Dest = read_16b(buf.begin()+index); index += 2; + } + + index += 2; + const bool crc_valid = checkCRC(buf.data(), index); + const bool buf_has_enough_data = (buf.size() >= index + _Plen); + + if (not buf_has_enough_data) { + return 0; + } + + _valid = ((not _FEC) or crc_valid) and buf_has_enough_data; + +#if 0 + if (!_valid) { + stringstream ss; + ss << "Invalid PF fragment: "; + if (_FEC) { + ss << " RSk=" << (uint32_t)_RSk << " RSz=" << (uint32_t)_RSz; + } + + if (_Addr) { + ss << " Source=" << _Source << " Dest=" << _Dest; + } + etiLog.log(debug, "%s\n", ss.str().c_str()); + } +#endif + + _payload.clear(); + if (_valid) { + copy( buf.begin()+index, + buf.begin()+index+_Plen, + back_inserter(_payload)); + index += _Plen; + } + + return index; +} + + +AFBuilder::AFBuilder(pseq_t Pseq, findex_t Fcount, int lifetime) +{ + _Pseq = Pseq; + _Fcount = Fcount; + lifeTime = lifetime; +} + +void AFBuilder::pushPFTFrag(const Fragment &frag) +{ + if (_Pseq != frag.Pseq() or _Fcount != frag.Fcount()) { + throw invalid_argument("Invalid PFT fragment Pseq or Fcount"); + } + const auto Findex = frag.Findex(); + const bool fragment_already_received = _fragments.count(Findex); + + if (not fragment_already_received) + { + _fragments[Findex] = frag; + } +} + +bool Fragment::checkConsistency(const Fragment& other) const +{ + /* Consistency check, TS 102 821 Clause 7.3.2. + * + * Every PFT Fragment produced from a single AF or RS Packet shall have + * the same values in all of the PFT Header fields except for the Findex, + * Plen and HCRC fields. + */ + + return other._Fcount == _Fcount and + other._FEC == _FEC and + other._RSk == _RSk and + other._RSz == _RSz and + other._Addr == _Addr and + other._Source == _Source and + other._Dest == _Dest and + + /* The Plen field of all fragments shall be the s for the initial f-1 + * fragments and s - (L%f) for the final fragment. + * Note that when Reed Solomon has been used, all fragments will be of + * length s. + */ + (_FEC ? other._Plen == _Plen : true); +} + + +AFBuilder::decode_attempt_result_t AFBuilder::canAttemptToDecode() const +{ + if (_fragments.empty()) { + return AFBuilder::decode_attempt_result_t::no; + } + + if (_fragments.size() == _Fcount) { + return AFBuilder::decode_attempt_result_t::yes; + } + + /* Check that all fragments are consistent */ + const Fragment& first = _fragments.begin()->second; + if (not std::all_of(_fragments.begin(), _fragments.end(), + [&](const pair<int, Fragment>& pair) { + const Fragment& frag = pair.second; + return first.checkConsistency(frag) and _Pseq == frag.Pseq(); + }) ) { + throw invalid_argument("Inconsistent PFT fragments"); + } + + // Calculate the minimum number of fragments necessary to apply FEC. + // This can't be done with the last fragment that may have a + // smaller size + // ETSI TS 102 821 V1.4.1 ch 7.4.4 + auto frag_it = _fragments.begin(); + if (frag_it->second.Fcount() == _Fcount - 1) { + frag_it++; + + if (frag_it == _fragments.end()) { + return AFBuilder::decode_attempt_result_t::no; + } + } + + const Fragment& frag = frag_it->second; + + if ( frag.FEC() ) + { + const uint16_t _Plen = frag.Plen(); + + /* max number of RS chunks that may have been sent */ + const uint32_t _cmax = (_Fcount*_Plen) / (frag.RSk()+48); + assert(_cmax > 0); + + /* Receiving _rxmin fragments does not guarantee that decoding + * will succeed! */ + const uint32_t _rxmin = _Fcount - (_cmax*48)/_Plen; + + if (_fragments.size() >= _rxmin) { + return AFBuilder::decode_attempt_result_t::maybe; + } + } + + return AFBuilder::decode_attempt_result_t::no; +} + +std::vector<uint8_t> AFBuilder::extractAF() const +{ + if (not _af_packet.empty()) { + return _af_packet; + } + + bool ok = false; + + if (canAttemptToDecode() != AFBuilder::decode_attempt_result_t::no) { + + auto frag_it = _fragments.begin(); + if (frag_it->second.Fcount() == _Fcount - 1) { + frag_it++; + + if (frag_it == _fragments.end()) { + throw std::runtime_error("Invalid attempt at extracting AF"); + } + } + + const Fragment& ref_frag = frag_it->second; + const auto RSk = ref_frag.RSk(); + const auto RSz = ref_frag.RSz(); + const auto Plen = ref_frag.Plen(); + + if ( ref_frag.FEC() ) + { + const uint32_t cmax = (_Fcount*Plen) / (RSk+48); + + // Keep track of erasures (missing fragments) for + // every chunk + map<int, vector<int> > erasures; + + + // Assemble fragments into a RS block, immediately + // deinterleaving it. + vector<uint8_t> rs_block(Plen * _Fcount); + for (size_t j = 0; j < _Fcount; j++) { + const bool fragment_present = _fragments.count(j); + if (fragment_present) { + const auto& fragment = _fragments.at(j).payload(); + + if (j != _Fcount - 1 and fragment.size() != Plen) { + throw runtime_error("Incorrect fragment length " + + to_string(fragment.size()) + " " + + to_string(Plen)); + } + + if (j == _Fcount - 1 and fragment.size() > Plen) { + throw runtime_error("Incorrect last fragment length " + + to_string(fragment.size()) + " " + + to_string(Plen)); + } + + size_t k = 0; + for (; k < fragment.size(); k++) { + rs_block[k * _Fcount + j] = fragment[k]; + } + + for (; k < Plen; k++) { + rs_block[k * _Fcount + j] = 0x00; + } + } + else { + // fill with zeros if fragment is missing + for (size_t k = 0; k < Plen; k++) { + rs_block[k * _Fcount + j] = 0x00; + + const size_t chunk_ix = (k * _Fcount + j) / (RSk + 48); + const size_t chunk_offset = (k * _Fcount + j) % (RSk + 48); + erasures[chunk_ix].push_back(chunk_offset); + } + } + } + + // The RS block is a concatenation of chunks of RSk bytes + 48 parity + // followed by RSz padding + + FECDecoder fec; + for (size_t i = 0; i < cmax; i++) { + // We need to pad the chunk ourself + vector<uint8_t> chunk(255); + const auto& block_begin = rs_block.begin() + (RSk + 48) * i; + copy(block_begin, block_begin + RSk, chunk.begin()); + // bytes between RSk and 207 are 0x00 already + copy(block_begin + RSk, block_begin + RSk + 48, + chunk.begin() + 207); + + int errors_corrected = -1; + if (erasures.count(i)) { + errors_corrected = fec.decode(chunk, erasures[i]); + } + else { + errors_corrected = fec.decode(chunk); + } + + if (errors_corrected == -1) { + _af_packet.clear(); + return {}; + } + +#if 0 + if (errors_corrected > 0) { + etiLog.log(debug, "Corrected %d errors at ", errors_corrected); + for (const auto &index : erasures[i]) { + etiLog.log(debug, " %d", index); + } + etiLog.log(debug, "\n"); + } +#endif + + _af_packet.insert(_af_packet.end(), chunk.begin(), chunk.begin() + RSk); + } + + _af_packet.resize(_af_packet.size() - RSz); + } + else { + // No FEC: just assemble fragments + + for (size_t j = 0; j < _Fcount; ++j) { + const bool fragment_present = _fragments.count(j); + if (fragment_present) + { + const auto& fragment = _fragments.at(j); + + _af_packet.insert(_af_packet.end(), + fragment.payload().begin(), + fragment.payload().end()); + } + else { + throw logic_error("Missing fragment"); + } + } + } + + // EDI specific, must have a CRC. + if( _af_packet.size() >= 12 ) { + ok = checkCRC(_af_packet.data(), _af_packet.size()); + + if (not ok) { + etiLog.log(debug, "Too many errors to reconstruct AF from %zu/%u" + " PFT fragments\n", _fragments.size(), _Fcount); + } + } + } + + if (not ok) { + _af_packet.clear(); + } + + return _af_packet; +} + +void PFT::pushPFTFrag(const Fragment &fragment) +{ + // Start decoding the first pseq we receive. In normal + // operation without interruptions, the map should + // never become empty + if (m_afbuilders.empty()) { + m_next_pseq = fragment.Pseq(); + etiLog.log(debug,"Initialise next_pseq to %u\n", m_next_pseq); + } + + if (m_afbuilders.count(fragment.Pseq()) == 0) { + // The AFBuilder wants to know the lifetime in number of fragments, + // we know the delay in number of AF packets. Every AF packet + // is cut into Fcount fragments. + const int lifetime = fragment.Fcount() * m_max_delay; + + // Build the afbuilder in the map in-place + m_afbuilders.emplace(std::piecewise_construct, + /* key */ + std::forward_as_tuple(fragment.Pseq()), + /* builder */ + std::forward_as_tuple(fragment.Pseq(), fragment.Fcount(), lifetime)); + } + + auto& p = m_afbuilders.at(fragment.Pseq()); + p.pushPFTFrag(fragment); + +#if 0 + etiLog.log(debug, "After new frag with pseq %u, afbuilders: ", fragment.Pseq()); + for (const auto &k : m_afbuilders) { + etiLog.log(debug, "%u ", k.first); + } + etiLog.log(debug, "\n"); +#endif +} + + +std::vector<uint8_t> PFT::getNextAFPacket() +{ + if (m_afbuilders.count(m_next_pseq) == 0) { + assert(m_afbuilders.empty()); + return {}; + } + + auto &builder = m_afbuilders.at(m_next_pseq); + //const auto lt = builder.lifeTime; + //const auto nf = builder.numberOfFragments(); + + using dar_t = AFBuilder::decode_attempt_result_t; + + if (builder.canAttemptToDecode() == dar_t::yes) { + //etiLog.log(debug, "pseq %d (%d %d/%d) yes\n", m_next_pseq, lt, nf.first, nf.second); + auto afpacket = builder.extractAF(); + assert(not afpacket.empty()); + incrementNextPseq(); + return afpacket; + } + else if (builder.canAttemptToDecode() == dar_t::maybe) { + //etiLog.log(debug, "pseq %d (%d %d/%d) maybe\n", m_next_pseq, lt, nf.first, nf.second); + builder.lifeTime--; + if (builder.lifeTime == 0) { + // Attempt Reed-Solomon decoding + auto afpacket = builder.extractAF(); + + if (afpacket.empty()) { + etiLog.log(debug,"pseq %d timed out after RS\n", m_next_pseq); + } + incrementNextPseq(); + return afpacket; + } + } + else { + //etiLog.log(debug, "pseq %d (%d %d/%d) no\n", m_next_pseq, lt, nf.first, nf.second); + builder.lifeTime--; + if (builder.lifeTime == 0) { + etiLog.log(debug, "pseq %d timed out\n", m_next_pseq); + incrementNextPseq(); + } + } + + return {}; +} + +void PFT::setMaxDelay(int num_af_packets) +{ + m_max_delay = num_af_packets; +} + +bool PFT::isPacketBuildable(pseq_t pseq) const +{ + return m_afbuilders.count(pseq) > 0 and + not m_afbuilders.at(pseq).extractAF().empty(); +} + +void PFT::incrementNextPseq() +{ + if (m_afbuilders.count(m_next_pseq - NUM_AFBUILDERS_TO_KEEP) > 0) { + m_afbuilders.erase(m_next_pseq - NUM_AFBUILDERS_TO_KEEP); + } + + m_next_pseq++; +} + +} + +} diff --git a/lib/edi/PFT.hpp b/lib/edi/PFT.hpp new file mode 100644 index 0000000..9ab14ae --- /dev/null +++ b/lib/edi/PFT.hpp @@ -0,0 +1,154 @@ +/* ------------------------------------------------------------------ + * Copyright (C) 2017 AVT GmbH - Fabien Vercasson + * Copyright (C) 2016 Matthias P. Braendli + * matthias.braendli@mpb.li + * + * http://opendigitalradio.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * ------------------------------------------------------------------- + */ + +#pragma once +#include <stdio.h> +#include <cassert> +#include <vector> +#include <map> +#include <stdint.h> + +namespace EdiDecoder { +namespace PFT { + +using pseq_t = uint16_t; +using findex_t = uint32_t; // findex is a 24-bit value + +class Fragment +{ + public: + // Load the data for one fragment from buf into + // the Fragment. + // \returns the number of bytes of useful data found in buf + // A non-zero return value doesn't imply a valid fragment + // the isValid() method must be used to verify this. + size_t loadData(const std::vector<uint8_t> &buf); + + bool isValid() const { return _valid; } + pseq_t Pseq() const { return _Pseq; } + findex_t Findex() const { return _Findex; } + findex_t Fcount() const { return _Fcount; } + bool FEC() const { return _FEC; } + uint16_t Plen() const { return _Plen; } + uint8_t RSk() const { return _RSk; } + uint8_t RSz() const { return _RSz; } + const std::vector<uint8_t>& payload() const + { return _payload; } + + bool checkConsistency(const Fragment& other) const; + + private: + std::vector<uint8_t> _payload; + + pseq_t _Pseq; + findex_t _Findex; + findex_t _Fcount; + bool _FEC; + bool _Addr; + uint16_t _Plen; + uint8_t _RSk; + uint8_t _RSz; + uint16_t _Source; + uint16_t _Dest; + bool _valid; +}; + +/* The AFBuilder collects Fragments and builds an Application Frame + * out of them. It does error correction if necessary + */ +class AFBuilder +{ + public: + enum class decode_attempt_result_t { + yes, // The AF packet can be build because all fragments are present + maybe, // RS decoding may correctly decode the AF packet + no, // Not enough fragments present to permit RS + }; + + AFBuilder(pseq_t Pseq, findex_t Fcount, int lifetime); + + void pushPFTFrag(const Fragment &frag); + + /* Assess if it may be possible to decode this AF packet */ + decode_attempt_result_t canAttemptToDecode() const; + + /* Try to build the AF with received fragments. + * Apply error correction if necessary (missing packets/CRC errors) + * \return an empty vector if building the AF is not possible + */ + std::vector<uint8_t> extractAF(void) const; + + std::pair<findex_t, findex_t> + numberOfFragments(void) const { + return {_fragments.size(), _Fcount}; + } + + /* The user of this instance can keep track of the lifetime of this + * builder + */ + int lifeTime; + + private: + + // A map from fragment index to fragment + std::map<findex_t, Fragment> _fragments; + + // cached version of decoded AF packet + mutable std::vector<uint8_t> _af_packet; + + pseq_t _Pseq; + findex_t _Fcount; +}; + +class PFT +{ + public: + void pushPFTFrag(const Fragment &fragment); + + /* Try to build the AF packet for the next pseq. This might + * skip one pseq according to the maximum delay setting. + * + * \return an empty vector if building the AF is not possible + */ + std::vector<uint8_t> getNextAFPacket(void); + + /* Set the maximum delay in number of AF Packets before we + * abandon decoding a given pseq. + */ + void setMaxDelay(int num_af_packets); + + private: + bool isPacketBuildable(pseq_t pseq) const; + + void incrementNextPseq(void); + + pseq_t m_next_pseq; + int m_max_delay = 10; + + // Keep one AFBuilder for each Pseq + std::map<pseq_t, AFBuilder> m_afbuilders; + +}; + +} + +} diff --git a/lib/edi/README.md b/lib/edi/README.md new file mode 100644 index 0000000..b6ab67a --- /dev/null +++ b/lib/edi/README.md @@ -0,0 +1 @@ +These files are copied from the odr-edilib project. diff --git a/lib/edi/buffer_unpack.hpp b/lib/edi/buffer_unpack.hpp new file mode 100644 index 0000000..05a1534 --- /dev/null +++ b/lib/edi/buffer_unpack.hpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2016 + 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 <stdint.h> + +namespace EdiDecoder { + +template<class T> +uint16_t read_16b(T buf) +{ + uint16_t value = 0; + value = (uint16_t)(buf[0]) << 8; + value |= (uint16_t)(buf[1]); + return value; +} + +template<class T> +uint32_t read_24b(T buf) +{ + uint32_t value = 0; + value = (uint32_t)(buf[0]) << 16; + value |= (uint32_t)(buf[1]) << 8; + value |= (uint32_t)(buf[2]); + return value; +} + +template<class T> +uint32_t read_32b(T buf) +{ + uint32_t value = 0; + value = (uint32_t)(buf[0]) << 24; + value |= (uint32_t)(buf[1]) << 16; + value |= (uint32_t)(buf[2]) << 8; + value |= (uint32_t)(buf[3]); + return value; +} + +inline uint32_t unpack1bit(uint8_t byte, int bitpos) +{ + return (byte & 1 << (7-bitpos)) > (7-bitpos); +} + +} diff --git a/lib/edi/eti.cpp b/lib/edi/eti.cpp new file mode 100644 index 0000000..3fc99ef --- /dev/null +++ b/lib/edi/eti.cpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2016 + 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 "eti.hpp" + +namespace EdiDecoder { + +//definitions des structures des champs du ETI(NI, G703) + +uint16_t eti_FC::getFrameLength() +{ + return (uint16_t)((FL_high << 8) | FL_low); +} + +void eti_FC::setFrameLength(uint16_t length) +{ + FL_high = (length >> 8) & 0x07; + FL_low = length & 0xff; +} + +void eti_STC::setSTL(uint16_t length) +{ + STL_high = length >> 8; + STL_low = length & 0xff; +} + +uint16_t eti_STC::getSTL() +{ + return (uint16_t)((STL_high << 8) + STL_low); +} + +void eti_STC::setStartAddress(uint16_t address) +{ + startAddress_high = address >> 8; + startAddress_low = address & 0xff; +} + +uint16_t eti_STC::getStartAddress() +{ + return (uint16_t)((startAddress_high << 8) + startAddress_low); +} + +} diff --git a/lib/edi/eti.hpp b/lib/edi/eti.hpp new file mode 100644 index 0000000..451ca48 --- /dev/null +++ b/lib/edi/eti.hpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2016 + 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 <stdint.h> +#define PACKED __attribute__ ((packed)) + +#include <time.h> + +namespace EdiDecoder { + +struct eti_SYNC { + uint32_t ERR:8; + uint32_t FSYNC:24; +} PACKED; + +struct eti_FC { + uint32_t FCT:8; + uint32_t NST:7; + uint32_t FICF:1; + uint32_t FL_high:3; + uint32_t MID:2; + uint32_t FP:3; + uint32_t FL_low:8; + uint16_t getFrameLength(); + void setFrameLength(uint16_t length); +} PACKED; + +struct eti_STC { + uint32_t startAddress_high:2; + uint32_t SCID:6; + uint32_t startAddress_low:8; + uint32_t STL_high:2; + uint32_t TPL:6; + uint32_t STL_low:8; + void setSTL(uint16_t length); + uint16_t getSTL(); + void setStartAddress(uint16_t address); + uint16_t getStartAddress(); +} PACKED; + +struct eti_EOH { + uint16_t MNSC; + uint16_t CRC; +} PACKED; + +struct eti_EOF { + uint16_t CRC; + uint16_t RFU; +} PACKED; + +struct eti_TIST { + uint32_t TIST; +} PACKED; + +struct eti_MNSC_TIME_0 { + uint32_t type:4; + uint32_t identifier:4; + uint32_t rfa:8; +} PACKED; + +struct eti_MNSC_TIME_1 { + uint32_t second_unit:4; + uint32_t second_tens:3; + uint32_t accuracy:1; + + uint32_t minute_unit:4; + uint32_t minute_tens:3; + uint32_t sync_to_frame:1; +} PACKED; + +struct eti_MNSC_TIME_2 { + uint32_t hour_unit:4; + uint32_t hour_tens:4; + + uint32_t day_unit:4; + uint32_t day_tens:4; +} PACKED; + +struct eti_MNSC_TIME_3 { + uint32_t month_unit:4; + uint32_t month_tens:4; + + uint32_t year_unit:4; + uint32_t year_tens:4; +} PACKED; + +struct eti_extension_TIME { + uint32_t TIME_SECONDS; +} PACKED; + +} |