diff --git a/meson.build b/meson.build index 344c0abc..afbe40b6 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ openrtx_src = ['openrtx/src/core/state.c', 'openrtx/src/protocols/M17/M17Callsign.cpp', 'openrtx/src/protocols/M17/M17Modulator.cpp', 'openrtx/src/protocols/M17/M17Demodulator.cpp', + 'openrtx/src/protocols/M17/M17FrameDecoder.cpp', 'openrtx/src/protocols/M17/M17Transmitter.cpp', 'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp'] diff --git a/openrtx/include/protocols/M17/M17Datatypes.h b/openrtx/include/protocols/M17/M17Datatypes.h index 6faaeb05..3d4d93b4 100644 --- a/openrtx/include/protocols/M17/M17Datatypes.h +++ b/openrtx/include/protocols/M17/M17Datatypes.h @@ -34,9 +34,9 @@ using meta_t = std::array< uint8_t, 14 >; // Data type for LSF metadata fie using payload_t = std::array< uint8_t, 16 >; // Data type for frame payload field using lich_t = std::array< uint8_t, 12 >; // Data type for Golay(24,12) encoded LICH data - -static constexpr std::array LSF_SYNC_WORD = {0x55, 0xF7}; // LSF sync word -static constexpr std::array DATA_SYNC_WORD = {0xFF, 0x5D}; // Stream data sync word +static constexpr std::array LSF_SYNC_WORD = {0x55, 0xF7}; // LSF sync word +static constexpr std::array DATA_SYNC_WORD = {0xFF, 0x5D}; // Stream data sync word +static constexpr std::array PACKET_SYNC_WORD = {0x75, 0xFF}; // Packet data sync word /** diff --git a/openrtx/include/protocols/M17/M17FrameDecoder.h b/openrtx/include/protocols/M17/M17FrameDecoder.h new file mode 100755 index 00000000..cdf586a3 --- /dev/null +++ b/openrtx/include/protocols/M17/M17FrameDecoder.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 * + ***************************************************************************/ + +#ifndef M17FRAMEDECODER_H +#define M17FRAMEDECODER_H + +#ifndef __cplusplus +#error This header is C++ only! +#endif + +#include +#include +#include "M17LinkSetupFrame.h" +#include "M17Viterbi.h" +#include "M17StreamFrame.h" + +enum class M17FrameType : uint8_t +{ + PREAMBLE = 0, ///< Frame contains a preamble. + LINK_SETUP = 1, ///< Frame is a Link Setup Frame. + STREAM = 2, ///< Frame is a stream data frame. + PACKET = 3, ///< Frame is a packet data frame. + UNKNOWN = 4 ///< Frame is unknown. +}; + +/** + * M17 frame decoder. + */ +class M17FrameDecoder +{ +public: + + /** + * Constructor. + */ + M17FrameDecoder(); + + /** + * Destructor. + */ + ~M17FrameDecoder(); + + /** + * Clear the internal data structures. + */ + void reset(); + + /** + * Decode an M17 frame, identifying its type. Frame data must contain the + * sync word in the first two bytes. + * + * @param frame: byte array containg frame data. + * @return the type of frame recognized. + */ + M17FrameType decodeFrame(const std::array< uint8_t, 48 >& frame); + + /** + * Get the latest Link Setup Frame decoded. Check of the validity of the + * data contained in the LSF is left to application code. + * + * @return a reference to the latest Link Setup Frame decoded. + */ + const M17LinkSetupFrame& getLsf() + { + return lsf; + } + + /** + * Get the latest stream data frame decoded. + * + * @return a reference to the latest stream data frame decoded. + */ + const M17StreamFrame& getStreamFrame() + { + return streamFrame; + } + +private: + + /** + * Decode Link Setup Frame data and update the internal LSF field with + * the new frame data. + * + * @param data: byte array containg frame data, without sync word. + */ + void decodeLSF(const std::array< uint8_t, 46 >& data); + + /** + * Decode stream data and update the internal LSF field with the new + * frame data. + * + * @param data: byte array containg frame data, without sync word. + */ + void decodeStream(const std::array< uint8_t, 46 >& data); + + /** + * Decode a LICH block. + * + * @param segment: byte array where to store the decoded Link Setup Frame + * segment. The last byte contains the segment number. + * @param lich: LICH block to be decoded. + * @return true when the LICH block is successfully decoded. + */ + bool decodeLich(std::array< uint8_t, 6 >& segment, const lich_t& lich); + + + uint8_t lsfSegmentMap; ///< Bitmap for LSF reassembly from LICH + M17LinkSetupFrame lsf; ///< Latest LSF received. + M17LinkSetupFrame lsfFromLich; ///< LSF assembled from LICH segments. + M17StreamFrame streamFrame; ///< Latest stream dat frame received. + M17Viterbi viterbi; ///< Viterbi decoder. +}; + +#endif /* M17FRAMEDECODER_H */ diff --git a/openrtx/include/protocols/M17/M17LinkSetupFrame.h b/openrtx/include/protocols/M17/M17LinkSetupFrame.h index d8ac8069..042f7d3c 100644 --- a/openrtx/include/protocols/M17/M17LinkSetupFrame.h +++ b/openrtx/include/protocols/M17/M17LinkSetupFrame.h @@ -29,6 +29,8 @@ #include #include "M17Datatypes.h" +class M17FrameDecoder; + /** * This class describes and handles an M17 Link Setup Frame. * By default the frame contains a broadcast destination address, unless a @@ -157,6 +159,9 @@ private: uint16_t crc; ///< CRC } data; ///< Frame data. + + // Frame decoder class needs to access raw frame data + friend class M17FrameDecoder; }; #endif /* M17_LINKSETUPFRAME_H */ diff --git a/openrtx/include/protocols/M17/M17StreamFrame.h b/openrtx/include/protocols/M17/M17StreamFrame.h index 27364b97..fca2cf7c 100644 --- a/openrtx/include/protocols/M17/M17StreamFrame.h +++ b/openrtx/include/protocols/M17/M17StreamFrame.h @@ -29,6 +29,8 @@ #include #include "M17Datatypes.h" +class M17FrameDecoder; + /** * This class describes and handles a generic M17 data frame. */ @@ -131,6 +133,9 @@ private: ///< Frame data. static constexpr uint16_t EOS_BIT = 0x0080; ///< End Of Stream bit. static constexpr uint16_t FN_MASK = 0x7FFF; ///< Bitmask for frame number. + + // Frame decoder class needs to access raw frame data + friend class M17FrameDecoder; }; #endif /* M17_FRAME_H */ diff --git a/openrtx/src/protocols/M17/M17FrameDecoder.cpp b/openrtx/src/protocols/M17/M17FrameDecoder.cpp new file mode 100644 index 00000000..af0085b8 --- /dev/null +++ b/openrtx/src/protocols/M17/M17FrameDecoder.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** + * Copyright (C) 2022 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 +#include +#include +#include +#include +#include + + +M17FrameDecoder::M17FrameDecoder() { } + +M17FrameDecoder::~M17FrameDecoder() { } + +void M17FrameDecoder::reset() +{ + lsfSegmentMap = 0; + lsf.clear(); + lsfFromLich.clear(); + streamFrame.clear(); +} + +M17FrameType M17FrameDecoder::decodeFrame(const std::array< uint8_t, 48 >& frame) +{ + std::array< uint8_t, 2 > syncWord; + std::array< uint8_t, 46 > data; + + std::copy_n(frame.begin(), 2, syncWord.begin()); + std::copy(frame.begin() + 2, frame.end(), data.begin()); + + // Re-correlating data is the same operation as decorrelating + decorrelate(data); + deinterleave(data); + + // Preamble + if((syncWord[0] == 0x77) && (syncWord[1] == 0x77)) + { + return M17FrameType::PREAMBLE; + } + + // Link Setup Frame + if(syncWord == LSF_SYNC_WORD) + { + decodeLSF(data); + return M17FrameType::LINK_SETUP; + } + + // Stream data frame + if(syncWord == DATA_SYNC_WORD) + { + decodeStream(data); + return M17FrameType::STREAM; + } + + // If we get here, we received an unknown frame + return M17FrameType::UNKNOWN; +} + +void M17FrameDecoder::decodeLSF(const std::array< uint8_t, 46 >& data) +{ + std::array< uint8_t, sizeof(M17LinkSetupFrame) > tmp; + + viterbi.decodePunctured(data, tmp, LSF_puncture); + memcpy(&lsf.data, tmp.data(), tmp.size()); +} + +void M17FrameDecoder::decodeStream(const std::array< uint8_t, 46 >& data) +{ + // Extract and unpack the LICH segment contained at beginning of frame + lich_t lich; + std::array < uint8_t, 6 > lsfSegment; + + std::copy_n(data.begin(), lich.size(), lich.begin()); + bool decodeOk = decodeLich(lsfSegment, lich); + + if(decodeOk) + { + // Append LICH segment + uint8_t segmentNum = lsfSegment[5]; + uint8_t *ptr = reinterpret_cast < uint8_t * >(&lsfFromLich.data); + memcpy(ptr + segmentNum, lsfSegment.data(), 5); + + // Mark this segment as present + lsfSegmentMap |= 1 << segmentNum; + + // Check if we have received all the five LICH segments + if(lsfSegmentMap == 0x1F) + { + if(lsfFromLich.valid()) lsf = lsfFromLich; + lsfSegmentMap = 0; + lsfFromLich.clear(); + } + } + + // Extract and decode stream data + std::array< uint8_t, 34 > punctured; + std::array< uint8_t, sizeof(M17StreamFrame) > tmp; + + auto begin = data.begin(); + begin += lich.size(); + std::copy(begin, data.end(), punctured.begin()); + + viterbi.decodePunctured(punctured, tmp, Audio_puncture); + memcpy(&streamFrame.data, tmp.data(), tmp.size()); +} + +bool M17FrameDecoder::decodeLich(std::array < uint8_t, 6 >& segment, + const lich_t& lich) +{ + /* + * Extract and unpack the LICH segment contained in the frame header. + * The LICH segment is composed of four blocks of Golay(24,12) encoded data + * and carries five bytes of the original Link Setup Frame. The sixth byte + * is the segment number, allowing to determine the correct position of the + * segment when reassembling the LSF. + * + * NOTE: LICH data is stored in big-endian format, swap and shift after + * memcpy convert it to little-endian. + */ + + segment.fill(0x00); + + size_t index = 0; + uint32_t block = 0; + + for(size_t i = 0; i < 4; i++) + { + memcpy(&block, lich.data() + 3*i, 3); + block = __builtin_bswap32(block) >> 8; + uint16_t decoded = golay24_decode(block); + + // Unrecoverable error, abort decoding + if(decoded == 0xFFFF) + { + segment.fill(0x00); + return false; + } + + if(i & 1) + { + segment[index++] |= (decoded >> 8); // upper 4 bits + segment[index++] = (decoded & 0xFF); // lower 8 bits + } + else + { + segment[index++] |= (decoded >> 4); // upper 8 bits + segment[index] = (decoded & 0x0F) << 4; // lower 4 bits + } + } + + // Last byte of the segment contains the segment number, shift left + // by five when packing the LICH. + segment[5] >>= 5; + + return true; +}