diff --git a/meson.build b/meson.build index 89de4f94..780daf06 100644 --- a/meson.build +++ b/meson.build @@ -33,11 +33,14 @@ openrtx_src = ['openrtx/src/state.c', 'openrtx/src/rtx/OpMode_FM.cpp', 'openrtx/src/gps.c', 'openrtx/src/dsp.cpp', - 'openrtx/src/memory_profiling.cpp'] + 'openrtx/src/memory_profiling.cpp', + 'openrtx/src/protocols/M17/M17Callsign.cpp', + 'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp'] openrtx_inc = ['openrtx/include', 'openrtx/include/calibration', 'openrtx/include/rtx', + 'openrtx/include/protocols', 'platform/drivers/ADC', 'platform/drivers/NVM', 'platform/drivers/GPS', diff --git a/openrtx/include/protocols/M17/M17Callsign.h b/openrtx/include/protocols/M17/M17Callsign.h new file mode 100644 index 00000000..411fcaf1 --- /dev/null +++ b/openrtx/include/protocols/M17/M17Callsign.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2021 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 M17CALLSIGN_H +#define M17CALLSIGN_H + +#ifndef __cplusplus +#error This header is C++ only! +#endif + +#include +#include "M17Datatypes.h" + +/** + * Encode a callsign in base-40 format, starting with the right-most character. + * The final value is written out in "big-endian" form, with the most-significant + * value first, leading to 0-padding of callsigns shorter than nine characters. + * + * \param callsign the callsign to encode. + * \param encodedCall call_t data structure where to put the encoded data. + * \param strict a flag (disabled by default) which indicates whether invalid + * characters are allowed and assigned a value of 0 or not allowed, making the + * function return an error. + * @return true if the callsign was successfully encoded, false on error. + */ +bool encode_callsign(const std::string& callsign, call_t& encodedCall, + bool strict = false); + +/** + * Decode a base-40 encoded callsign to its text representation. This decodes + * a 6-byte big-endian value into a string of up to 9 characters. + * + * \param encodedCall base-40 encoded callsign. + * \return a string containing the decoded text. + */ +std::string decode_callsign(const call_t& encodedCall); + +#endif /* M17CALLSIGN_H */ diff --git a/openrtx/include/protocols/M17/M17Datatypes.h b/openrtx/include/protocols/M17/M17Datatypes.h new file mode 100644 index 00000000..a925e8b3 --- /dev/null +++ b/openrtx/include/protocols/M17/M17Datatypes.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2021 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 M17DATATYPES_H +#define M17DATATYPES_H + +#include +#include + +#ifndef __cplusplus +#error This header is C++ only! +#endif + + +using call_t = std::array< uint8_t, 6 >; // Data type for encoded callsign +using meta_t = std::array< uint8_t, 14 >; // Data type for LSF metadata field +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 + + +/** + * This structure provides bit field definitions for the "TYPE" field + * contained in an M17 Link Setup Frame. + */ +typedef struct +{ + uint16_t stream : 1; //< Packet/stream indicator: 0 = packet, 1 = stream + uint16_t dataType : 2; //< Data type indicator + uint16_t encType : 2; //< Encryption type + uint16_t encSubType : 2; //< Encryption subtype + uint16_t CAN : 4; //< Channel Access Number + uint16_t : 4; //< Reserved, padding to 16 bit +} +__attribute__((packed)) streamType_t; + + +/** + * Data structure corresponding to a full M17 Link Setup Frame. + */ +typedef struct +{ + call_t dst; //< Destination callsign + call_t src; //< Source callsign + streamType_t type; //< Stream type information + meta_t meta; //< Metadata + uint16_t crc; //< CRC +} +__attribute__((packed)) lsf_t; + + +/** + * Data structure corresponding to a full M17 data frame. + */ +typedef struct +{ + uint16_t frameNum; //< Frame number + payload_t payload; //< Payload data +} +__attribute__((packed)) dataFrame_t; + +#endif /* M17DATATYPES_H */ diff --git a/openrtx/include/protocols/M17/M17Frame.h b/openrtx/include/protocols/M17/M17Frame.h new file mode 100644 index 00000000..5602c321 --- /dev/null +++ b/openrtx/include/protocols/M17/M17Frame.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2021 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 M17FRAME_H +#define M17FRAME_H + +#ifndef __cplusplus +#error This header is C++ only! +#endif + +#include +#include +#include "M17Datatypes.h" + +/** + * This class describes and handles a generic M17 data frame. + */ +class M17Frame +{ +public: + + /** + * Constructor. + */ + M17Frame() + { + clear(); + } + + /** + * Destructor. + */ + ~M17Frame(){ } + + /** + * Clear the frame content, filling it with zeroes. + */ + void clear() + { + memset(&data, 0x00, sizeof(dataFrame_t)); + } + + /** + * Set frame sequence number. + * + * @param seqNum: frame number, between 0 and 0x7FFF. + */ + void setFrameNumber(const uint16_t seqNum) + { + // NOTE: M17 fields are big-endian, we need to swap bytes + data.frameNum = __builtin_bswap16(seqNum & 0x7fff); + } + + /** + * Mark this frame as the last one in the transmission, informing the + * receiver that transmission ends. + */ + void lastFrame() + { + data.frameNum |= 0x0080; + } + + /** + * Access frame payload. + * + * @return a reference to frame's paylod field, allowing for both read and + * write access. + */ + payload_t& payload() + { + return data.payload; + } + + /** + * Get underlying data structure. + * + * @return a reference to the underlying dataFrame_t data structure. + */ + dataFrame_t& getData() + { + return data; + } + + /** + * Dump the frame content to a std::array. + * + * \return std::array containing the content of the frame. + */ + std::array< uint8_t, sizeof(dataFrame_t) > toArray() + { + std::array< uint8_t, sizeof(dataFrame_t) > frame; + memcpy(frame.data(), &data, frame.size()); + return frame; + } + +private: + + dataFrame_t data; ///< Underlying frame data. +}; + +#endif /* M17FRAME_H */ diff --git a/openrtx/include/protocols/M17/M17LinkSetupFrame.h b/openrtx/include/protocols/M17/M17LinkSetupFrame.h new file mode 100644 index 00000000..b554417b --- /dev/null +++ b/openrtx/include/protocols/M17/M17LinkSetupFrame.h @@ -0,0 +1,128 @@ +/*************************************************************************** + * Copyright (C) 2021 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 M17LINKSETUPFRAME_H +#define M17LINKSETUPFRAME_H + +#ifndef __cplusplus +#error This header is C++ only! +#endif + +#include +#include +#include "M17Datatypes.h" + +/** + * This class describes and handles an M17 Link Setup Frame. + * By default the frame contains a broadcast destination address, unless a + * specific address is set by calling the corresponding function. + */ +class M17LinkSetupFrame +{ +public: + + /** + * Constructor. + */ + M17LinkSetupFrame(); + + /** + * Destructor. + */ + ~M17LinkSetupFrame(); + + /** + * Clear the frame content, filling it with zeroes and resetting the + * destination address to broadcast. + */ + void clear(); + + /** + * Set source callsign. + * + * @param callsign: string containing the source callsign. + */ + void setSource(const std::string& callsign); + + /** + * Set destination callsign. + * + * @param callsign: string containing the destination callsign. + */ + void setDestination(const std::string& callsign); + + /** + * Get stream type field. + * + * @return a copy of the frame's tream type field. + */ + streamType_t getType(); + + /** + * Set stream type field. + * + * @param type: stream type field to be written. + */ + void setType(streamType_t type); + + /** + * Get metadata field. + * + * @return a reference to frame's metadata field, allowing for both read and + * write access. + */ + meta_t& metadata(); + + /** + * Compute a new CRC over the frame content and update the corresponding + * field. + */ + void updateCrc(); + + /** + * Get underlying data structure. + * + * @return a reference to the underlying dataFrame_t data structure. + */ + lsf_t& getData(); + + /** + * Dump the frame content to a std::array. + * + * \return std::array containing the content of the frame. + */ + std::array< uint8_t, sizeof(lsf_t) > toArray(); + +private: + + /** + * Compute the CRC16 of a given chunk of data using the polynomial 0x5935 + * with an initial value set to 0xFFFF, as per M17 specification. + * + * \param data: pointer to the data block. + * \param len: lenght of the data block, in bytes. + * \return computed CRC16 over the data block. + */ + uint16_t crc16(const void *data, const size_t len); + + lsf_t data; ///< Underlying frame data. +}; + +#endif /* M17LINKSETUPFRAME_H */ diff --git a/openrtx/src/protocols/M17/M17Callsign.cpp b/openrtx/src/protocols/M17/M17Callsign.cpp new file mode 100644 index 00000000..e98e6095 --- /dev/null +++ b/openrtx/src/protocols/M17/M17Callsign.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * Adapted from original code written by Rob Riggs, Mobilinkd LLC * + * * + * 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 + +bool encode_callsign(const std::string& callsign, call_t& encodedCall, + bool strict) +{ + encodedCall.fill(0x00); + if(callsign.size() > 9) return false; + + // Encode the characters to base-40 digits. + uint64_t encoded = 0; + + for(auto it = callsign.rbegin(); it != callsign.rend(); ++it) + { + encoded *= 40; + if (*it >= 'A' and *it <= 'Z') + { + encoded += (*it - 'A') + 1; + } + else if (*it >= '0' and *it <= '9') + { + encoded += (*it - '0') + 27; + } + else if (*it == '-') + { + encoded += 37; + } + else if (*it == '/') + { + encoded += 38; + } + else if (*it == '.') + { + encoded += 39; + } + else if (strict) + { + return false; + } + } + + auto *ptr = reinterpret_cast< uint8_t *>(&encoded); + std::copy(ptr, ptr + 6, encodedCall.rbegin()); + + return true; +} + +std::string decode_callsign(const call_t& encodedCall) +{ + // First of all, check if encoded address is a broadcast one + bool isBroadcast = true; + for(auto& elem : encodedCall) + { + if(elem != 0xFF) + { + isBroadcast = false; + break; + } + } + + if(isBroadcast) return "BROADCAST"; + + /* + * Address is not broadcast, decode it. + * TODO: current implementation works only on little endian architectures. + */ + static const char charMap[] = "xABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."; + + uint64_t encoded = 0; + auto p = reinterpret_cast(&encoded); + std::copy(encodedCall.rbegin(), encodedCall.rend(), p); + + // Decode each base-40 digit and map them to the appriate character. + std::string result; + size_t index = 0; + + while(encoded) + { + result[index++] = charMap[encoded % 40]; + encoded /= 40; + } + + result[index] = '\0'; + + return result; +} diff --git a/openrtx/src/protocols/M17/M17LinkSetupFrame.cpp b/openrtx/src/protocols/M17/M17LinkSetupFrame.cpp new file mode 100644 index 00000000..b4a2a4c5 --- /dev/null +++ b/openrtx/src/protocols/M17/M17LinkSetupFrame.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (C) 2021 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 + +M17LinkSetupFrame::M17LinkSetupFrame() +{ + clear(); +} + +M17LinkSetupFrame::~M17LinkSetupFrame() +{ + +} + +void M17LinkSetupFrame::clear() +{ + memset(&data, 0x00, sizeof(lsf_t)); + data.dst.fill(0xFF); +} + +void M17LinkSetupFrame::setSource(const std::string& callsign) +{ + encode_callsign(callsign, data.src); +} + +void M17LinkSetupFrame::setDestination(const std::string& callsign) +{ + encode_callsign(callsign, data.dst); +} + +streamType_t M17LinkSetupFrame::getType() +{ + // NOTE: M17 fields are big-endian, we need to swap bytes + uint16_t *a = reinterpret_cast< uint16_t* >(&data.type); + uint16_t b = __builtin_bswap16(*a); + return *reinterpret_cast< streamType_t* >(&b); +} + +void M17LinkSetupFrame::setType(streamType_t type) +{ + // NOTE: M17 fields are big-endian, we need to swap bytes + uint16_t *a = reinterpret_cast< uint16_t* >(&type); + uint16_t b = __builtin_bswap16(*a); + data.type = *reinterpret_cast< streamType_t* >(&b); +} + +meta_t& M17LinkSetupFrame::metadata() +{ + return data.meta; +} + +void M17LinkSetupFrame::updateCrc() +{ + // Compute CRC over the first 28 bytes, then store it in big endian format. + uint16_t crc = crc16(&data, 28); + data.crc = __builtin_bswap16(crc); +} + +lsf_t& M17LinkSetupFrame::getData() +{ + return data; +} + +std::array< uint8_t, sizeof(lsf_t) > M17LinkSetupFrame::toArray() +{ + std::array< uint8_t, sizeof(lsf_t) > frame; + memcpy(frame.data(), &data, frame.size()); + return frame; +} + +uint16_t M17LinkSetupFrame::crc16(const void *data, const size_t len) +{ + const uint8_t *ptr = reinterpret_cast< const uint8_t *>(data); + uint16_t crc = 0xFFFF; + + for(size_t i = 0; i < len; i++) + { + crc ^= (ptr[i] << 8); + + for(uint8_t j = 0; j < 8; j++) + { + if(crc & 0x8000) + crc = (crc << 1) ^ 0x5935; + else + crc = (crc << 1); + } + } + + return crc; +}