Impl: M17 Dual-Mode Support & Legacy Compat Flag

Implemented M17LegacyCompat flag (default true). Added dual-mode receiver for 54/56-byte frames. Updated transmitter and Parrot to respect compat flag.
pull/19/head
Dave Behnke 1 month ago
parent c71dfc95a2
commit 1230625601

@ -62,6 +62,7 @@
#define JIPV6EXTERNAL "IPv6External"
#define JJSONPATH "JsonPath"
#define JM17 "M17"
#define JM17LEGACYCOMPAT "M17LegacyCompat"
#define JMMDVM "MMDVM"
#define JMODE "Mode"
#define JMODULE "Module"
@ -368,6 +369,8 @@ bool CConfigure::ReadData(const std::string &path)
case ESection::m17:
if (0 == key.compare(JPORT))
data[g_Keys.m17.port] = getUnsigned(value, "M17 Port", 1024, 65535, 17000);
else if (0 == key.compare(JM17LEGACYCOMPAT))
data[g_Keys.m17.compat] = IS_TRUE(value[0]);
else
badParam(key);
break;
@ -684,6 +687,11 @@ bool CConfigure::ReadData(const std::string &path)
isDefined(ErrorLevel::fatal, JDMRPLUS, JPORT, g_Keys.dmrplus.port, rval);
isDefined(ErrorLevel::fatal, JDPLUS, JPORT, g_Keys.dplus.port, rval);
isDefined(ErrorLevel::fatal, JM17, JPORT, g_Keys.m17.port, rval);
if (data.contains(g_Keys.m17.compat))
data[g_Keys.m17.compat] = GetBoolean(g_Keys.m17.compat);
else
data[g_Keys.m17.compat] = true; // Default to Legacy Mode (54 bytes) for compatibility
isDefined(ErrorLevel::fatal, JURF, JPORT, g_Keys.urf.port, rval);
// BM

@ -26,9 +26,12 @@ struct SJsonKeys {
dcs { "DCSPort" },
dextra { "DExtraPort" },
dmrplus { "DMRPlusPort" },
dplus { "DPlusPort" },
m17 { "M17Port" },
urf { "URFPort" };
dplus { "DPlusPort" };
struct { const std::string port, compat; }
m17 { "M17Port", "M17LegacyCompat" };
PORTONLY urf { "URFPort" };
struct G3 { const std::string enable; }
g3 { "G3Enable" };

@ -21,12 +21,21 @@
#include "M17Packet.h"
CM17Packet::CM17Packet(const uint8_t *buf)
CM17Packet::CM17Packet(const uint8_t *buf, bool isStandard)
: m_isStandard(isStandard)
{
memcpy(m17.magic, buf, sizeof(SM17Frame));
destination.CodeIn(m17.lich.addr_dst);
source.CodeIn(m17.lich.addr_src);
if (m_isStandard)
{
memcpy(m_frame.buffer, buf, sizeof(SM17FrameStandard));
destination.CodeIn(m_frame.standard.lich.addr_dst);
source.CodeIn(m_frame.standard.lich.addr_src);
}
else
{
memcpy(m_frame.buffer, buf, sizeof(SM17FrameLegacy));
destination.CodeIn(m_frame.legacy.lich.addr_dst);
source.CodeIn(m_frame.legacy.lich.addr_src);
}
}
const CCallsign &CM17Packet::GetDestCallsign() const
@ -46,45 +55,130 @@ char CM17Packet::GetDestModule() const
uint16_t CM17Packet::GetFrameNumber() const
{
return ntohs(m17.framenumber);
if (m_isStandard)
return ntohs(m_frame.standard.framenumber);
else
return ntohs(m_frame.legacy.framenumber);
}
uint16_t CM17Packet::GetFrameType() const
{
return ntohs(m17.lich.frametype);
if (m_isStandard)
return ntohs(m_frame.standard.lich.frametype);
else
return ntohs(m_frame.legacy.lich.frametype);
}
const uint8_t *CM17Packet::GetPayload() const
{
return m17.payload;
if (m_isStandard)
return m_frame.standard.payload;
else
return m_frame.legacy.payload;
}
const uint8_t *CM17Packet::GetNonce() const
{
return m17.lich.nonce;
if (m_isStandard)
return m_frame.standard.lich.nonce;
else
return m_frame.legacy.lich.nonce;
}
void CM17Packet::SetPayload(const uint8_t *newpayload)
{
memcpy(m17.payload, newpayload, 16);
if (m_isStandard)
memcpy(m_frame.standard.payload, newpayload, 16);
else
memcpy(m_frame.legacy.payload, newpayload, 16);
}
uint16_t CM17Packet::GetStreamId() const
{
return ntohs(m17.streamid);
if (m_isStandard)
return ntohs(m_frame.standard.streamid);
else
return ntohs(m_frame.legacy.streamid);
}
uint16_t CM17Packet::GetCRC() const
{
return ntohs(m17.crc);
if (m_isStandard)
return ntohs(m_frame.standard.crc);
else
return ntohs(m_frame.legacy.crc);
}
void CM17Packet::SetCRC(uint16_t crc)
{
m17.crc = htons(crc);
if (m_isStandard)
m_frame.standard.crc = htons(crc);
else
m_frame.legacy.crc = htons(crc);
}
void CM17Packet::SetDestCallsign(const CCallsign &cs)
{
destination = cs;
if (m_isStandard)
destination.CodeOut(m_frame.standard.lich.addr_dst);
else
destination.CodeOut(m_frame.legacy.lich.addr_dst);
}
void CM17Packet::SetSourceCallsign(const CCallsign &cs)
{
source = cs;
if (m_isStandard)
source.CodeOut(m_frame.standard.lich.addr_src);
else
source.CodeOut(m_frame.legacy.lich.addr_src);
}
void CM17Packet::SetStreamId(uint16_t id)
{
if (m_isStandard)
m_frame.standard.streamid = htons(id);
else
m_frame.legacy.streamid = htons(id);
}
void CM17Packet::SetFrameNumber(uint16_t fn)
{
if (m_isStandard)
m_frame.standard.framenumber = htons(fn);
else
m_frame.legacy.framenumber = htons(fn);
}
void CM17Packet::SetFrameType(uint16_t ft)
{
if (m_isStandard)
m_frame.standard.lich.frametype = htons(ft);
else
m_frame.legacy.lich.frametype = htons(ft);
}
void CM17Packet::SetMagic()
{
if (m_isStandard)
memcpy(m_frame.standard.magic, "M17 ", 4);
else
memcpy(m_frame.legacy.magic, "M17 ", 4);
}
void CM17Packet::SetNonce(const uint8_t *nonce)
{
if (m_isStandard)
memcpy(m_frame.standard.lich.nonce, nonce, 14);
else
memcpy(m_frame.legacy.lich.nonce, nonce, 14);
}
bool CM17Packet::IsLastPacket() const
{
return ((0x8000u & ntohs(m17.framenumber)) == 0x8000u);
if (m_isStandard)
return ((0x8000u & ntohs(m_frame.standard.framenumber)) == 0x8000u);
else
return ((0x8000u & ntohs(m_frame.legacy.framenumber)) == 0x8000u);
}

@ -30,23 +30,42 @@
// M17 Packets
//all structures must be big endian on the wire, so you'll want htonl (man byteorder 3) and such.
using SM17Lich = struct __attribute__((__packed__)) lich_tag {
using SM17LichLegacy = struct __attribute__((__packed__)) lich_tag {
uint8_t addr_dst[6];
uint8_t addr_src[6];
uint16_t frametype; //frametype flag field per the M17 spec
uint8_t nonce[14]; //bytes for the nonce
}; // 6 + 6 + 2 + 14 = 28 bytes
// Standard LICH includes CRC
using SM17LichStandard = struct __attribute__((__packed__)) lich_std_tag {
uint8_t addr_dst[6];
uint8_t addr_src[6];
uint16_t frametype;
uint8_t nonce[14];
uint16_t crc;
}; // 6 + 6 + 2 + 14 + 2 = 30 bytes
//without SYNC or other parts
using SM17Frame = struct __attribute__((__packed__)) m17_tag {
using SM17FrameLegacy = struct __attribute__((__packed__)) m17_tag {
uint8_t magic[4];
uint16_t streamid;
SM17Lich lich;
SM17LichLegacy lich;
uint16_t framenumber;
uint8_t payload[16];
uint16_t crc; //16 bit CRC
}; // 4 + 2 + 28 + 2 + 16 + 2 = 54 bytes
using SM17FrameStandard = struct __attribute__((__packed__)) m17_std_tag {
uint8_t magic[4];
uint16_t streamid;
SM17LichStandard lich;
uint16_t framenumber;
uint8_t payload[16];
uint16_t crc; //16 bit CRC
}; // 4 + 2 + 30 + 2 + 16 + 2 = 56 bytes
using SLinkPacket = struct __attribute__((__packed__)) link_tag {
uint8_t magic[4];
uint8_t fromcs[6];
@ -57,7 +76,7 @@ class CM17Packet
{
public:
CM17Packet() {}
CM17Packet(const uint8_t *buf);
CM17Packet(const uint8_t *buf, bool isStandard = false);
const CCallsign &GetDestCallsign() const;
const CCallsign &GetSourceCallsign() const;
char GetDestModule() const;
@ -69,9 +88,30 @@ public:
uint16_t GetStreamId() const;
uint16_t GetCRC() const;
void SetCRC(uint16_t crc);
void SetDestCallsign(const CCallsign &cs);
void SetSourceCallsign(const CCallsign &cs);
void SetStreamId(uint16_t id);
void SetFrameNumber(uint16_t fn);
void SetFrameType(uint16_t ft);
void SetNonce(const uint8_t *nonce);
void SetMagic();
uint8_t *GetLICHPointer() { return m_isStandard ? (uint8_t*)&m_frame.standard.lich : (uint8_t*)&m_frame.legacy.lich; }
size_t GetLICHSize() const { return m_isStandard ? sizeof(SM17LichStandard) : sizeof(SM17LichLegacy); }
const uint8_t *GetBuffer() const { return m_frame.buffer; }
size_t GetSize() const { return m_isStandard ? sizeof(SM17FrameStandard) : sizeof(SM17FrameLegacy); }
bool IsLastPacket() const;
private:
CCallsign destination, source;
SM17Frame m17;
// Flexible storage for either Legacy or Standard frame
union {
SM17FrameLegacy legacy;
SM17FrameStandard standard;
uint8_t buffer[60];
} m_frame;
bool m_isStandard;
};

@ -22,12 +22,14 @@ void CM17StreamParrot::Add(const CBuffer &Buffer, uint16_t streamId, uint16_t fr
{
m_streamId = streamId;
size_t length = m_is3200 ? 16 : 8;
// Payload is at offset 40 in SM17Frame (4 magic + 2 streamid + 28 lich + 2 fn + 16 payload + 2 crc)
// urfd's CDvFramePacket/CM17Packet probably maps this.
// For simplicity in this implementation, we assume Buffer passed is the raw payload OR mapped.
// Looking at M17Protocol.cpp:410, payload starts at packet.payload
bool isStandard = false;
if (Buffer.size() == 56) isStandard = true;
// Use parser to get payload pointer safely
CM17Packet parser(Buffer.data(), isStandard);
const uint8_t *payload = parser.GetPayload();
const uint8_t *payload = Buffer.data() + 40; // payload offset in SM17Frame
m_data.emplace_back(payload, payload + length);
}
m_lastHeard.start();
@ -47,15 +49,28 @@ void CM17StreamParrot::playThread()
{
m_state = EParrotState::play;
SM17Frame frame;
memset(&frame, 0, sizeof(frame));
memcpy(frame.magic, "M17 ", 4);
frame.streamid = m_streamId; // reuse or generate new? mrefd generates new.
// Determine format to send
bool useLegacy = g_Configure.GetBoolean(g_Keys.m17.compat);
uint8_t buffer[60];
CM17Packet pkt(buffer, !useLegacy);
memset(buffer, 0, 60); // clear buffer
// Set LICH addresses
memset(frame.lich.addr_dst, 0xFF, 6); // @ALL
m_src.CodeOut(frame.lich.addr_src);
frame.lich.frametype = htons(m_frameType);
pkt.SetMagic();
pkt.SetStreamId(m_streamId);
// I will add `SetDestBytes` to CM17Packet? Or just use explicit CCallsign.
// I will try to use the `m_src` as dest? No, that's what `CodeOut` does.
// I will use `pkt.SetDestCallsign` with a dummy, and then manually overwrite if needed?
// Better: `CM17Packet` exposes `GetLichPointer()`. I can write to it manually!
// Set Source
pkt.SetSourceCallsign(m_src);
pkt.SetFrameType(m_frameType);
// Set Dest to FF
uint8_t *lich = pkt.GetLICHPointer();
memset(lich, 0xFF, 6); // Dest is at offset 0 of LICH
auto clock = std::chrono::steady_clock::now();
size_t size = m_data.size();
@ -63,21 +78,32 @@ void CM17StreamParrot::playThread()
for (size_t n = 0; n < size; n++)
{
size_t length = m_is3200 ? 16 : 8;
memcpy(frame.payload, m_data[n].data(), length);
pkt.SetPayload(m_data[n].data());
uint16_t fn = (uint16_t)n;
if (n == size - 1)
fn |= 0x8000u;
frame.framenumber = htons(fn);
pkt.SetFrameNumber(fn);
CM17CRC m17crc;
frame.crc = htons(m17crc.CalcCRC((uint8_t*)&frame, sizeof(SM17Frame)-2));
// CRC
CM17CRC m17crc_inst;
if (!useLegacy) {
// Standard LICH CRC
uint16_t l_crc = m17crc_inst.CalcCRC(lich, 28);
((SM17LichStandard*)lich)->crc = htons(l_crc);
}
uint16_t p_crc = m17crc_inst.CalcCRC(pkt.GetBuffer(), pkt.GetSize()-2);
pkt.SetCRC(p_crc);
clock = clock + std::chrono::milliseconds(40);
std::this_thread::sleep_until(clock);
if (m_proto)
m_proto->Send(frame, m_client->GetIp());
if (m_proto) {
CBuffer sendBuf;
sendBuf.Append(pkt.GetBuffer(), pkt.GetSize());
m_proto->Send(sendBuf, m_client->GetIp());
}
m_data[n].clear();
}
m_data.clear();

@ -312,18 +312,22 @@ void CM17Protocol::HandleQueue(void)
if ( packet->IsDvHeader() )
{
// this relies on queue feeder setting valid module id
// m_StreamsCache[module] will be created if it doesn't exist
m_StreamsCache[module].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet.get());
// m_StreamsCache[module].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet.get());
m_StreamsCache[module].m_iSeqCounter = 0;
}
else if (packet->IsDvFrame())
{
if ((1 == m_StreamsCache[module].m_iSeqCounter % 2) || packet->IsLastPacket())
{
// encode it
SM17Frame frame;
// Determine if we should send Legacy or Standard packets
// Default to Legacy (true) if key missing, but Configure.cpp handles default.
bool useLegacy = g_Configure.GetBoolean(g_Keys.m17.compat);
EncodeM17Packet(frame, m_StreamsCache[module].m_dvHeader, (CDvFramePacket *)packet.get(), m_StreamsCache[module].m_iSeqCounter);
// encode it using M17Packet wrapper
uint8_t buffer[60]; // Enough for both
CM17Packet m17pkt(buffer, !useLegacy);
EncodeM17Packet(m17pkt, m_StreamsCache[module].m_dvHeader, (CDvFramePacket *)packet.get(), m_StreamsCache[module].m_iSeqCounter);
// push it to all our clients linked to the module and who are not streaming in
CClients *clients = g_Reflector.GetClients();
@ -335,12 +339,27 @@ void CM17Protocol::HandleQueue(void)
if ( !client->IsAMaster() && (client->GetReflectorModule() == module) )
{
// set the destination
client->GetCallsign().CodeOut(frame.lich.addr_dst);
// set the crc
frame.crc = htons(m17crc.CalcCRC(frame.magic, sizeof(SM17Frame)-2));
// now send the packet
Send(frame, client->GetIp());
m17pkt.SetDestCallsign(client->GetCallsign());
// Calculate LICH CRC if Standard
if (!useLegacy) {
uint8_t *lich = m17pkt.GetLICHPointer();
// CRC over first 28 bytes of LICH
uint16_t l_crc = m17crc.CalcCRC(lich, 28);
// Set CRC at offset 28 of LICH (bytes 28,29)
// We can cast to SM17LichStandard to be safe or use set/memcpy
((SM17LichStandard*)lich)->crc = htons(l_crc);
}
// set the packet crc
// Legacy: CRC over first 52 bytes. Standard: CRC over first 54 bytes.
uint16_t p_crc = m17crc.CalcCRC(m17pkt.GetBuffer(), m17pkt.GetSize() - 2);
m17pkt.SetCRC(p_crc);
// now send the packet
CBuffer sendBuf;
sendBuf.Append(m17pkt.GetBuffer(), m17pkt.GetSize());
Send(sendBuf, client->GetIp());
}
}
g_Reflector.ReleaseClients();
@ -462,10 +481,23 @@ bool CM17Protocol::IsValidDvPacket(const CBuffer &Buffer, std::unique_ptr<CDvHea
{
uint8_t tag[] = { 'M', '1', '7', ' ' };
if ( (Buffer.size() == sizeof(SM17Frame)) && (0 == Buffer.Compare(tag, sizeof(tag))) && (0x4U == (0x1CU & Buffer[19])) )
bool isStandard = false;
bool validSize = false;
if (Buffer.size() == sizeof(SM17FrameLegacy)) {
validSize = true;
isStandard = false;
} else if (Buffer.size() == sizeof(SM17FrameStandard)) {
validSize = true;
isStandard = true;
}
if ( validSize && (0 == Buffer.Compare(tag, sizeof(tag))) && (0x4U == (0x1CU & Buffer[19])) )
{
// Make the M17 header
CM17Packet m17(Buffer.data());
// Make the M17 header wrapper
// Note: CM17Packet constructor copies the buffer
CM17Packet m17(Buffer.data(), isStandard);
// get the header
header = std::unique_ptr<CDvHeaderPacket>(new CDvHeaderPacket(m17));
@ -490,28 +522,28 @@ void CM17Protocol::EncodeKeepAlivePacket(CBuffer &Buffer)
g_Reflector.GetCallsign().CodeOut(Buffer.data() + 4);
}
void CM17Protocol::EncodeM17Packet(SM17Frame &frame, const CDvHeaderPacket &Header, const CDvFramePacket *DvFrame, uint32_t iSeq) const
void CM17Protocol::EncodeM17Packet(CM17Packet &packet, const CDvHeaderPacket &Header, const CDvFramePacket *DvFrame, uint32_t iSeq) const
{
ECodecType codec_in = Header.GetCodecIn(); // We'll need this
// do the lich structure first
// first, the src callsign (the lich.dest will be set in HandleQueue)
CCallsign from = Header.GetMyCallsign();
from.CodeOut(frame.lich.addr_src);
packet.SetSourceCallsign(Header.GetMyCallsign());
// then the frame type, if the incoming frame is M17 1600, then it will be Voice+Data only, otherwise Voice-Only
frame.lich.frametype = htons((ECodecType::c2_1600==codec_in) ? 0x7U : 0x5U);
memcpy(frame.lich.nonce, DvFrame->GetNonce(), 14);
packet.SetFrameType((ECodecType::c2_1600==codec_in) ? 0x7U : 0x5U);
packet.SetNonce(DvFrame->GetNonce());
// now the main part of the packet
memcpy(frame.magic, "M17 ", 4);
packet.SetMagic();
// the frame number comes from the stream sequence counter
uint16_t fn = (iSeq / 2) % 0x8000U;
if (DvFrame->IsLastPacket())
fn |= 0x8000U;
frame.framenumber = htons(fn);
memcpy(frame.payload, DvFrame->GetCodecData(ECodecType::c2_3200), 16);
frame.streamid = Header.GetStreamId(); // no host<--->network byte swapping since we never do any math on this value
packet.SetFrameNumber(fn);
packet.SetPayload(DvFrame->GetCodecData(ECodecType::c2_3200));
packet.SetStreamId(Header.GetStreamId());
// the CRC will be set in HandleQueue, after lich.dest is set
}

@ -68,7 +68,8 @@ public:
// packet encoding helpers (public for Parrot access)
void Send(const CBuffer &buf, const CIp &Ip) const { CProtocol::Send(buf, Ip); }
void Send(const char *buf, const CIp &Ip) const { CProtocol::Send(buf, Ip); }
void Send(const SM17Frame &frame, const CIp &Ip) const { CProtocol::Send(frame, Ip); }
virtual bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer &) const override;
virtual bool EncodeDvFramePacket(const CDvFramePacket &, CBuffer &) const override;
@ -94,7 +95,7 @@ private:
// packet encoding helpers
void EncodeKeepAlivePacket(CBuffer &);
void EncodeM17Packet(SM17Frame &, const CDvHeaderPacket &, const CDvFramePacket *, uint32_t) const;
void EncodeM17Packet(CM17Packet &packet, const CDvHeaderPacket &, const CDvFramePacket *, uint32_t) const;
// parrot
void HandleParrot(const CIp &Ip, const CBuffer &Buffer, bool isStream);

@ -335,21 +335,7 @@ void CProtocol::Send(const char *buf, const CIp &Ip, uint16_t port) const
}
}
void CProtocol::Send(const SM17Frame &frame, const CIp &Ip) const
{
switch (Ip.GetFamily())
{
case AF_INET:
m_Socket4.Send(frame.magic, sizeof(SM17Frame), Ip);
break;
case AF_INET6:
m_Socket6.Send(frame.magic, sizeof(SM17Frame), Ip);
break;
default:
std::cerr << "WrongFamily: " << Ip.GetFamily() << std::endl;
break;
}
}
#ifdef DEBUG
void CProtocol::Dump(const char *title, const uint8_t *data, int length)

@ -113,7 +113,8 @@ protected:
void Send(const char *buf, const CIp &Ip) const;
void Send(const CBuffer &buf, const CIp &Ip, uint16_t port) const;
void Send(const char *buf, const CIp &Ip, uint16_t port) const;
void Send(const SM17Frame &frame, const CIp &Ip) const;
#ifdef DEBUG
void Dump(const char *title, const uint8_t *data, int length);
#endif

Loading…
Cancel
Save

Powered by TurnKey Linux.