// Copyright 2015-2021 Mobilinkd LLC // All rights reserved. #pragma once #include "Encoder.h" #include "HdlcFrame.hpp" #include "LinkSetupFrame.h" #include "CRC16.h" #include "Convolution.h" #include "M17Randomizer.h" #include "PolynomialInterleaver.h" #include #include #include namespace mobilinkd { /** * M17 audio stream encoder. This is the data link layer for M17 * audio streams. This is responsible for sending the preamble, sync * words, encoding, interleaving and randomizing the data. * * The sender is responsible for sending a link setup frame and for * constructing the payload portion of the audio stream (frame number, * Codec2-encoded audio, CRC). The encoder is responsible for encoding * the LSF into LICH segments and assembling the LICH and payload into * an audio stream frame. * * @note: the stream encoder is actually agnostic about the payload * content. It could be V/V, V/D, or D/C mode. It is also agnostic * about the payload encoding. It is assumed that, if encryption is * used, the payload arrives encrypted. * * There is no collision avoidance used for audio streams. * * The M17 audio encoder will insert up to 2 "dead" frames (frames * with the payload set to all 0) before dropping the connection. * It will do dropped frame detection by looking at the frame number. * It will also do end of stream detection by looking at the frame * number. If more than two frames time slots are missed, the * encoder assumes the transmission has ended and stops transmitting. * * It will not transmit until a new LSF is sent to establish a new * session. * * The uses the frame size to determine the type of frame it is * handling. There are only two acceptable frame sizes: * * - 30 byte frames are link setup frames. * - 20 byte frames are data frames. * * The 20-byte data frame consists of a 16-bit frame number, 128-bit * payload, and 16-bit CRC, for a total of 160 bits or 20 bytes. * * If the modulator receives any other frame size, it will immediately * stop transmitting and signal an error condition. */ struct M17Encoder : public Encoder { static const std::array LSF_SYNC; static const std::array STREAM_SYNC; static const std::array PACKET_SYNC; static const std::array BERT_SYNC; enum class State {INACTIVE, IDLE, ACTIVE}; enum class FrameType {BASIC_PACKET, FULL_PACKET, VOICE_STREAM}; M17Encoder(osMessageQId input); ~M17Encoder(); void run() override; void update_settings() override; void updateModulator() override; void stop() override; EncoderType encoder_type() const override { return EncoderType::M17; } tnc::hdlc::IoFrame* create_bert_frame() override; private: using lsf_t = std::array; using lich_segment_t = std::array; // Golay-encoded LICH. using bitstream_t = std::array; // M17 frame of bits (in bytes). using payload_t = std::array; // Bytes in the payload of a data frame. using frame_t = std::array; // M17 frame (without sync word). static void encoderTask(void const*); void process_packet(tnc::hdlc::IoFrame*, FrameType type); void process_stream(tnc::hdlc::IoFrame*, FrameType type); void send_preamble(); void send_link_setup(); void send_basic_packet(tnc::hdlc::IoFrame*); void send_full_packet(tnc::hdlc::IoFrame*); void send_packet_frame(const std::array& packet_frame); void send_stream(tnc::hdlc::IoFrame*, FrameType type); void create_link_setup(tnc::hdlc::IoFrame*, FrameType type); lich_segment_t make_lich_segment(std::array segment); /** * Do the p*persistent CSMA handling. In order to prevent resource * starvation, we drop any packets delayed by more than 5 seconds. * * 0. CSMA called. * 1. If the channel is open * 1a. Pick a random number between 0-255. * 1b. If it less than or equal to p, transmit the packet. * 2. Otherwise wait slot_time * 10 ms. * 2.b Go to step 1. * * @pre The demodulator is running in order to detect the data carrier. * * @note For this to work, the demodulator must be left running * while CSMA is taking place in order to do carrier detection. * * @return true if OK to send, otherwise CSMA has timed out and * the packet should be dropped. */ bool do_csma(); /** * Convolutional encode N bytes of data, returning N*2 + 1 bytes * of encoded data (4 zero flush bits are added). If total bits * are not an even byte boundary, the high bits of the last byte * are used. * * @param data * @param total_bits * @return */ template static std::array conv_encode(std::array data, size_t total_bits = N * 8) { std::array result; uint8_t bit_index = 0; uint8_t byte_index = 0; uint8_t tmp = 0; uint32_t memory = 0; for (auto b : data) { auto bits = std::min(size_t(8), total_bits); for (size_t i = 0; i != bits; ++i) { uint32_t x = (b & 0x80) >> 7; b <<= 1; memory = update_memory<4>(memory, x); tmp = (tmp << 1) | convolve_bit(031, memory); tmp = (tmp << 1) | convolve_bit(027, memory); bit_index += 2; if (bit_index == 8) { bit_index = 0; result[byte_index++] = tmp; tmp = 0; } } total_bits -= bits; } // Flush the encoder. for (size_t i = 0; i != 4; ++i) { memory = update_memory<4>(memory, 0); tmp = (tmp << 1) | convolve_bit(031, memory); tmp = (tmp << 1) | convolve_bit(027, memory); bit_index += 2; if (bit_index == 8) { bit_index = 0; result[byte_index++] = tmp; tmp = 0; } } // Frame may not end on a byte boundary. if (bit_index != 0) { tmp <<= (8 - bit_index); result[byte_index] = tmp; } return result; } osMessageQId input_queue; osThreadId encoderTaskHandle; State state = State::INACTIVE; frame_t m17_frame; payload_t stream_payload; lsf_t current_lsf; LinkSetupFrame::encoded_call_t src; LinkSetupFrame::encoded_call_t dest; M17ByteRandomizer<46> randomizer; PolynomialInterleaver<45, 92, 368> interleaver; CRC16<0x5935, 0xFFFF> crc; bool back2back = false; }; } // mobilinkd