diff --git a/reflector/Main.h b/reflector/Main.h
index 0eeaf8c..d71ba99 100644
--- a/reflector/Main.h
+++ b/reflector/Main.h
@@ -69,7 +69,7 @@
#define VERSION_MAJOR 0
#define VERSION_MINOR 0
-#define VERSION_REVISION 6
+#define VERSION_REVISION 7
// global ------------------------------------------------------
@@ -202,7 +202,7 @@ enum class EProtocol { any, none, dextra, dplus, dcs, bm, urf, dmrplus, dmrmmdvm
//NXDNid database
#define NXDNIDDB_USE_RLX_SERVER 0 // 1 = use http, 0 = use local file
-#define NXDNIDDB_PATH "/usr/local/etc/NXDN.csv" // local file path
+#define NXDNIDDB_PATH "/home/pi/NXDN.csv" // local file path
#define NXDNIDDB_REFRESH_RATE 180 // in minutes
// Wires-X node database ----------------------------------------
diff --git a/reflector/NXDNClient.cpp b/reflector/NXDNClient.cpp
new file mode 100644
index 0000000..64cb428
--- /dev/null
+++ b/reflector/NXDNClient.cpp
@@ -0,0 +1,47 @@
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+
+// urfd -- The universal reflector
+// Copyright © 2021 Thomas A. Early N7TAE
+// Copyright © 2021 Doug McLain AD8DP
+//
+// 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 "Main.h"
+#include "NXDNClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CNXDNClient::CNXDNClient()
+{
+}
+
+CNXDNClient::CNXDNClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+}
+
+CNXDNClient::CNXDNClient(const CNXDNClient &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CNXDNClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.time() < NXDN_KEEPALIVE_TIMEOUT);
+}
diff --git a/reflector/NXDNClient.h b/reflector/NXDNClient.h
new file mode 100644
index 0000000..e75c118
--- /dev/null
+++ b/reflector/NXDNClient.h
@@ -0,0 +1,41 @@
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+
+// urfd -- The universal reflector
+// Copyright © 2021 Thomas A. Early N7TAE
+// Copyright © 2021 Doug McLain AD8DP
+//
+// 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 .
+
+#pragma once
+
+#include "Client.h"
+class CNXDNClient : public CClient
+{
+public:
+ // constructors
+ CNXDNClient();
+ CNXDNClient(const CCallsign &, const CIp &, char = ' ');
+ CNXDNClient(const CNXDNClient &);
+
+ // destructor
+ virtual ~CNXDNClient() {};
+
+ // identity
+ EProtocol GetProtocol(void) const { return EProtocol::nxdn; }
+ const char *GetProtocolName(void) const { return "NXDN"; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+};
diff --git a/reflector/NXDNProtocol.cpp b/reflector/NXDNProtocol.cpp
new file mode 100644
index 0000000..3aba152
--- /dev/null
+++ b/reflector/NXDNProtocol.cpp
@@ -0,0 +1,825 @@
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+
+// urfd -- The universal reflector
+// Copyright © 2021 Thomas A. Early N7TAE
+// Copyright © 2021 Doug McLain AD8DP
+//
+// 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 "Main.h"
+#include
+#include "NXDNClient.h"
+#include "NXDNProtocol.h"
+#include "YSFDefines.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+#include "Golay24128.h"
+
+const uint8_t NXDN_LICH_RFCT_RDCH = 2U;
+const uint8_t NXDN_LICH_USC_SACCH_NS = 0U;
+const uint8_t NXDN_LICH_USC_SACCH_SS = 2U;
+const uint8_t NXDN_LICH_STEAL_FACCH = 0U;
+const uint8_t NXDN_LICH_STEAL_NONE = 3U;
+const uint8_t NXDN_LICH_DIRECTION_INBOUND = 0U;
+const uint8_t NXDN_MESSAGE_TYPE_VCALL = 1U;
+const uint8_t NXDN_MESSAGE_TYPE_TX_REL = 8U;
+
+const int dvsi_interleave[49] = {
+ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 41, 43, 45, 47,
+ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 42, 44, 46, 48,
+ 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+CNXDNProtocol::CNXDNProtocol()
+{
+ m_seqNo = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CNXDNProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.start();
+
+ return true;
+}
+
+void CNXDNProtocol::Close(void)
+{
+ // base class
+ CProtocol::Close();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CNXDNProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+
+ std::unique_ptr Header;
+ std::array, 4> Frames;
+
+ // handle incoming packets
+#if NXDN_IPV6==true
+#if NXDN_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ // crack the packet
+ if ( IsValidDvFramePacket(Ip, Buffer, Header, Frames) )
+ {
+ OnDvFramePacketIn(Frames[0], &Ip);
+ OnDvFramePacketIn(Frames[1], &Ip);
+ OnDvFramePacketIn(Frames[2], &Ip);
+ OnDvFramePacketIn(Frames[3], &Ip);
+ }
+ else if ( IsValidDvHeaderPacket(Ip, Buffer, Header) )
+ {
+ // node linked and callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, EProtocol::ysf, Header->GetRpt2Module()) )
+ {
+ // handle it
+ OnDvHeaderPacketIn(Header, Ip);
+ }
+ }
+ else if ( IsValidDvLastFramePacket(Ip, Buffer) )
+ {
+ m_uiStreamId = 0;
+ }
+ else if ( IsValidConnectPacket(Buffer, &Callsign) )
+ {
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, EProtocol::nxdn) )
+ {
+ // add client if needed
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Callsign, Ip, EProtocol::nxdn);
+ // client already connected ?
+ if ( client == nullptr )
+ {
+ std::cout << "NXDN connect packet from " << Callsign << " at " << Ip << std::endl;
+
+ // create the client
+ auto newclient = std::make_shared(Callsign, Ip);
+
+ // aautolink, if enabled
+#if NXDN_AUTOLINK_ENABLE
+ newclient->SetReflectorModule(NXDN_AUTOLINK_MODULE);
+#endif
+
+ // and append
+ clients->AddClient(newclient);
+ }
+ else
+ {
+ client->Alive();
+ }
+
+ // acknowledge the request -- NXDNReflector simply echoes the packet
+ Send(Buffer, Ip);
+ // and done
+ g_Reflector.ReleaseClients();
+ }
+ }
+ else
+ {
+#ifdef DEBUG
+ std::string title("Unknown NXDN packet from ");
+ title += Ip.GetAddress();
+ Buffer.Dump(title);
+#endif
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+ // keep client alive
+ if ( m_LastKeepaliveTime.time() > NXDN_KEEPALIVE_PERIOD )
+ {
+ //
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.start();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CNXDNProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ auto stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ // no stream open yet, open a new one
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // find this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, EProtocol::nxdn);
+ if ( client )
+ {
+ // get client callsign
+ rpt1 = client->GetCallsign();
+ // get module it's linked to
+ auto m = client->GetReflectorModule();
+ Header->SetRpt2Module(m);
+ rpt2.SetCSModule(m);
+
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams[stream->GetStreamId()] = stream;
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+
+ // update last heard
+ if ( g_Reflector.IsValidModule(rpt2.GetCSModule()) )
+ {
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CNXDNProtocol::HandleQueue(void)
+{
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.pop();
+
+ // get our sender's id
+ const auto mod = packet->GetPacketModule();
+
+ // encode
+ CBuffer buffer;
+
+ // check if it's header
+ if ( packet->IsDvHeader() )
+ {
+ // update local stream cache
+ // this relies on queue feeder setting valid module id
+ m_StreamsCache[mod].m_dvHeader = CDvHeaderPacket((CDvHeaderPacket &)*packet.get());
+ m_StreamsCache[mod].m_iSeqCounter = 0;
+
+ // encode it
+ EncodeNXDNHeaderPacket((CDvHeaderPacket &)*packet.get(), buffer);
+ }
+ // check if it's a last frame
+ else if ( packet->IsLastPacket() )
+ {
+ // encode it
+ EncodeNXDNHeaderPacket((CDvHeaderPacket &)*packet.get(), buffer, true);
+ }
+ // otherwise, just a regular DV frame
+ else
+ {
+ // update local stream cache or send triplet when needed
+ uint8_t pid = packet->GetNXDNPacketId();
+ if (pid <= 3)
+ {
+ m_StreamsCache[mod].m_dvFrames[pid] = CDvFramePacket((CDvFramePacket &)*packet.get());
+ if ( pid == 3 )
+ {
+ EncodeNXDNPacket(m_StreamsCache[mod].m_dvHeader, m_StreamsCache[mod].m_iSeqCounter++, m_StreamsCache[mod].m_dvFrames, buffer);
+ }
+ }
+ }
+
+ // send it
+ if ( buffer.size() > 0 )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(EProtocol::nxdn, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetPacketModule()) )
+ {
+ // no, send the packet
+ Send(buffer, client->GetIp());
+
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ m_Queue.Unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CNXDNProtocol::HandleKeepalives(void)
+{
+ // YSF protocol keepalive request is client tasks
+ // here, just check that all clients are still alive
+ // and disconnect them if not
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(EProtocol::nxdn, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // check it's still with us
+ else if ( !client->IsAlive() )
+ {
+ // no, remove it
+ std::cout << "NXDN client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DV packet decoding helpers
+
+bool CNXDNProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ uint8_t tag[] = { 'N','X','D','N','P' };
+
+ bool valid = false;
+
+ if ( (Buffer.size() == 17) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ callsign->SetCallsign(Buffer.data()+5, 8);
+ valid = (callsign->IsValid());
+ }
+ return valid;
+}
+
+bool CNXDNProtocol::IsValidDisconnectPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ uint8_t tag[] = { 'N','X','D','N','U' };
+
+ if ( (Buffer.size() == 14) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ return true;
+ }
+ return false;
+}
+
+bool CNXDNProtocol::IsValidDvHeaderPacket(const CIp &Ip, const CBuffer &Buffer, std::unique_ptr &header)
+{
+ if(!memcmp(Buffer.data(), "NXDND", 5) && (Buffer.size() == 43) && (Buffer.data()[10] == NXDN_LICH_USC_SACCH_NS) && (Buffer.data()[9] == 1) )
+ {
+ auto stream = GetStream(m_uiStreamId, &Ip);
+ if ( !stream )
+ {
+ uint16_t uiSrcId = ((Buffer.data()[5] << 8) & 0xff00) | (Buffer.data()[6] & 0xff);
+ m_uiStreamId = static_cast(::rand());
+ CCallsign csMY = CCallsign("", 0, uiSrcId);
+ CCallsign rpt1 = CCallsign("", 0, uiSrcId);
+ CCallsign rpt2 = m_ReflectorCallsign;
+ rpt1.SetCSModule(NXDN_MODULE_ID);
+ rpt2.SetCSModule(' ');
+ header = std::unique_ptr(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, false));
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CNXDNProtocol::IsValidDvFramePacket(const CIp &Ip, const CBuffer &Buffer, std::unique_ptr &header, std::array, 4> &frames)
+{
+ if(!memcmp(Buffer.data(), "NXDND", 5) && (Buffer.size() == 43) && (Buffer.data()[10] != NXDN_LICH_USC_SACCH_NS) )
+ {
+ auto stream = GetStream(m_uiStreamId, &Ip);
+ if ( !stream )
+ {
+ uint16_t uiSrcId = ((Buffer.data()[5] << 8) & 0xff00) | (Buffer.data()[6] & 0xff);
+ m_uiStreamId = static_cast(::rand());
+ CCallsign csMY = CCallsign("", 0, uiSrcId);
+ CCallsign rpt1 = CCallsign("", 0, uiSrcId);
+ CCallsign rpt2 = m_ReflectorCallsign;
+ rpt1.SetCSModule(NXDN_MODULE_ID);
+ rpt2.SetCSModule(' ');
+ header = std::unique_ptr(new CDvHeaderPacket(csMY, CCallsign("CQCQCQ"), rpt1, rpt2, m_uiStreamId, false));
+
+ if ( g_GateKeeper.MayTransmit(header->GetMyCallsign(), Ip, EProtocol::nxdn, header->GetRpt2Module()) )
+ {
+ OnDvHeaderPacketIn(header, Ip);
+ }
+ }
+
+ // get DV frames
+ uint8_t ambe49[7];
+
+ uint8_t ambe0[9];
+ uint8_t ambe1[9];
+ uint8_t ambe2[9];
+ uint8_t ambe3[9];
+
+ memcpy(ambe49, Buffer.data() + 15, 7);
+ encode(ambe49, ambe0);
+
+ uint8_t t[7];
+ const uint8_t *d = &(Buffer.data()[21]);
+ for(int i = 0; i < 6; ++i){
+ t[i] = d[i] << 1;
+ t[i] |= (1 & (d[i+1] >> 7));
+ }
+ t[6] = d[6] << 1;
+
+ memcpy(ambe49, t, 7);
+ encode(ambe49, ambe1);
+
+ memcpy(ambe49, Buffer.data() + 29, 7);
+ encode(ambe49, ambe2);
+
+ d = &(Buffer.data()[35]);
+ for(int i = 0; i < 6; ++i){
+ t[i] = d[i] << 1;
+ t[i] |= (1 & (d[i+1] >> 7));
+ }
+ t[6] = d[6] << 1;
+
+ memcpy(ambe49, t, 7);
+ encode(ambe49, ambe3);
+
+ // get DV frames
+ frames[0] = std::unique_ptr(new CDvFramePacket(ambe0, m_uiStreamId, 0, false));
+ frames[1] = std::unique_ptr(new CDvFramePacket(ambe1, m_uiStreamId, 1, false));
+ frames[2] = std::unique_ptr(new CDvFramePacket(ambe2, m_uiStreamId, 2, false));
+ frames[3] = std::unique_ptr(new CDvFramePacket(ambe3, m_uiStreamId, 3, false));
+
+ // check validity of packets
+ if ( frames[0] && frames[0]->IsValid() && frames[1] && frames[1]->IsValid() && frames[2] && frames[2]->IsValid() && frames[3] && frames[3]->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CNXDNProtocol::IsValidDvLastFramePacket(const CIp &Ip, const CBuffer &Buffer)
+{
+ if(!memcmp(Buffer.data(), "NXDND", 5) && (Buffer.size() == 43) && (Buffer.data()[10] == NXDN_LICH_USC_SACCH_NS) && ((Buffer.data()[9U] & 0x08) == 0x08) )
+ {
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DV packet encoding helpers
+
+bool CNXDNProtocol::EncodeNXDNHeaderPacket(const CDvHeaderPacket &Header, CBuffer &Buffer, bool islast)
+{
+ Buffer.resize(43);
+ uint16_t NXDNId = Header.GetMyCallsign().GetNXDNid();
+ uint16_t RptrId = Header.GetRpt1Callsign().GetNXDNid();
+ memcpy(Buffer.data(), "NXDND", 5);
+ Buffer.data()[5U] = (NXDNId >> 8) & 0xFFU;
+ Buffer.data()[6U] = (NXDNId >> 0) & 0xFFU;
+ Buffer.data()[7U] = (RptrId >> 8) & 0xFFU;
+ Buffer.data()[8U] = (RptrId >> 0) & 0xFFU;
+ Buffer.data()[9U] = 0x01U;
+
+ const uint8_t idle[3U] = {0x10, 0x00, 0x00};
+ m_lich = 0;
+ memset(m_sacch, 0, 5U);
+ memset(m_layer3, 0, 22U);
+ set_lich_rfct(NXDN_LICH_RFCT_RDCH);
+ set_lich_fct(NXDN_LICH_USC_SACCH_NS);
+ set_lich_option(NXDN_LICH_STEAL_FACCH);
+ set_lich_dir(NXDN_LICH_DIRECTION_INBOUND);
+ Buffer.data()[10U] = get_lich();
+
+ set_sacch_ran(0x01);
+ set_sacch_struct(0); //Single
+ set_sacch_data(idle);
+ get_sacch(&Buffer.data()[11U]);
+ if(islast){
+ set_layer3_msgtype(NXDN_MESSAGE_TYPE_TX_REL);
+ }
+ else{
+ set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL);
+ }
+ set_layer3_srcid(NXDNId);
+ set_layer3_dstid(RptrId);
+ set_layer3_grp(true);
+ set_layer3_blks(0U);
+ memcpy(&Buffer.data()[15U], m_layer3, 14U);
+ memcpy(&Buffer.data()[29U], m_layer3, 14U);
+
+ if (Buffer.data()[10U] == 0x81U || Buffer.data()[10U] == 0x83U) {
+ Buffer.data()[9U] |= Buffer.data()[15U] == 0x01U ? 0x04U : 0x00U;
+ Buffer.data()[9U] |= Buffer.data()[15U] == 0x08U ? 0x08U : 0x00U;
+ }
+ else if ((Buffer.data()[10U] & 0xF0U) == 0x90U) {
+ Buffer.data()[9U] |= 0x02U;
+ if (Buffer.data()[10U] == 0x90U || Buffer.data()[10U] == 0x92U || Buffer.data()[10U] == 0x9CU || Buffer.data()[10U] == 0x9EU) {
+ Buffer.data()[9U] |= Buffer.data()[12U] == 0x09U ? 0x04U : 0x00U;
+ Buffer.data()[9U] |= Buffer.data()[12U] == 0x08U ? 0x08U : 0x00U;
+ }
+ }
+
+ return true;
+}
+
+bool CNXDNProtocol::EncodeNXDNPacket(const CDvHeaderPacket &Header, uint32_t seq, const CDvFramePacket *DvFrames, CBuffer &Buffer)
+{
+ uint8_t ambe[28];
+ Buffer.resize(43);
+ uint16_t NXDNId = Header.GetMyCallsign().GetNXDNid();
+ uint16_t RptrId = Header.GetRpt1Callsign().GetNXDNid();
+
+ memcpy(Buffer.data(), "NXDND", 5);
+ Buffer.data()[5U] = (NXDNId >> 8) & 0xFFU;
+ Buffer.data()[6U] = (NXDNId >> 0) & 0xFFU;
+ Buffer.data()[7U] = (RptrId >> 8) & 0xFFU;
+ Buffer.data()[8U] = (RptrId >> 0) & 0xFFU;
+ Buffer.data()[9U] = 0x01U;
+
+ uint8_t msg[3U];
+ m_lich = 0;
+ memset(m_sacch, 0, 5U);
+ memset(m_layer3, 0, 22U);
+ set_lich_rfct(NXDN_LICH_RFCT_RDCH);
+ set_lich_fct(NXDN_LICH_USC_SACCH_SS);
+ set_lich_option(NXDN_LICH_STEAL_NONE);
+ set_lich_dir(NXDN_LICH_DIRECTION_INBOUND);
+ Buffer.data()[10U] = get_lich();
+
+ set_sacch_ran(0x01);
+
+ set_layer3_msgtype(NXDN_MESSAGE_TYPE_VCALL);
+ set_layer3_srcid(NXDNId);
+ set_layer3_dstid(RptrId);
+ set_layer3_grp(true);
+ set_layer3_blks(0U);
+
+ switch(seq % 4){
+ case 0:
+ set_sacch_struct(3);
+ layer3_encode(msg, 18U, 0U);
+ set_sacch_data(msg);
+ break;
+ case 1:
+ set_sacch_struct(2);
+ layer3_encode(msg, 18U, 18U);
+ set_sacch_data(msg);
+ break;
+ case 2:
+ set_sacch_struct(1);
+ layer3_encode(msg, 18U, 36U);
+ set_sacch_data(msg);
+ break;
+ case 3:
+ set_sacch_struct(0);
+ layer3_encode(msg, 18U, 54U);
+ set_sacch_data(msg);
+ break;
+ }
+ get_sacch(&Buffer.data()[11U]);
+
+ for(int i = 0; i < 4; ++i){
+ decode(DvFrames[i].GetCodecData(ECodecType::dmr), ambe+(i*7));
+ }
+
+ memcpy(&Buffer.data()[15], ambe, 7);
+ for(int i = 0; i < 7; ++i){
+ Buffer.data()[21+i] |= (ambe[7+i] >> 1);
+ Buffer.data()[22+i] = (ambe[7+i] & 1) << 7;
+ }
+ Buffer.data()[28] |= (ambe[13] >> 2);
+
+ memcpy(&Buffer.data()[29], &ambe[14], 7);
+ for(int i = 0; i < 7; ++i){
+ Buffer.data()[35+i] |= (ambe[21+i] >> 1);
+ Buffer.data()[36+i] = (ambe[21+i] & 1) << 7;
+ }
+ Buffer.data()[41] |= (ambe[27] >> 2);
+
+ return true;
+}
+
+void CNXDNProtocol::decode(const unsigned char* in, unsigned char* out) const
+{
+ unsigned int a = 0U;
+ unsigned int MASK = 0x800000U;
+ for (unsigned int i = 0U; i < 24U; i++, MASK >>= 1) {
+ unsigned int aPos = DMR_A_TABLE[i];
+ if (READ_BIT(in, aPos))
+ a |= MASK;
+ }
+
+ unsigned int b = 0U;
+ MASK = 0x400000U;
+ for (unsigned int i = 0U; i < 23U; i++, MASK >>= 1) {
+ unsigned int bPos = DMR_B_TABLE[i];
+ if (READ_BIT(in, bPos))
+ b |= MASK;
+ }
+
+ unsigned int c = 0U;
+ MASK = 0x1000000U;
+ for (unsigned int i = 0U; i < 25U; i++, MASK >>= 1) {
+ unsigned int cPos = DMR_C_TABLE[i];
+ if (READ_BIT(in, cPos))
+ c |= MASK;
+ }
+
+ a >>= 12;
+
+ // The PRNG
+ b ^= (PRNG_TABLE[a] >> 1);
+ b >>= 11;
+
+ MASK = 0x000800U;
+ for (unsigned int i = 0U; i < 12U; i++, MASK >>= 1) {
+ unsigned int aPos = i + 0U;
+ unsigned int bPos = i + 12U;
+ WRITE_BIT(out, aPos, a & MASK);
+ WRITE_BIT(out, bPos, b & MASK);
+ }
+
+ MASK = 0x1000000U;
+ for (unsigned int i = 0U; i < 25U; i++, MASK >>= 1) {
+ unsigned int cPos = i + 24U;
+ WRITE_BIT(out, cPos, c & MASK);
+ }
+}
+
+void CNXDNProtocol::encode(const unsigned char* in, unsigned char* out) const
+{
+
+ unsigned int aOrig = 0U;
+ unsigned int bOrig = 0U;
+ unsigned int cOrig = 0U;
+
+ unsigned int MASK = 0x000800U;
+ for (unsigned int i = 0U; i < 12U; i++, MASK >>= 1) {
+ unsigned int n1 = i + 0U;
+ unsigned int n2 = i + 12U;
+ if (READ_BIT(in, n1))
+ aOrig |= MASK;
+ if (READ_BIT(in, n2))
+ bOrig |= MASK;
+ }
+
+ MASK = 0x1000000U;
+ for (unsigned int i = 0U; i < 25U; i++, MASK >>= 1) {
+ unsigned int n = i + 24U;
+ if (READ_BIT(in, n))
+ cOrig |= MASK;
+ }
+
+ unsigned int a = CGolay24128::encode24128(aOrig);
+
+ // The PRNG
+ unsigned int p = PRNG_TABLE[aOrig] >> 1;
+
+ unsigned int b = CGolay24128::encode23127(bOrig) >> 1;
+ b ^= p;
+
+ MASK = 0x800000U;
+ for (unsigned int i = 0U; i < 24U; i++, MASK >>= 1) {
+ unsigned int aPos = DMR_A_TABLE[i];
+ WRITE_BIT(out, aPos, a & MASK);
+ }
+
+ MASK = 0x400000U;
+ for (unsigned int i = 0U; i < 23U; i++, MASK >>= 1) {
+ unsigned int bPos = DMR_B_TABLE[i];
+ WRITE_BIT(out, bPos, b & MASK);
+ }
+
+ MASK = 0x1000000U;
+ for (unsigned int i = 0U; i < 25U; i++, MASK >>= 1) {
+ unsigned int cPos = DMR_C_TABLE[i];
+ WRITE_BIT(out, cPos, cOrig & MASK);
+ }
+}
+
+uint8_t CNXDNProtocol::get_lich_fct(uint8_t lich)
+{
+ return (lich >> 4) & 0x03U;
+}
+
+void CNXDNProtocol::set_lich_rfct(uint8_t rfct)
+{
+ m_lich &= 0x3FU;
+ m_lich |= (rfct << 6) & 0xC0U;
+}
+
+void CNXDNProtocol::set_lich_fct(uint8_t fct)
+{
+ m_lich &= 0xCFU;
+ m_lich |= (fct << 4) & 0x30U;
+}
+
+void CNXDNProtocol::set_lich_option(uint8_t o)
+{
+ m_lich &= 0xF3U;
+ m_lich |= (o << 2) & 0x0CU;
+}
+
+void CNXDNProtocol::set_lich_dir(uint8_t d)
+{
+ m_lich &= 0xFDU;
+ m_lich |= (d << 1) & 0x02U;
+}
+
+uint8_t CNXDNProtocol::get_lich()
+{
+ bool parity;
+ switch (m_lich & 0xF0U) {
+ case 0x80U:
+ case 0xB0U:
+ parity = true;
+ break;
+ default:
+ parity = false;
+ }
+ if (parity)
+ m_lich |= 0x01U;
+ else
+ m_lich &= 0xFEU;
+
+ return m_lich;
+}
+
+
+void CNXDNProtocol::set_sacch_ran(uint8_t ran)
+{
+ m_sacch[0] &= 0xC0U;
+ m_sacch[0] |= ran;
+}
+
+void CNXDNProtocol::set_sacch_struct(uint8_t s)
+{
+ m_sacch[0] &= 0x3FU;
+ m_sacch[0] |= (s << 6) & 0xC0U;;
+}
+
+void CNXDNProtocol::set_sacch_data(const uint8_t *d)
+{
+ uint8_t offset = 8U;
+ for (uint8_t i = 0U; i < 18U; i++, offset++) {
+ bool b = READ_BIT(d, i);
+ WRITE_BIT(m_sacch, offset, b);
+ }
+}
+
+void CNXDNProtocol::get_sacch(uint8_t *d)
+{
+ memcpy(d, m_sacch, 4U);
+ encode_crc6(d, 26);
+}
+
+void CNXDNProtocol::set_layer3_msgtype(uint8_t t)
+{
+ m_layer3[0] &= 0xC0U;
+ m_layer3[0] |= t & 0x3FU;
+}
+
+void CNXDNProtocol::set_layer3_srcid(uint16_t src)
+{
+ m_layer3[3U] = (src >> 8) & 0xFF;
+ m_layer3[4U] = (src >> 0) & 0xFF ;
+}
+
+void CNXDNProtocol::set_layer3_dstid(uint16_t dst)
+{
+ m_layer3[5U] = (dst >> 8) & 0xFF;
+ m_layer3[6U] = (dst >> 0) & 0xFF ;
+}
+
+void CNXDNProtocol::set_layer3_grp(bool grp)
+{
+ m_layer3[2U] |= grp ? 0x20U : 0x20U;
+}
+
+void CNXDNProtocol::set_layer3_blks(uint8_t b)
+{
+ m_layer3[8U] &= 0xF0U;
+ m_layer3[8U] |= b & 0x0FU;
+}
+
+void CNXDNProtocol::layer3_encode(uint8_t* d, uint8_t len, uint8_t offset)
+{
+ for (uint32_t i = 0U; i < len; i++, offset++) {
+ bool b = READ_BIT(m_layer3, offset);
+ WRITE_BIT(d, i, b);
+ }
+}
+
+void CNXDNProtocol::encode_crc6(uint8_t *d, uint8_t len)
+{
+ uint8_t crc = 0x3FU;
+
+ for (uint32_t i = 0U; i < len; i++) {
+ bool bit1 = READ_BIT(d, i) != 0x00U;
+ bool bit2 = (crc & 0x20U) == 0x20U;
+ crc <<= 1;
+
+ if (bit1 ^ bit2)
+ crc ^= 0x27U;
+ }
+ crc &= 0x3FU;
+ uint8_t n = len;
+ for (uint8_t i = 2U; i < 8U; i++, n++) {
+ bool b = READ_BIT((&crc), i);
+ WRITE_BIT(d, n, b);
+ }
+}
+
diff --git a/reflector/NXDNProtocol.h b/reflector/NXDNProtocol.h
new file mode 100644
index 0000000..50c1542
--- /dev/null
+++ b/reflector/NXDNProtocol.h
@@ -0,0 +1,124 @@
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+
+// urfd -- The universal reflector
+// Copyright © 2021 Thomas A. Early N7TAE
+// Copyright © 2021 Doug McLain AD8DP
+//
+// 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 .
+
+#pragma once
+
+#include "Timer.h"
+#include "Protocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+// NXDN Module ID
+#define NXDN_MODULE_ID 'B'
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CNXDNStreamCacheItem
+{
+public:
+ CNXDNStreamCacheItem() : m_iSeqCounter(0) {}
+
+ CDvHeaderPacket m_dvHeader;
+ CDvFramePacket m_dvFrames[4];
+ uint32_t m_iSeqCounter;
+};
+
+class CNXDNProtocol : public CProtocol
+{
+public:
+ // constructor
+ CNXDNProtocol();
+
+ // initialization
+ bool Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6);
+ void Close(void);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // DV packet decoding helpers
+ bool IsValidConnectPacket(const CBuffer &, CCallsign *);
+ bool IsValidDisconnectPacket(const CBuffer &, CCallsign *);
+ bool IsValidDvHeaderPacket(const CIp &, const CBuffer &, std::unique_ptr &);
+ bool IsValidDvFramePacket(const CIp &, const CBuffer &, std::unique_ptr &, std::array, 4> &);
+ bool IsValidDvLastFramePacket(const CIp &, const CBuffer &);
+
+ // DV packet encoding helpers
+ bool EncodeNXDNHeaderPacket(const CDvHeaderPacket &, CBuffer &, bool islast = false);
+ bool EncodeNXDNPacket(const CDvHeaderPacket &, uint32_t, const CDvFramePacket *, CBuffer &);
+ bool EncodeLastNXDNPacket(const CDvHeaderPacket &, CBuffer &) const;
+
+ // uiStreamId helpers
+ uint32_t IpToStreamId(const CIp &) const;
+
+ void encode(const unsigned char*, unsigned char*) const;
+ void decode(const unsigned char*, unsigned char*) const;
+
+ uint8_t get_lich_fct(uint8_t);
+ void set_lich_rfct(uint8_t);
+ void set_lich_fct(uint8_t);
+ void set_lich_option(uint8_t);
+ void set_lich_dir(uint8_t);
+ void set_sacch_ran(uint8_t);
+ void set_sacch_struct(uint8_t);
+ void set_sacch_data(const uint8_t *);
+ void set_layer3_msgtype(uint8_t);
+ void set_layer3_srcid(uint16_t);
+ void set_layer3_dstid(uint16_t);
+ void set_layer3_grp(bool);
+ void set_layer3_blks(uint8_t);
+ void layer3_encode(uint8_t*, uint8_t, uint8_t);
+
+ uint8_t get_lich();
+ void get_sacch(uint8_t *);
+ void encode_crc6(uint8_t *, uint8_t);
+
+ // debug
+ bool DebugTestDecodePacket(const CBuffer &);
+ bool DebugDumpHeaderPacket(const CBuffer &);
+ bool DebugDumpDvPacket(const CBuffer &);
+ bool DebugDumpLastDvPacket(const CBuffer &);
+
+protected:
+ // for keep alive
+ CTimer m_LastKeepaliveTime;
+
+ // for queue header caches
+ std::unordered_map m_StreamsCache;
+
+ unsigned char m_seqNo;
+ uint32_t m_uiStreamId;
+ uint8_t m_lich;
+ uint8_t m_sacch[5];
+ uint8_t m_layer3[22];
+};