From 1230625601edda4d5d2b882dec537758a714847b Mon Sep 17 00:00:00 2001 From: Dave Behnke <916775+dbehnke@users.noreply.github.com> Date: Sun, 28 Dec 2025 01:40:03 -0500 Subject: [PATCH] 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. --- reflector/Configure.cpp | 8 +++ reflector/JsonKeys.h | 9 ++- reflector/M17Packet.cpp | 122 +++++++++++++++++++++++++++++++++----- reflector/M17Packet.h | 50 ++++++++++++++-- reflector/M17Parrot.cpp | 64 ++++++++++++++------ reflector/M17Protocol.cpp | 78 +++++++++++++++++------- reflector/M17Protocol.h | 5 +- reflector/Protocol.cpp | 16 +---- reflector/Protocol.h | 3 +- 9 files changed, 273 insertions(+), 82 deletions(-) diff --git a/reflector/Configure.cpp b/reflector/Configure.cpp index 445e953..8d87b00 100644 --- a/reflector/Configure.cpp +++ b/reflector/Configure.cpp @@ -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 diff --git a/reflector/JsonKeys.h b/reflector/JsonKeys.h index 72cfc0d..c29361c 100644 --- a/reflector/JsonKeys.h +++ b/reflector/JsonKeys.h @@ -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" }; diff --git a/reflector/M17Packet.cpp b/reflector/M17Packet.cpp index 7ed45a8..e00419a 100644 --- a/reflector/M17Packet.cpp +++ b/reflector/M17Packet.cpp @@ -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); } diff --git a/reflector/M17Packet.h b/reflector/M17Packet.h index 3d58ffd..3620e19 100644 --- a/reflector/M17Packet.h +++ b/reflector/M17Packet.h @@ -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; }; diff --git a/reflector/M17Parrot.cpp b/reflector/M17Parrot.cpp index 7fbe3fc..9955fd2 100644 --- a/reflector/M17Parrot.cpp +++ b/reflector/M17Parrot.cpp @@ -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(); diff --git a/reflector/M17Protocol.cpp b/reflector/M17Protocol.cpp index 9da0223..afe674b 100644 --- a/reflector/M17Protocol.cpp +++ b/reflector/M17Protocol.cpp @@ -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(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 } diff --git a/reflector/M17Protocol.h b/reflector/M17Protocol.h index 5887eb3..efcc57a 100644 --- a/reflector/M17Protocol.h +++ b/reflector/M17Protocol.h @@ -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); diff --git a/reflector/Protocol.cpp b/reflector/Protocol.cpp index 0bcc223..132d6fa 100644 --- a/reflector/Protocol.cpp +++ b/reflector/Protocol.cpp @@ -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) diff --git a/reflector/Protocol.h b/reflector/Protocol.h index 678b533..d342232 100644 --- a/reflector/Protocol.h +++ b/reflector/Protocol.h @@ -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