NucleoTNC/TNC/M17Encoder.h

210 wiersze
6.8 KiB
C++

// Copyright 2015-2021 Mobilinkd LLC <rob@mobilinkd.com>
// 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 <cmsis_os.h>
#include <array>
#include <cstdint>
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<uint8_t, 2> LSF_SYNC;
static const std::array<uint8_t, 2> STREAM_SYNC;
static const std::array<uint8_t, 2> PACKET_SYNC;
static const std::array<uint8_t, 2> 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<uint8_t, 30>;
using lich_segment_t = std::array<uint8_t, 12>; // Golay-encoded LICH.
using bitstream_t = std::array<uint8_t, 48>; // M17 frame of bits (in bytes).
using payload_t = std::array<uint8_t, 34>; // Bytes in the payload of a data frame.
using frame_t = std::array<uint8_t, 46>; // 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<uint8_t, 26>& 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<uint8_t, 6> 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 <typename T, size_t N>
static std::array<T, N * 2 + 1> conv_encode(std::array<T, N> data,
size_t total_bits = N * 8)
{
std::array<T, N * 2 + 1> 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