diff --git a/NXDNGateway/APRSWriter.cpp b/NXDNGateway/APRSWriter.cpp index 4c88902..e5eb58e 100644 --- a/NXDNGateway/APRSWriter.cpp +++ b/NXDNGateway/APRSWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2010-2014,2016,2017,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -112,6 +112,8 @@ void CAPRSWriter::clock(unsigned int ms) { m_idTimer.clock(ms); + m_thread->clock(ms); + if (m_socket != NULL) { if (m_idTimer.hasExpired()) { pollGPS(); diff --git a/NXDNGateway/APRSWriterThread.cpp b/NXDNGateway/APRSWriterThread.cpp index 93e2e48..c388609 100644 --- a/NXDNGateway/APRSWriterThread.cpp +++ b/NXDNGateway/APRSWriterThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2010-2014,2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -41,6 +41,8 @@ m_socket(address, port), m_queue(20U, "APRS Queue"), m_exit(false), m_connected(false), +m_reconnectTimer(1000U), +m_tries(1U), m_APRSReadCallback(NULL), m_filter(), m_clientName("YSFGateway") @@ -63,6 +65,8 @@ m_socket(address, port), m_queue(20U, "APRS Queue"), m_exit(false), m_connected(false), +m_reconnectTimer(1000U), +m_tries(1U), m_APRSReadCallback(NULL), m_filter(filter), m_clientName(clientName) @@ -94,19 +98,28 @@ void CAPRSWriterThread::entry() LogMessage("Starting the APRS Writer thread"); m_connected = connect(); + if (!m_connected) { + LogError("Connect attempt to the APRS server has failed"); + startReconnectionTimer(); + } try { while (!m_exit) { if (!m_connected) { - m_connected = connect(); + if (m_reconnectTimer.isRunning() && m_reconnectTimer.hasExpired()) { + m_reconnectTimer.stop(); - if (!m_connected){ - LogError("Reconnect attempt to the APRS server has failed"); - sleep(10000UL); // 10 secs + m_connected = connect(); + if (!m_connected) { + LogError("Reconnect attempt to the APRS server has failed"); + startReconnectionTimer(); + } } } if (m_connected) { + m_tries = 0U; + if (!m_queue.isEmpty()){ char* p = NULL; m_queue.getData(&p, 1U); @@ -115,11 +128,12 @@ void CAPRSWriterThread::entry() ::strcat(p, "\r\n"); - bool ret = m_socket.write((unsigned char*)p, ::strlen(p)); + bool ret = m_socket.write((unsigned char*)p, (unsigned int)::strlen(p)); if (!ret) { m_connected = false; m_socket.close(); LogError("Connection to the APRS thread has failed"); + startReconnectionTimer(); } delete[] p; @@ -132,6 +146,7 @@ void CAPRSWriterThread::entry() m_connected = false; m_socket.close(); LogError("Error when reading from the APRS server"); + startReconnectionTimer(); } if(length > 0 && line.at(0U) != '#'//check if we have something and if that something is an APRS frame @@ -176,7 +191,7 @@ void CAPRSWriterThread::write(const char* data) if (!m_connected) return; - unsigned int len = ::strlen(data); + unsigned int len = (unsigned int)::strlen(data); char* p = new char[len + 5U]; ::strcpy(p, data); @@ -196,6 +211,11 @@ void CAPRSWriterThread::stop() wait(); } +void CAPRSWriterThread::clock(unsigned int ms) +{ + m_reconnectTimer.clock(ms); +} + bool CAPRSWriterThread::connect() { bool ret = m_socket.open(); @@ -246,3 +266,14 @@ bool CAPRSWriterThread::connect() return true; } + +void CAPRSWriterThread::startReconnectionTimer() +{ + // Clamp at a ten minutes reconnect time + m_tries++; + if (m_tries > 10U) + m_tries = 10U; + + m_reconnectTimer.setTimeout(m_tries * 60U); + m_reconnectTimer.start(); +} diff --git a/NXDNGateway/APRSWriterThread.h b/NXDNGateway/APRSWriterThread.h index 42bfd67..c86c23a 100644 --- a/NXDNGateway/APRSWriterThread.h +++ b/NXDNGateway/APRSWriterThread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010,2011,2012,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2010,2011,2012,2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -21,6 +21,7 @@ #include "TCPSocket.h" #include "RingBuffer.h" +#include "Timer.h" #include "Thread.h" #include @@ -45,6 +46,8 @@ public: void setReadAPRSCallback(ReadAPRSFrameCallback cb); + void clock(unsigned int ms); + private: std::string m_username; std::string m_password; @@ -52,11 +55,14 @@ private: CRingBuffer m_queue; bool m_exit; bool m_connected; + CTimer m_reconnectTimer; + unsigned int m_tries; ReadAPRSFrameCallback m_APRSReadCallback; std::string m_filter; std::string m_clientName; bool connect(); + void startReconnectionTimer(); }; #endif diff --git a/NXDNGateway/Conf.cpp b/NXDNGateway/Conf.cpp index 92cba3c..446be3d 100644 --- a/NXDNGateway/Conf.cpp +++ b/NXDNGateway/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -35,13 +35,15 @@ enum SECTION { SECTION_LOG, SECTION_APRS_FI, SECTION_NETWORK, - SECTION_MOBILE_GPS + SECTION_MOBILE_GPS, + SECTION_REMOTE_COMMANDS }; CConf::CConf(const std::string& file) : m_file(file), m_callsign(), m_suffix(), +m_rptProtocol("Icom"), m_rptAddress(), m_rptPort(0U), m_myPort(0U), @@ -81,7 +83,9 @@ m_networkInactivityTimeout(0U), m_networkDebug(false), m_mobileGPSEnabled(false), m_mobileGPSAddress(), -m_mobileGPSPort(0U) +m_mobileGPSPort(0U), +m_remoteCommandsEnabled(false), +m_remoteCommandsPort(6075U) { } @@ -121,6 +125,8 @@ bool CConf::read() section = SECTION_NETWORK; else if (::strncmp(buffer, "[Mobile GPS]", 12U) == 0) section = SECTION_MOBILE_GPS; + else if (::strncmp(buffer, "[Remote Commands]", 17U) == 0) + section = SECTION_REMOTE_COMMANDS; else section = SECTION_NONE; @@ -143,7 +149,9 @@ bool CConf::read() for (unsigned int i = 0U; value[i] != 0; i++) value[i] = ::toupper(value[i]); m_suffix = value; - } else if (::strcmp(key, "RptAddress") == 0) + } else if (::strcmp(key, "RptProtocol") == 0) + m_rptProtocol = value; + else if (::strcmp(key, "RptAddress") == 0) m_rptAddress = value; else if (::strcmp(key, "RptPort") == 0) m_rptPort = (unsigned int)::atoi(value); @@ -230,6 +238,11 @@ bool CConf::read() m_mobileGPSAddress = value; else if (::strcmp(key, "Port") == 0) m_mobileGPSPort = (unsigned int)::atoi(value); + } else if (section == SECTION_REMOTE_COMMANDS) { + if (::strcmp(key, "Enable") == 0) + m_remoteCommandsEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Port") == 0) + m_remoteCommandsPort = (unsigned int)::atoi(value); } } @@ -248,6 +261,11 @@ std::string CConf::getSuffix() const return m_suffix; } +std::string CConf::getRptProtocol() const +{ + return m_rptProtocol; +} + std::string CConf::getRptAddress() const { return m_rptAddress; @@ -448,3 +466,12 @@ unsigned int CConf::getMobileGPSPort() const return m_mobileGPSPort; } +bool CConf::getRemoteCommandsEnabled() const +{ + return m_remoteCommandsEnabled; +} + +unsigned int CConf::getRemoteCommandsPort() const +{ + return m_remoteCommandsPort; +} diff --git a/NXDNGateway/Conf.h b/NXDNGateway/Conf.h index 29dce87..439727e 100644 --- a/NXDNGateway/Conf.h +++ b/NXDNGateway/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -33,6 +33,7 @@ public: // The General section std::string getCallsign() const; std::string getSuffix() const; + std::string getRptProtocol() const; std::string getRptAddress() const; unsigned int getRptPort() const; unsigned int getMyPort() const; @@ -84,14 +85,19 @@ public: bool getNetworkDebug() const; // The Mobile GPS section - bool getMobileGPSEnabled() const; - std::string getMobileGPSAddress() const; - unsigned int getMobileGPSPort() const; + bool getMobileGPSEnabled() const; + std::string getMobileGPSAddress() const; + unsigned int getMobileGPSPort() const; + + // The Remote Commands section + bool getRemoteCommandsEnabled() const; + unsigned int getRemoteCommandsPort() const; private: std::string m_file; std::string m_callsign; std::string m_suffix; + std::string m_rptProtocol; std::string m_rptAddress; unsigned int m_rptPort; unsigned int m_myPort; @@ -139,6 +145,9 @@ private: bool m_mobileGPSEnabled; std::string m_mobileGPSAddress; unsigned int m_mobileGPSPort; + + bool m_remoteCommandsEnabled; + unsigned int m_remoteCommandsPort; }; #endif diff --git a/NXDNGateway/GPSHandler.cpp b/NXDNGateway/GPSHandler.cpp index 696f5ff..e0a511f 100644 --- a/NXDNGateway/GPSHandler.cpp +++ b/NXDNGateway/GPSHandler.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2018 by Jonathan Naylor G4KLX +* Copyright (C) 2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -63,8 +63,12 @@ void CGPSHandler::processData(const unsigned char* data) ::memcpy(m_data + m_length, data + 1U, NXDN_DATA_LENGTH); m_length += NXDN_DATA_LENGTH; + // The packet frame number and block number are zero to indicate the last section of the + // data transmission. if (data[0U] == 0x00U) { - processNMEA(); + bool ret = processIcom(); + if (!ret) + processKenwood(); reset(); } } @@ -81,23 +85,25 @@ void CGPSHandler::reset() m_source.clear(); } -void CGPSHandler::processNMEA() +bool CGPSHandler::processIcom() { if (m_data[0U] != NXDN_DATA_TYPE_GPS) - return; + return false; if (::memcmp(m_data + 1U, "$G", 2U) != 0) - return; + return false; if (::strchr((char*)(m_data + 1U), '*') == NULL) - return; + return false; + + // From here onwards we have something that looks like Icom GPS data if (!checkXOR()) - return; + return true; if (::memcmp(m_data + 4U, "RMC", 3U) != 0) { CUtils::dump("Unhandled NMEA sentence", (unsigned char*)(m_data + 1U), m_length - 1U); - return; + return true; } // Parse the $GxRMC string into tokens @@ -114,11 +120,11 @@ void CGPSHandler::processNMEA() // Is there any position data? if (pRMC[3U] == NULL || pRMC[4U] == NULL || pRMC[5U] == NULL || pRMC[6U] == NULL || ::strlen(pRMC[3U]) == 0U || ::strlen(pRMC[4U]) == 0U || ::strlen(pRMC[5U]) == 0 || ::strlen(pRMC[6U]) == 0) - return; + return true; // Is it a valid GPS fix? if (::strcmp(pRMC[2U], "A") != 0) - return; + return true; double latitude = ::atof(pRMC[3U]); double longitude = ::atof(pRMC[5U]); @@ -134,14 +140,16 @@ void CGPSHandler::processNMEA() int bearing = ::atoi(pRMC[8U]); int speed = ::atoi(pRMC[7U]); - ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr%03d/%03d via MMDVM", + ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr%03d/%03d Icom IDAS via MMDVM", source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U], bearing, speed); } else { - ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr via MMDVM", + ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr Icom IDAS via MMDVM", source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U]); } m_writer->write(output); + + return true; } bool CGPSHandler::checkXOR() const @@ -158,3 +166,87 @@ bool CGPSHandler::checkXOR() const return ::memcmp(buffer, p2 + 1U, 2U) == 0; } + +bool CGPSHandler::processKenwood() +{ + enum { + GPS_FULL, + GPS_SHORT, + GPS_VERY_SHORT + } type; + + switch (m_data[0U]) { + case 0x00U: + if (m_length < 38U) + return false; + type = GPS_FULL; + break; + case 0x01U: + if (m_length < 17U) + return false; + type = GPS_SHORT; + break; + case 0x02U: + if (m_length < 15U) + return false; + type = GPS_VERY_SHORT; + break; + default: + return true; + } + + unsigned char north = 'N'; + unsigned int latAfter = 0x7FFFU; + unsigned int latBefore = 0xFFFFU; + unsigned char east = 'E'; + unsigned int lonAfter = 0x7FFFU; + unsigned int lonBefore = 0xFFFFU; + if (type == GPS_VERY_SHORT) { + north = (m_data[7U] & 0x01U) == 0x00U ? 'N' : 'S'; + latAfter = ((m_data[7U] & 0xFEU) >> 1) | (m_data[8U] << 7); + latBefore = (m_data[10U] << 8) | m_data[9U]; + + east = (m_data[11U] & 0x01U) == 0x00U ? 'E' : 'W'; + lonAfter = ((m_data[11U] & 0xFEU) >> 1) | (m_data[12U] << 7); + lonBefore = (m_data[13U] << 8) | m_data[13U]; + } else { + north = (m_data[9U] & 0x01U) == 0x00U ? 'N' : 'S'; + latAfter = ((m_data[9U] & 0xFEU) >> 1) | (m_data[10U] << 7); + latBefore = (m_data[12U] << 8) | m_data[11U]; + + east = (m_data[13U] & 0x01U) == 0x00U ? 'E' : 'W'; + lonAfter = ((m_data[13U] & 0xFEU) >> 1) | (m_data[14U] << 7); + lonBefore = (m_data[16U] << 8) | m_data[15U]; + } + + if (latAfter == 0x7FFFU || latBefore == 0xFFFFU || lonAfter == 0x7FFFU || lonBefore == 0xFFFFU) + return true; + + unsigned int course = 0xFFFFU; + unsigned int speedBefore = 0x3FFU; + unsigned int speedAfter = 0x0FU; + if (type == GPS_FULL) { + course = (m_data[23U] << 4) | (m_data[24U] & 0x0FU); + speedBefore = ((m_data[25U] & 0xF0U) << 2) | (m_data[26U] & 0x3FU); + speedAfter = m_data[25U] & 0x0FU; + } + + std::string source = m_source; + if (!m_suffix.empty()) { + source.append("-"); + source.append(m_suffix.substr(0U, 1U)); + } + + char output[300U]; + if (course != 0xFFFFU && speedBefore != 0x3FFU && speedAfter != 0x0FU) { + ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr%03d/%03d Kenwood NEXEDGE via MMDVM", + source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east, course / 10U, speedBefore); + } else { + ::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr Kenwood NEXEDGE via MMDVM", + source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east); + } + + m_writer->write(output); + + return true; +} diff --git a/NXDNGateway/GPSHandler.h b/NXDNGateway/GPSHandler.h index 44cdb34..1a03eac 100644 --- a/NXDNGateway/GPSHandler.h +++ b/NXDNGateway/GPSHandler.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2018 by Jonathan Naylor G4KLX +* Copyright (C) 2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -42,7 +42,8 @@ private: std::string m_source; std::string m_suffix; - void processNMEA(); + bool processIcom(); + bool processKenwood(); bool checkXOR() const; void reset(); }; diff --git a/NXDNGateway/IcomNetwork.cpp b/NXDNGateway/IcomNetwork.cpp index 655acb9..6cbd9b2 100644 --- a/NXDNGateway/IcomNetwork.cpp +++ b/NXDNGateway/IcomNetwork.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -26,10 +26,17 @@ const unsigned int BUFFER_LENGTH = 200U; -CIcomNetwork::CIcomNetwork(unsigned int localPort, bool debug) : +CIcomNetwork::CIcomNetwork(unsigned int localPort, const std::string& rptAddress, unsigned int rptPort, bool debug) : m_socket(localPort), +m_address(), +m_port(rptPort), m_debug(debug) { + assert(localPort > 0U); + assert(!rptAddress.empty()); + assert(rptPort > 0U); + + m_address = CUDPSocket::lookup(rptAddress); } CIcomNetwork::~CIcomNetwork() @@ -46,7 +53,7 @@ bool CIcomNetwork::open() return m_socket.open(); } -bool CIcomNetwork::write(const unsigned char* data, unsigned int length, const in_addr& address, unsigned int port) +bool CIcomNetwork::write(const unsigned char* data, unsigned int length) { assert(data != NULL); @@ -77,22 +84,29 @@ bool CIcomNetwork::write(const unsigned char* data, unsigned int length, const i if (m_debug) CUtils::dump(1U, "Icom Data Sent", buffer, 102U); - return m_socket.write(buffer, 102U, address, port); + return m_socket.write(buffer, 102U, m_address, m_port); } -bool CIcomNetwork::read(unsigned char* data, in_addr& address, unsigned int& port) +unsigned int CIcomNetwork::read(unsigned char* data) { assert(data != NULL); unsigned char buffer[BUFFER_LENGTH]; + in_addr address; + unsigned int port; int length = m_socket.read(buffer, BUFFER_LENGTH, address, port); if (length <= 0) - return false; + return 0U; + + if (m_address.s_addr != address.s_addr || m_port != port) { + LogWarning("Icom Data received from an unknown address or port - %08X:%u", ntohl(address.s_addr), port); + return 0U; + } // Invalid packet type? if (::memcmp(buffer, "ICOM", 4U) != 0) - return false; + return 0U; // An Icom repeater connect request if (buffer[4U] == 0x01U && buffer[5U] == 0x61U) { @@ -101,18 +115,18 @@ bool CIcomNetwork::read(unsigned char* data, in_addr& address, unsigned int& por buffer[38U] = 0x4FU; buffer[39U] = 0x4BU; m_socket.write(buffer, length, address, port); - return false; + return 0U; } if (length != 102) - return false; + return 0U; if (m_debug) CUtils::dump(1U, "Icom Data Received", buffer, length); ::memcpy(data, buffer + 40U, 33U); - return true; + return 33U; } void CIcomNetwork::close() @@ -121,3 +135,7 @@ void CIcomNetwork::close() LogMessage("Closing Icom connection"); } + +void CIcomNetwork::clock(unsigned int ms) +{ +} diff --git a/NXDNGateway/IcomNetwork.h b/NXDNGateway/IcomNetwork.h index 38923d1..e2aec93 100644 --- a/NXDNGateway/IcomNetwork.h +++ b/NXDNGateway/IcomNetwork.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -19,24 +19,26 @@ #ifndef IcomNetwork_H #define IcomNetwork_H +#include "RptNetwork.h" #include "UDPSocket.h" -#include "Timer.h" #include #include -class CIcomNetwork { +class CIcomNetwork : public IRptNetwork { public: - CIcomNetwork(unsigned int localPort, bool debug); - ~CIcomNetwork(); + CIcomNetwork(unsigned int localPort, const std::string& rptAddress, unsigned int rptPort, bool debug); + virtual ~CIcomNetwork(); - bool open(); + virtual bool open(); - bool write(const unsigned char* data, unsigned int length, const in_addr& address, unsigned int port); + virtual bool write(const unsigned char* data, unsigned int length); - bool read(unsigned char* data, in_addr& address, unsigned int& port); + virtual unsigned int read(unsigned char* data); - void close(); + virtual void close(); + + virtual void clock(unsigned int ms); private: CUDPSocket m_socket; diff --git a/NXDNGateway/KenwoodNetwork.cpp b/NXDNGateway/KenwoodNetwork.cpp new file mode 100644 index 0000000..c5a4a20 --- /dev/null +++ b/NXDNGateway/KenwoodNetwork.cpp @@ -0,0 +1,924 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "KenwoodNetwork.h" +#include "NXDNCRC.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include +#include + +const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +const unsigned int BUFFER_LENGTH = 200U; + +CKenwoodNetwork::CKenwoodNetwork(unsigned int localPort, const std::string& rptAddress, unsigned int rptPort, bool debug) : +m_rtpSocket(localPort + 0U), +m_rtcpSocket(localPort + 1U), +m_address(), +m_rtcpPort(rptPort + 1U), +m_rtpPort(rptPort + 0U), +m_headerSeen(false), +m_seen1(false), +m_seen2(false), +m_seen3(false), +m_seen4(false), +m_sacch(NULL), +m_sessionId(1U), +m_seqNo(0U), +m_ssrc(0U), +m_debug(debug), +m_startSecs(0U), +m_startUSecs(0U), +m_rtcpTimer(1000U, 0U, 200U), +m_hangTimer(1000U, 5U), +m_hangType(0U), +m_hangSrc(0U), +m_hangDst(0U) +{ + assert(localPort > 0U); + assert(!rptAddress.empty()); + assert(rptPort > 0U); + + m_sacch = new unsigned char[10U]; + + m_address = CUDPSocket::lookup(rptAddress); +} + +CKenwoodNetwork::~CKenwoodNetwork() +{ + delete[] m_sacch; +} + +bool CKenwoodNetwork::open() +{ + LogMessage("Opening Kenwood connection"); + + if (m_address.s_addr == INADDR_NONE) + return false; + + if (!m_rtcpSocket.open()) + return false; + + if (!m_rtpSocket.open()) { + m_rtcpSocket.close(); + return false; + } + + m_ssrc = m_rtpSocket.getLocalAddress(); + + return true; +} + +bool CKenwoodNetwork::write(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + + switch (data[0U]) { + case 0x81U: // Voice header or trailer + case 0x83U: + return processIcomVoiceHeader(data); + case 0xACU: // Voice data + case 0xAEU: + return processIcomVoiceData(data); + default: + return false; + } +} + +bool CKenwoodNetwork::processIcomVoiceHeader(const unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[30U]; + ::memset(outData, 0x00U, 30U); + + // SACCH + outData[0U] = inData[2U]; + outData[1U] = inData[1U]; + outData[2U] = inData[4U] & 0xC0U; + outData[3U] = inData[3U]; + + // FACCH 1+2 + outData[4U] = outData[14U] = inData[6U]; + outData[5U] = outData[15U] = inData[5U]; + outData[6U] = outData[16U] = inData[8U]; + outData[7U] = outData[17U] = inData[7U]; + outData[8U] = outData[18U] = inData[10U]; + outData[9U] = outData[19U] = inData[9U]; + outData[10U] = outData[20U] = inData[12U]; + outData[11U] = outData[21U] = inData[11U]; + + unsigned short src = (inData[8U] << 8) + (inData[9U] << 0); + unsigned short dst = (inData[10U] << 8) + (inData[11U] << 0); + unsigned char type = (inData[7U] >> 5) & 0x07U; + + switch (inData[5U] & 0x3FU) { + case 0x01U: + m_hangTimer.stop(); + m_rtcpTimer.start(); + writeRTCPStart(); + return writeRTPVoiceHeader(outData); + case 0x08U: { + m_hangTimer.start(); + bool ret = writeRTPVoiceTrailer(outData); + writeRTCPHang(type, src, dst); + return ret; + } + default: + return false; + } +} + +bool CKenwoodNetwork::processIcomVoiceData(const unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[40U], temp[10U]; + ::memset(outData, 0x00U, 40U); + + // SACCH + outData[0U] = inData[2U]; + outData[1U] = inData[1U]; + outData[2U] = inData[4U] & 0xC0U; + outData[3U] = inData[3U]; + + // Audio 1 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (5U * 8U) + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[4U] = temp[1U]; + outData[5U] = temp[0U]; + outData[6U] = temp[3U]; + outData[7U] = temp[2U]; + outData[8U] = temp[5U]; + outData[9U] = temp[4U]; + outData[10U] = temp[7U]; + outData[11U] = temp[6U]; + + // Audio 2 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (5U * 8U) + 49U + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[12U] = temp[1U]; + outData[13U] = temp[0U]; + outData[14U] = temp[3U]; + outData[15U] = temp[2U]; + outData[16U] = temp[5U]; + outData[17U] = temp[4U]; + outData[18U] = temp[7U]; + outData[19U] = temp[6U]; + + // Audio 3 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (19U * 8U) + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[20U] = temp[1U]; + outData[21U] = temp[0U]; + outData[22U] = temp[3U]; + outData[23U] = temp[2U]; + outData[24U] = temp[5U]; + outData[25U] = temp[4U]; + outData[26U] = temp[7U]; + outData[27U] = temp[6U]; + + // Audio 4 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (19U * 8U) + 49U + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[28U] = temp[1U]; + outData[29U] = temp[0U]; + outData[30U] = temp[3U]; + outData[31U] = temp[2U]; + outData[32U] = temp[5U]; + outData[33U] = temp[4U]; + outData[34U] = temp[7U]; + outData[35U] = temp[6U]; + + return writeRTPVoiceData(outData); +} + +bool CKenwoodNetwork::writeRTPVoiceHeader(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[50U]; + ::memset(buffer, 0x00U, 50U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + m_sessionId++; + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x03U; + buffer[18U] = 0x04U; + buffer[19U] = 0x04U; + buffer[20U] = 0x0AU; + buffer[21U] = 0x05U; + buffer[22U] = 0x0AU; + + ::memcpy(buffer + 23U, data, 24U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 47U); + + return m_rtpSocket.write(buffer, 47U, m_address, m_rtpPort); +} + +bool CKenwoodNetwork::writeRTPVoiceTrailer(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[50U]; + ::memset(buffer, 0x00U, 50U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x03U; + buffer[18U] = 0x04U; + buffer[19U] = 0x04U; + buffer[20U] = 0x0AU; + buffer[21U] = 0x05U; + buffer[22U] = 0x0AU; + + ::memcpy(buffer + 23U, data, 24U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 47U); + + return m_rtpSocket.write(buffer, 47U, m_address, m_rtpPort); +} + +bool CKenwoodNetwork::writeRTPVoiceData(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[60U]; + ::memset(buffer, 0x00U, 60U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x02U; + buffer[18U] = 0x04U; + buffer[19U] = 0x07U; + buffer[20U] = 0x10U; + buffer[21U] = 0x08U; + buffer[22U] = 0x10U; + + ::memcpy(buffer + 23U, data, 36U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 59U); + + return m_rtpSocket.write(buffer, 59U, m_address, m_rtpPort); +} + +bool CKenwoodNetwork::writeRTCPStart() +{ +#if defined(_WIN32) || defined(_WIN64) + time_t now; + ::time(&now); + + m_startSecs = uint32_t(now); + + SYSTEMTIME st; + ::GetSystemTime(&st); + + m_startUSecs = st.wMilliseconds * 1000U; +#else + struct timeval tod; + ::gettimeofday(&tod, NULL); + + m_startSecs = tod.tv_sec; + m_startUSecs = tod.tv_usec; +#endif + + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8AU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x06U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_startSecs >> 24) & 0xFFU; + buffer[13U] = (m_startSecs >> 16) & 0xFFU; + buffer[14U] = (m_startSecs >> 8) & 0xFFU; + buffer[15U] = (m_startSecs >> 0) & 0xFFU; + + buffer[16U] = (m_startUSecs >> 24) & 0xFFU; + buffer[17U] = (m_startUSecs >> 16) & 0xFFU; + buffer[18U] = (m_startUSecs >> 8) & 0xFFU; + buffer[19U] = (m_startUSecs >> 0) & 0xFFU; + + buffer[22U] = 0x02U; + + buffer[24U] = 0x01U; + + buffer[27U] = 0x0AU; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U); + + return m_rtcpSocket.write(buffer, 28U, m_address, m_rtcpPort); +} + +bool CKenwoodNetwork::writeRTCPPing() +{ + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8AU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x06U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_startSecs >> 24) & 0xFFU; + buffer[13U] = (m_startSecs >> 16) & 0xFFU; + buffer[14U] = (m_startSecs >> 8) & 0xFFU; + buffer[15U] = (m_startSecs >> 0) & 0xFFU; + + buffer[16U] = (m_startUSecs >> 24) & 0xFFU; + buffer[17U] = (m_startUSecs >> 16) & 0xFFU; + buffer[18U] = (m_startUSecs >> 8) & 0xFFU; + buffer[19U] = (m_startUSecs >> 0) & 0xFFU; + + buffer[22U] = 0x02U; + + buffer[24U] = 0x01U; + + buffer[27U] = 0x7BU; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U); + + return m_rtcpSocket.write(buffer, 28U, m_address, m_rtcpPort); +} + +bool CKenwoodNetwork::writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst) +{ + m_hangType = type; + m_hangSrc = src; + m_hangDst = dst; + + return writeRTCPHang(); +} + +bool CKenwoodNetwork::writeRTCPHang() +{ + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8BU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x04U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_hangSrc >> 8) & 0xFFU; + buffer[13U] = (m_hangSrc >> 0) & 0xFFU; + + buffer[14U] = (m_hangDst >> 8) & 0xFFU; + buffer[15U] = (m_hangDst >> 0) & 0xFFU; + + buffer[16U] = m_hangType; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 20U); + + return m_rtcpSocket.write(buffer, 20U, m_address, m_rtcpPort); +} + +unsigned int CKenwoodNetwork::read(unsigned char* data) +{ + assert(data != NULL); + + unsigned char dummy[BUFFER_LENGTH]; + readRTCP(dummy); + + unsigned int len = readRTP(data); + switch (len) { + case 0U: // Nothing received + return 0U; + case 35U: // Voice header or trailer + return processKenwoodVoiceHeader(data); + case 47U: // Voice data + if (m_headerSeen) + return processKenwoodVoiceData(data); + else + return processKenwoodVoiceLateEntry(data); + case 31U: // Data + return processKenwoodData(data); + default: + CUtils::dump(5U, "Unknown data received from the Kenwood network", data, len); + return 0U; + } +} + +unsigned int CKenwoodNetwork::readRTP(unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[BUFFER_LENGTH]; + + in_addr address; + unsigned int port; + int length = m_rtpSocket.read(buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return 0U; + + // Check if the data is for us + if (m_address.s_addr != address.s_addr) { + LogMessage("Kenwood RTP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr); + return 0U; + } + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Received", buffer, length); + + ::memcpy(data, buffer + 12U, length - 12U); + + return length - 12U; +} + +unsigned int CKenwoodNetwork::readRTCP(unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[BUFFER_LENGTH]; + + in_addr address; + unsigned int port; + int length = m_rtcpSocket.read(buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return 0U; + + // Check if the data is for us + if (m_address.s_addr != address.s_addr) { + LogMessage("Kenwood RTCP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr); + return 0U; + } + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Received", buffer, length); + + if (::memcmp(buffer + 8U, "KWNE", 4U) != 0) { + LogError("Missing RTCP KWNE signature"); + return 0U; + } + + ::memcpy(data, buffer + 12U, length - 12U); + + return length - 12U; +} + +void CKenwoodNetwork::close() +{ + m_rtcpSocket.close(); + m_rtpSocket.close(); + + LogMessage("Closing Kenwood connection"); +} + +void CKenwoodNetwork::clock(unsigned int ms) +{ + m_rtcpTimer.clock(ms); + if (m_rtcpTimer.isRunning() && m_rtcpTimer.hasExpired()) { + if (m_hangTimer.isRunning()) + writeRTCPHang(); + else + writeRTCPPing(); + m_rtcpTimer.start(); + } + + m_hangTimer.clock(ms); + if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) { + m_rtcpTimer.stop(); + m_hangTimer.stop(); + } +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceHeader(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[50U], temp[20U]; + ::memset(outData, 0x00U, 50U); + + // LICH + outData[0U] = 0x83U; + + // SACCH + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[12U]; + temp[1U] = inData[11U]; + temp[2U] = inData[14U]; + temp[3U] = inData[13U]; + CNXDNCRC::encodeCRC6(temp, 26U); + ::memcpy(outData + 1U, temp, 4U); + + // FACCH 1+2 + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[16U]; + temp[1U] = inData[15U]; + temp[2U] = inData[18U]; + temp[3U] = inData[17U]; + temp[4U] = inData[20U]; + temp[5U] = inData[19U]; + temp[6U] = inData[22U]; + temp[7U] = inData[21U]; + temp[8U] = inData[24U]; + temp[9U] = inData[23U]; + CNXDNCRC::encodeCRC12(temp, 80U); + ::memcpy(outData + 5U, temp, 12U); + ::memcpy(outData + 19U, temp, 12U); + + switch (outData[5U] & 0x3FU) { + case 0x01U: + ::memcpy(inData, outData, 33U); + m_headerSeen = true; + m_seen1 = false; + m_seen2 = false; + m_seen3 = false; + m_seen4 = false; + return 33U; + case 0x08U: + ::memcpy(inData, outData, 33U); + m_headerSeen = false; + m_seen1 = false; + m_seen2 = false; + m_seen3 = false; + m_seen4 = false; + return 33U; + default: + return 0U; + } +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceData(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[50U], temp[20U]; + ::memset(outData, 0x00U, 50U); + + // LICH + outData[0U] = 0xAEU; + + // SACCH + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[12U]; + temp[1U] = inData[11U]; + temp[2U] = inData[14U]; + temp[3U] = inData[13U]; + CNXDNCRC::encodeCRC6(temp, 26U); + ::memcpy(outData + 1U, temp, 4U); + + // AMBE 1+2 + unsigned int n = 5U * 8U; + + temp[0U] = inData[16U]; + temp[1U] = inData[15U]; + temp[2U] = inData[18U]; + temp[3U] = inData[17U]; + temp[4U] = inData[20U]; + temp[5U] = inData[19U]; + temp[6U] = inData[22U]; + temp[7U] = inData[21U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + temp[0U] = inData[24U]; + temp[1U] = inData[23U]; + temp[2U] = inData[26U]; + temp[3U] = inData[25U]; + temp[4U] = inData[28U]; + temp[5U] = inData[27U]; + temp[6U] = inData[30U]; + temp[7U] = inData[29U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + // AMBE 3+4 + n = 19U * 8U; + + temp[0U] = inData[32U]; + temp[1U] = inData[31U]; + temp[2U] = inData[34U]; + temp[3U] = inData[33U]; + temp[4U] = inData[36U]; + temp[5U] = inData[35U]; + temp[6U] = inData[38U]; + temp[7U] = inData[37U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + temp[0U] = inData[40U]; + temp[1U] = inData[39U]; + temp[2U] = inData[42U]; + temp[3U] = inData[41U]; + temp[4U] = inData[44U]; + temp[5U] = inData[43U]; + temp[6U] = inData[46U]; + temp[7U] = inData[45U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + ::memcpy(inData, outData, 33U); + + return 33U; +} + +unsigned int CKenwoodNetwork::processKenwoodData(unsigned char* inData) +{ + if (inData[7U] != 0x09U && inData[7U] != 0x0BU && inData[7U] != 0x08U) + return 0U; + + unsigned char outData[50U]; + + if (inData[7U] == 0x09U || inData[7U] == 0x08U) { + outData[0U] = 0x90U; + outData[1U] = inData[8U]; + outData[2U] = inData[7U]; + outData[3U] = inData[10U]; + outData[4U] = inData[9U]; + outData[5U] = inData[12U]; + outData[6U] = inData[11U]; + ::memcpy(inData, outData, 7U); + return 7U; + } else { + outData[0U] = 0x90U; + outData[1U] = inData[8U]; + outData[2U] = inData[7U]; + outData[3U] = inData[10U]; + outData[4U] = inData[9U]; + outData[5U] = inData[12U]; + outData[6U] = inData[11U]; + outData[7U] = inData[14U]; + outData[8U] = inData[13U]; + outData[9U] = inData[16U]; + outData[10U] = inData[15U]; + outData[11U] = inData[18U]; + outData[12U] = inData[17U]; + outData[13U] = inData[20U]; + outData[14U] = inData[19U]; + outData[15U] = inData[22U]; + outData[16U] = inData[21U]; + outData[17U] = inData[24U]; + outData[18U] = inData[23U]; + outData[19U] = inData[26U]; + outData[20U] = inData[25U]; + outData[21U] = inData[28U]; + outData[22U] = inData[27U]; + outData[23U] = inData[29U]; + ::memcpy(inData, outData, 24U); + return 24U; + } +} + +unsigned long CKenwoodNetwork::getTimeStamp() const +{ + unsigned long timeStamp = 0UL; + +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + ::GetSystemTime(&st); + + unsigned int hh = st.wHour; + unsigned int mm = st.wMinute; + unsigned int ss = st.wSecond; + unsigned int ms = st.wMilliseconds; + + timeStamp += hh * 3600U * 1000U * 80U; + timeStamp += mm * 60U * 1000U * 80U; + timeStamp += ss * 1000U * 80U; + timeStamp += ms * 80U; +#else + struct timeval tod; + ::gettimeofday(&tod, NULL); + + unsigned int ss = tod.tv_sec; + unsigned int ms = tod.tv_usec / 1000U; + + timeStamp += ss * 1000U * 80U; + timeStamp += ms * 80U; +#endif + + return timeStamp; +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceLateEntry(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char sacch[4U]; + sacch[0U] = inData[12U]; + sacch[1U] = inData[11U]; + sacch[2U] = inData[14U]; + sacch[3U] = inData[13U]; + + switch (sacch[0U] & 0xC0U) { + case 0xC0U: + if (!m_seen1) { + unsigned int offset = 0U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen1 = true; + } + break; + case 0x80U: + if (!m_seen2) { + unsigned int offset = 18U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen2 = true; + } + break; + case 0x40U: + if (!m_seen3) { + unsigned int offset = 36U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen3 = true; + } + break; + case 0x00U: + if (!m_seen4) { + unsigned int offset = 54U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen4 = true; + } + break; + } + + if (!m_seen1 || !m_seen2 || !m_seen3 || !m_seen4) + return 0U; + + // Create a dummy header + // Header SACCH + inData[11U] = 0x10U; + inData[12U] = 0x01U; + inData[13U] = 0x00U; + inData[14U] = 0x00U; + + // Header FACCH + inData[15U] = m_sacch[1U]; + inData[16U] = m_sacch[0U]; + inData[17U] = m_sacch[3U]; + inData[18U] = m_sacch[2U]; + inData[19U] = m_sacch[5U]; + inData[20U] = m_sacch[4U]; + inData[21U] = m_sacch[7U]; + inData[22U] = m_sacch[6U]; + inData[23U] = 0x00U; + inData[24U] = m_sacch[8U]; + + return processKenwoodVoiceHeader(inData); +} diff --git a/NXDNGateway/KenwoodNetwork.h b/NXDNGateway/KenwoodNetwork.h new file mode 100644 index 0000000..aa52d64 --- /dev/null +++ b/NXDNGateway/KenwoodNetwork.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef KenwoodNetwork_H +#define KenwoodNetwork_H + +#include "RptNetwork.h" +#include "UDPSocket.h" +#include "Timer.h" + +#include +#include + +class CKenwoodNetwork : public IRptNetwork { +public: + CKenwoodNetwork(unsigned int localPort, const std::string& rptAddress, unsigned int rptPort, bool debug); + virtual ~CKenwoodNetwork(); + + virtual bool open(); + + virtual bool write(const unsigned char* data, unsigned int length); + + virtual unsigned int read(unsigned char* data); + + virtual void close(); + + virtual void clock(unsigned int ms); + +private: + CUDPSocket m_rtpSocket; + CUDPSocket m_rtcpSocket; + in_addr m_address; + unsigned int m_rtcpPort; + unsigned int m_rtpPort; + bool m_headerSeen; + bool m_seen1; + bool m_seen2; + bool m_seen3; + bool m_seen4; + unsigned char* m_sacch; + uint8_t m_sessionId; + uint16_t m_seqNo; + unsigned int m_ssrc; + bool m_debug; + uint32_t m_startSecs; + uint32_t m_startUSecs; + CTimer m_rtcpTimer; + CTimer m_hangTimer; + unsigned char m_hangType; + unsigned short m_hangSrc; + unsigned short m_hangDst; + + bool processIcomVoiceHeader(const unsigned char* data); + bool processIcomVoiceData(const unsigned char* data); + unsigned int processKenwoodVoiceHeader(unsigned char* data); + unsigned int processKenwoodVoiceData(unsigned char* data); + unsigned int processKenwoodVoiceLateEntry(unsigned char* data); + unsigned int processKenwoodData(unsigned char* data); + bool writeRTPVoiceHeader(const unsigned char* data); + bool writeRTPVoiceData(const unsigned char* data); + bool writeRTPVoiceTrailer(const unsigned char* data); + bool writeRTCPStart(); + bool writeRTCPPing(); + bool writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst); + bool writeRTCPHang(); + unsigned int readRTP(unsigned char* data); + unsigned int readRTCP(unsigned char* data); + unsigned long getTimeStamp() const; +}; + +#endif diff --git a/NXDNGateway/Makefile b/NXDNGateway/Makefile index 881ee6d..4ec984b 100644 --- a/NXDNGateway/Makefile +++ b/NXDNGateway/Makefile @@ -4,8 +4,8 @@ CFLAGS = -g -O3 -Wall -std=c++0x -pthread LIBS = -lpthread LDFLAGS = -g -OBJECTS = APRSWriter.o APRSWriterThread.o Conf.o GPSHandler.o IcomNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o Reflectors.o \ - StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o +OBJECTS = APRSWriter.o APRSWriterThread.o Conf.o GPSHandler.o IcomNetwork.o KenwoodNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o \ + Reflectors.o RptNetwork.o StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o all: NXDNGateway diff --git a/NXDNGateway/NXDNGateway.cpp b/NXDNGateway/NXDNGateway.cpp index b86fb9e..e614f9a 100644 --- a/NXDNGateway/NXDNGateway.cpp +++ b/NXDNGateway/NXDNGateway.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2017,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -16,9 +16,11 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "KenwoodNetwork.h" #include "IcomNetwork.h" #include "NXDNNetwork.h" #include "NXDNGateway.h" +#include "RptNetwork.h" #include "NXDNLookup.h" #include "Reflectors.h" #include "GPSHandler.h" @@ -27,6 +29,7 @@ #include "Thread.h" #include "Voice.h" #include "Timer.h" +#include "Utils.h" #include "Log.h" #if defined(_WIN32) || defined(_WIN64) @@ -113,7 +116,8 @@ void CNXDNGateway::run() if (pid == -1) { ::fprintf(stderr, "Couldn't fork() , exiting\n"); return; - } else if (pid != 0) { + } + else if (pid != 0) { exit(EXIT_SUCCESS); } @@ -174,13 +178,17 @@ void CNXDNGateway::run() } #endif - in_addr rptAddr = CUDPSocket::lookup(m_conf.getRptAddress()); - unsigned int rptPort = m_conf.getRptPort(); - createGPS(); - CIcomNetwork localNetwork(m_conf.getMyPort(), m_conf.getRptDebug()); - ret = localNetwork.open(); + IRptNetwork* localNetwork = NULL; + std::string protocol = m_conf.getRptProtocol(); + + if (protocol == "Kenwood") + localNetwork = new CKenwoodNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getRptDebug()); + else + localNetwork = new CIcomNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getRptDebug()); + + ret = localNetwork->open(); if (!ret) { ::LogFinalise(); return; @@ -189,11 +197,22 @@ void CNXDNGateway::run() CNXDNNetwork remoteNetwork(m_conf.getNetworkPort(), m_conf.getCallsign(), m_conf.getNetworkDebug()); ret = remoteNetwork.open(); if (!ret) { - localNetwork.close(); + localNetwork->close(); + delete localNetwork; ::LogFinalise(); return; } + CUDPSocket* remoteSocket = NULL; + if (m_conf.getRemoteCommandsEnabled()) { + remoteSocket = new CUDPSocket(m_conf.getRemoteCommandsPort()); + ret = remoteSocket->open(); + if (!ret) { + delete remoteSocket; + remoteSocket = NULL; + } + } + CReflectors reflectors(m_conf.getNetworkHosts1(), m_conf.getNetworkHosts2(), m_conf.getNetworkReloadTime()); if (m_conf.getNetworkParrotPort() > 0U) reflectors.setParrot(m_conf.getNetworkParrotAddress(), m_conf.getNetworkParrotPort()); @@ -249,7 +268,7 @@ void CNXDNGateway::run() LogMessage("Linked at startup to reflector %u", currentId); } else { -startupId = 9999U; + startupId = 9999U; } } @@ -272,7 +291,7 @@ startupId = 9999U; bool grp = (buffer[9U] & 0x01U) == 0x01U; if (grp && currentId == dstId) - localNetwork.write(buffer + 10U, len - 10U, rptAddr, rptPort); + localNetwork->write(buffer + 10U, len - 10U); } // Any network activity is proof that the reflector is alive @@ -281,7 +300,7 @@ startupId = 9999U; } // From the MMDVM to the reflector or control data - len = localNetwork.read(buffer, address, port); + len = localNetwork->read(buffer); if (len > 0U) { // Only process the beginning and ending voice blocks here if ((buffer[0U] == 0x81U || buffer[0U] == 0x83U) && (buffer[5U] == 0x01U || buffer[5U] == 0x08U)) { @@ -381,7 +400,80 @@ startupId = 9999U; if (voice != NULL) { unsigned int length = voice->read(buffer); if (length > 0U) - localNetwork.write(buffer, length, rptAddr, rptPort); + localNetwork->write(buffer, length); + } + + if (remoteSocket != NULL) { + int res = remoteSocket->read(buffer, 200U, address, port); + if (res > 0) { + buffer[res] = '\0'; + if (::memcmp(buffer + 0U, "TalkGroup", 9U) == 0) { + unsigned int tg = (unsigned int)::atoi((char*)(buffer + 9U)); + + CNXDNReflector* reflector = NULL; + if (tg != 9999U) + reflector = reflectors.find(tg); + + if (reflector == NULL && currentId != 9999U) { + LogMessage("Unlinked from reflector %u by remote command", currentId); + + if (voice != NULL) + voice->unlinked(); + + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + + inactivityTimer.stop(); + pollTimer.stop(); + lostTimer.stop(); + + currentId = 9999U; + } else if (reflector != NULL && currentId == 9999U) { + currentId = tg; + currentAddr = reflector->m_address; + currentPort = reflector->m_port; + + LogMessage("Linked to reflector %u by remote command", currentId); + + if (voice != NULL) + voice->linkedTo(currentId); + + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + + inactivityTimer.start(); + pollTimer.start(); + lostTimer.start(); + } else if (reflector != NULL && currentId != 9999U) { + LogMessage("Unlinked from reflector %u by remote command", currentId); + + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + remoteNetwork.writeUnlink(currentAddr, currentPort, currentId); + + currentId = tg; + currentAddr = reflector->m_address; + currentPort = reflector->m_port; + + LogMessage("Linked to reflector %u by remote command", currentId); + + if (voice != NULL) + voice->linkedTo(currentId); + + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + remoteNetwork.writePoll(currentAddr, currentPort, currentId); + + inactivityTimer.start(); + pollTimer.start(); + lostTimer.start(); + } + } else { + CUtils::dump("Invalid remote command received", buffer, res); + } + } } unsigned int ms = stopWatch.elapsed(); @@ -389,6 +481,8 @@ startupId = 9999U; reflectors.clock(ms); + localNetwork->clock(ms); + if (voice != NULL) voice->clock(ms); @@ -469,7 +563,13 @@ startupId = 9999U; delete voice; - localNetwork.close(); + localNetwork->close(); + delete localNetwork; + + if (remoteSocket != NULL) { + remoteSocket->close(); + delete remoteSocket; + } remoteNetwork.close(); diff --git a/NXDNGateway/NXDNGateway.ini b/NXDNGateway/NXDNGateway.ini index 884d8bd..205857c 100644 --- a/NXDNGateway/NXDNGateway.ini +++ b/NXDNGateway/NXDNGateway.ini @@ -1,6 +1,18 @@ [General] Callsign=G4KLX Suffix=NXDN +# The next four lines are for a Kenwood repeater +# RptProtocol=Kenwood +# RptAddress=1.2.3.4 +# RptPort=64000 +# LocalPort=64000 +# The next four lines are for an Icom repeater +# RptProtocol=Icom +# RptAddress=1.2.3.4 +# RptPort=41300 +# LocalPort=41300 +# The next four lines are for an MMDVM +RptProtocol=Icom RptAddress=127.0.0.1 RptPort=14021 LocalPort=14020 @@ -57,3 +69,6 @@ Enable=0 Address=127.0.0.1 Port=7834 +[Remote Commands] +Enable=0 +Port=6075 diff --git a/NXDNGateway/NXDNGateway.vcxproj b/NXDNGateway/NXDNGateway.vcxproj index 38995e9..e955110 100644 --- a/NXDNGateway/NXDNGateway.vcxproj +++ b/NXDNGateway/NXDNGateway.vcxproj @@ -22,32 +22,32 @@ {8B7A5406-8560-4B40-ADDA-9B8EBF93E232} Win32Proj NXDNGateway - 10.0.15063.0 + 10.0 Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode @@ -151,6 +151,7 @@ + @@ -159,6 +160,7 @@ + @@ -174,6 +176,7 @@ + @@ -181,6 +184,7 @@ + diff --git a/NXDNGateway/NXDNGateway.vcxproj.filters b/NXDNGateway/NXDNGateway.vcxproj.filters index 5013a7b..4215f7e 100644 --- a/NXDNGateway/NXDNGateway.vcxproj.filters +++ b/NXDNGateway/NXDNGateway.vcxproj.filters @@ -74,6 +74,12 @@ Header Files + + Header Files + + + Header Files + @@ -133,5 +139,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/NXDNGateway/NXDNHosts.txt b/NXDNGateway/NXDNHosts.txt index b3ad2ed..cb16ad1 100644 --- a/NXDNGateway/NXDNHosts.txt +++ b/NXDNGateway/NXDNHosts.txt @@ -2,29 +2,70 @@ # # The format of this file is the number of the Talk Group followed by the host name or address and port # +# HELLAS Zone TG202 +202 hellaszone.com 41400 + +# RED NXDN SPAIN +214 nxdn214.xreflector.es 41400 + +# HBLink Poland ,TG260 +260 nxdn.hblink.pl 41400 + +# 302, P25 Canada +302 p25canada.hopto.org 41400 + +# Super Freq 420 +420 hb.superfreqdigital.com 41400 # VKCore, 505 505 43.229.63.42 41450 # New Zealand, 530 -530 101.98.22.194 41400 +530 zldigitalreflectors.hopto.org 41400 + +# XLX545E PA NXDN +545 70.44.20.24 41401 + +# RuralMN Reflector 707 +707 707nxdn.kd0ioe.com 41400 + +# 911 Cop Talk, Multimode TG for Police and First Responders +911 nxdn.k2cop.com 41400 + +# 914 Latin America +914 164.132.96.157 41400 # Florida, 1200 1200 florida.nxref.org 41400 +# 2140 DMRplus BM Multi +2140 94.177.235.81 41400 + +# 2225 IT Tuscany +2225 80.211.99.134 41400 + +# 2249 IT SICILIA +2249 nxdn.digitalsicilia.it 41400 + +# RU DMR TG2503 +2503 nxdn.r1ik.ru 41400 + +# 3023 Ontario Crosslink +3023 ontxlink.hopto.org 41400 + +# 3142 Pennsylvania +3142 3.215.215.169 41402 + # VK7 TAS, 5057 5057 45.248.50.37 41400 -# BM NXCore bridge, 50599 -50599 nxdn.duckdns.org 41490 +# Multiprotocolo Argentina +7225 ysfarg.ddns.net 41400 # North America, 10200 10200 dvswitch.org 42400 -# Portuguese speaking test, 10268 -26810 84.90.7.110 41400 - -# Spanish speaking, 10301 +# HBLINK Espana, 10301 10301 ea5gvk.duckdns.org 41400 # NXDN 10302 Multimode BM 21461 EA Spain @@ -42,8 +83,31 @@ # Europe, German speaking, 20000 20000 89.185.97.38 41400 -# CT NXCore, 25000 -25000 45.62.211.216 41425 +# 20945 Deutschland DL1BH +20945 dl1bh.ddns.net 41400 + +# 21465 ADER Multimode +21465 80.211.106.186 41400 + +# 22200 IT HBLINK ITALY +22200 nxdn.hblink.it 41400 + +# 22202 IT NXDN Sardegna +22202 nxdn.is0hha.hblink.it 41400 + +# 22220 IT ECHOLINK ITALY +22220 dagobah.hblink.it 41400 +# 22245 IT PIEDMONT GDO +22245 nxdngdo.duckdns.org 41400 + +# 22825 Swiss NXDN Reflerctor +22825 212.237.33.114 41400 + +# NXDN Scotland, 23551 +23551 nxdnscotland.ddns.net 41400 + +# NX Core, 25000 +25000 173.166.94.77 41400 # Russia NXDN Net, 25641 25641 194.182.85.217 41400 @@ -51,6 +115,12 @@ # Poland, 26000 26000 31.0.161.238 41400 +# Deutschland, 26200 +26200 26200.ham-nxdn.de 41400 + +# Portuguese speaking test, 10268 +26810 84.90.7.110 41400 + # America-Ragchew, 28299 28299 65.101.7.52 41400 @@ -60,24 +130,39 @@ # Alabama-Link, 31010 31010 nxdn.alabamalink.info 41400 +# Colorado Fun Machine WE0FUN Bridge to C4FM, DMR, DStar, P25 and AllStarLink (Analog) http://www.we0fun.com +31081 nxdn.we0fun.com 41400 + # Colorado HD, 31088 31088 54.191.50.212 41400 # Connecticut Chat, 31092 31092 nxdn.alecwasserman.com 41400 +# Kingsland Digital Link +31137 74.91.119.94 41400 + # Illinois, 31171 31171 74.208.235.115 41400 # Southern Indiana, 31188 31188 w9windigital.org 41400 +# 31264 XLX625 The BROniverse www.wa8bro.com +31264 nxdn.dudetronics.com 41400 + # Central New Jersey, 31340 31340 cnjham.msmts.com 41400 # Oklahoma Link, 31403 31403 3.208.70.29 41400 +# XLX545A Pennsylvania Cross Mode, 31425 +31425 70.44.20.24 41400 + +# XLX545A Pennsylvania Cross Mode (alt), 31426 +31426 3.215.215.169 41400 + # Rhode Island Digital Link, 31444 31444 18.219.32.21 41400 @@ -88,7 +173,16 @@ 31672 nxdn-31672.pistar.uk 41400 # DX-LINK SYSTEM, 31777 -31777 45.77.204.214 41400 +31777 8.9.4.102 41400 + +# K8JTK Hub Multimode ILS/DVMIS (K8JTK.org), 31983 +31983 NXDNReflector31983.K8JTK.org 41400 + +# CW-Ops Academy, NXDN Reflector 32103 +32103 cwops.dyndns.org 41400 + +# OMISS Group, NXDN Reflector 33581 +33581 omiss.dyndns.org 41400 # Fusion Canada Fr /Wires-x /Ysf /Nxdn Network 40721 38.110.97.161 41400 @@ -99,6 +193,9 @@ # DMR TG50525 bridge, 50525 50525 50525.nxref.org 41400 +# BM NXCore bridge, 50599 +50599 nxdn.duckdns.org 41490 + # Thailand DTDXA XLX520N, 52000 52000 nxdn.dtdxa.com 41400 @@ -106,22 +203,13 @@ 53099 203.86.206.49 41400 # World Wide, 65000 -65000 176.9.1.168 41400 +65000 176.9.1.168 41400 # French-Test, 65208 65208 m55.evxonline.net 41400 -# 3023 Ontario Crosslink -3023 ontxlink.hopto.org 41400 +# NXDN - 2007DXGROUP +65100 89.46.75.115 41400 -# 2140 DMRplus BM Multi -2140 94.177.235.81 41400 - -# 2225 IT Tuscany -2225 80.211.99.134 41400 - -# 21465 ADER Multimode -21465 80.211.106.186 41400 - -# 22245 IT PIEDMONT GDO -22245 nxdngdo.duckdns.org 41400 +# NXDN SPAIN 21410 +21410 nxdn21410.nxdn.es 41400 diff --git a/NXDNGateway/RptNetwork.cpp b/NXDNGateway/RptNetwork.cpp new file mode 100644 index 0000000..952ce99 --- /dev/null +++ b/NXDNGateway/RptNetwork.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "RptNetwork.h" + +IRptNetwork::~IRptNetwork() +{ +} diff --git a/NXDNGateway/RptNetwork.h b/NXDNGateway/RptNetwork.h new file mode 100644 index 0000000..44f86d8 --- /dev/null +++ b/NXDNGateway/RptNetwork.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef RptNetwork_H +#define RptNetwork_H + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include + +class IRptNetwork { +public: + virtual ~IRptNetwork() = 0; + + virtual bool open() = 0; + + virtual bool write(const unsigned char* data, unsigned int length) = 0; + + virtual unsigned int read(unsigned char* data) = 0; + + virtual void close() = 0; + + virtual void clock(unsigned int ms) = 0; + +private: +}; + +#endif diff --git a/NXDNGateway/UDPSocket.cpp b/NXDNGateway/UDPSocket.cpp index 396f1f7..c105b0b 100644 --- a/NXDNGateway/UDPSocket.cpp +++ b/NXDNGateway/UDPSocket.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2016 by Jonathan Naylor G4KLX + * Copyright (C) 2006-2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -24,6 +24,7 @@ #if !defined(_WIN32) && !defined(_WIN64) #include #include +#include #endif @@ -260,3 +261,31 @@ void CUDPSocket::close() ::close(m_fd); #endif } + +unsigned long CUDPSocket::getLocalAddress() const +{ + unsigned long address = 0UL; + + char hostname[80U]; + int ret = ::gethostname(hostname, 80); + if (ret == -1) + return 0UL; + + struct hostent* phe = ::gethostbyname(hostname); + if (phe == NULL) + return 0UL; + + if (phe->h_addrtype != AF_INET) + return 0UL; + + for (unsigned int i = 0U; phe->h_addr_list[i] != NULL; i++) { + struct in_addr addr; + ::memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); + if (addr.s_addr != INADDR_LOOPBACK) { + address = addr.s_addr; + break; + } + } + + return address; +} diff --git a/NXDNGateway/UDPSocket.h b/NXDNGateway/UDPSocket.h index e0af272..ffc3b92 100644 --- a/NXDNGateway/UDPSocket.h +++ b/NXDNGateway/UDPSocket.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2011,2013,2015,2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -47,6 +47,8 @@ public: void close(); + unsigned long getLocalAddress() const; + static in_addr lookup(const std::string& hostName); private: diff --git a/NXDNGateway/Version.h b/NXDNGateway/Version.h index 53badbe..1c4898c 100644 --- a/NXDNGateway/Version.h +++ b/NXDNGateway/Version.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20180524"; +const char* VERSION = "20200519"; #endif diff --git a/NXDNParrot/NXDNParrot.vcxproj b/NXDNParrot/NXDNParrot.vcxproj index 053937b..a92087d 100644 --- a/NXDNParrot/NXDNParrot.vcxproj +++ b/NXDNParrot/NXDNParrot.vcxproj @@ -22,32 +22,32 @@ {2AE94EAA-FD57-45C9-8555-6425CFA777A3} Win32Proj NXDNParrot - 10.0.15063.0 + 10.0 Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode diff --git a/NXDNReflector/Conf.cpp b/NXDNReflector/Conf.cpp index 0564d15..f1e1cf8 100644 --- a/NXDNReflector/Conf.cpp +++ b/NXDNReflector/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -48,6 +48,7 @@ m_logFileRoot(), m_networkPort(0U), m_networkDebug(false), m_nxCoreEnabled(false), +m_nxCoreProtocol("Icom"), m_nxCoreAddress(), m_nxCoreTGEnable(0U), m_nxCoreTGDisable(0U), @@ -123,6 +124,8 @@ bool CConf::read() } else if (section == SECTION_NXCORE) { if (::strcmp(key, "Enabled") == 0) m_nxCoreEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Protocol") == 0) + m_nxCoreProtocol = value; else if (::strcmp(key, "Address") == 0) m_nxCoreAddress = value; else if (::strcmp(key, "TGEnable") == 0) @@ -194,6 +197,11 @@ bool CConf::getNXCoreEnabled() const return m_nxCoreEnabled; } +std::string CConf::getNXCoreProtocol() const +{ + return m_nxCoreProtocol; +} + std::string CConf::getNXCoreAddress() const { return m_nxCoreAddress; diff --git a/NXDNReflector/Conf.h b/NXDNReflector/Conf.h index d5c4c2e..57156ed 100644 --- a/NXDNReflector/Conf.h +++ b/NXDNReflector/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -50,6 +50,7 @@ public: // The NXCore section bool getNXCoreEnabled() const; + std::string getNXCoreProtocol() const; std::string getNXCoreAddress() const; unsigned short getNXCoreTGEnable() const; unsigned short getNXCoreTGDisable() const; @@ -72,6 +73,7 @@ private: bool m_networkDebug; bool m_nxCoreEnabled; + std::string m_nxCoreProtocol; std::string m_nxCoreAddress; unsigned short m_nxCoreTGEnable; unsigned short m_nxCoreTGDisable; diff --git a/NXDNReflector/CoreNetwork.cpp b/NXDNReflector/CoreNetwork.cpp new file mode 100644 index 0000000..c842dc7 --- /dev/null +++ b/NXDNReflector/CoreNetwork.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CoreNetwork.h" + +ICoreNetwork::~ICoreNetwork() +{ +} diff --git a/NXDNReflector/CoreNetwork.h b/NXDNReflector/CoreNetwork.h new file mode 100644 index 0000000..d991b81 --- /dev/null +++ b/NXDNReflector/CoreNetwork.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CoreNetwork_H +#define CoreNetwork_H + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include + +class ICoreNetwork { +public: + virtual ~ICoreNetwork() = 0; + + virtual bool open() = 0; + + virtual bool write(const unsigned char* data, unsigned int length) = 0; + + virtual unsigned int read(unsigned char* data) = 0; + + virtual void close() = 0; + + virtual void clock(unsigned int ms) = 0; + +private: +}; + +#endif diff --git a/NXDNReflector/NXCoreNetwork.cpp b/NXDNReflector/IcomNetwork.cpp similarity index 67% rename from NXDNReflector/NXCoreNetwork.cpp rename to NXDNReflector/IcomNetwork.cpp index e676710..08d68ec 100644 --- a/NXDNReflector/NXCoreNetwork.cpp +++ b/NXDNReflector/IcomNetwork.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -16,7 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include "NXCoreNetwork.h" +#include "IcomNetwork.h" #include "Utils.h" #include "Log.h" @@ -26,10 +26,10 @@ const unsigned int BUFFER_LENGTH = 200U; -const unsigned int NXCORE_PORT = 41300U; +const unsigned int ICOM_PORT = 41300U; -CNXCoreNetwork::CNXCoreNetwork(const std::string& address, bool debug) : -m_socket(NXCORE_PORT), +CIcomNetwork::CIcomNetwork(const std::string& address, bool debug) : +m_socket(ICOM_PORT), m_address(), m_debug(debug) { @@ -38,13 +38,13 @@ m_debug(debug) m_address = CUDPSocket::lookup(address); } -CNXCoreNetwork::~CNXCoreNetwork() +CIcomNetwork::~CIcomNetwork() { } -bool CNXCoreNetwork::open() +bool CIcomNetwork::open() { - LogMessage("Opening NXCore network connection"); + LogMessage("Opening Icom network connection"); if (m_address.s_addr == INADDR_NONE) return false; @@ -52,7 +52,7 @@ bool CNXCoreNetwork::open() return m_socket.open(); } -bool CNXCoreNetwork::write(const unsigned char* data, unsigned int len) +bool CIcomNetwork::write(const unsigned char* data, unsigned int len) { assert(data != NULL); @@ -81,12 +81,12 @@ bool CNXCoreNetwork::write(const unsigned char* data, unsigned int len) ::memcpy(buffer + 40U, data + 10U, 33U); if (m_debug) - CUtils::dump(1U, "NXCore Network Data Sent", buffer, 102U); + CUtils::dump(1U, "Icom Network Data Sent", buffer, 102U); - return m_socket.write(buffer, 102U, m_address, NXCORE_PORT); + return m_socket.write(buffer, 102U, m_address, ICOM_PORT); } -unsigned int CNXCoreNetwork::read(unsigned char* data) +unsigned int CIcomNetwork::read(unsigned char* data) { unsigned char buffer[BUFFER_LENGTH]; @@ -97,8 +97,8 @@ unsigned int CNXCoreNetwork::read(unsigned char* data) return 0U; // Check if the data is for us - if (m_address.s_addr != address.s_addr || port != NXCORE_PORT) { - LogMessage("NXCore packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, NXCORE_PORT, port); + if (m_address.s_addr != address.s_addr || port != ICOM_PORT) { + LogMessage("Icom packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, ICOM_PORT, port); return 0U; } @@ -110,16 +110,20 @@ unsigned int CNXCoreNetwork::read(unsigned char* data) return 0U; if (m_debug) - CUtils::dump(1U, "NXCore Network Data Received", buffer, length); + CUtils::dump(1U, "Icom Network Data Received", buffer, length); ::memcpy(data, buffer + 40U, 33U); return 33U; } -void CNXCoreNetwork::close() +void CIcomNetwork::clock(unsigned int ms) +{ +} + +void CIcomNetwork::close() { m_socket.close(); - LogMessage("Closing NXCore network connection"); + LogMessage("Closing Icom network connection"); } diff --git a/NXDNReflector/NXCoreNetwork.h b/NXDNReflector/IcomNetwork.h similarity index 66% rename from NXDNReflector/NXCoreNetwork.h rename to NXDNReflector/IcomNetwork.h index 78c896c..704b12b 100644 --- a/NXDNReflector/NXCoreNetwork.h +++ b/NXDNReflector/IcomNetwork.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -16,27 +16,30 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifndef NXCoreNetwork_H -#define NXCoreNetwork_H +#ifndef IcomNetwork_H +#define IcomNetwork_H +#include "CoreNetwork.h" #include "UDPSocket.h" #include "Timer.h" #include #include -class CNXCoreNetwork { +class CIcomNetwork : public ICoreNetwork { public: - CNXCoreNetwork(const std::string& address, bool debug); - ~CNXCoreNetwork(); + CIcomNetwork(const std::string& address, bool debug); + virtual ~CIcomNetwork(); - bool open(); + virtual bool open(); - bool write(const unsigned char* data, unsigned int len); + virtual bool write(const unsigned char* data, unsigned int len); - unsigned int read(unsigned char* data); + virtual unsigned int read(unsigned char* data); - void close(); + virtual void close(); + + virtual void clock(unsigned int ms); private: CUDPSocket m_socket; diff --git a/NXDNReflector/KenwoodNetwork.cpp b/NXDNReflector/KenwoodNetwork.cpp new file mode 100644 index 0000000..8a872c0 --- /dev/null +++ b/NXDNReflector/KenwoodNetwork.cpp @@ -0,0 +1,923 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "KenwoodNetwork.h" +#include "NXDNCRC.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include +#include + +const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +const unsigned int BUFFER_LENGTH = 200U; + +const unsigned int RTP_PORT = 64000U; +const unsigned int RTCP_PORT = 64001U; + +CKenwoodNetwork::CKenwoodNetwork(const std::string& address, bool debug) : +m_rtpSocket(RTP_PORT), +m_rtcpSocket(RTCP_PORT), +m_address(), +m_headerSeen(false), +m_seen1(false), +m_seen2(false), +m_seen3(false), +m_seen4(false), +m_sacch(NULL), +m_sessionId(1U), +m_seqNo(0U), +m_ssrc(0U), +m_debug(debug), +m_startSecs(0U), +m_startUSecs(0U), +m_rtcpTimer(1000U, 0U, 200U), +m_hangTimer(1000U, 5U), +m_hangType(0U), +m_hangSrc(0U), +m_hangDst(0U) +{ + assert(!address.empty()); + + m_sacch = new unsigned char[10U]; + + m_address = CUDPSocket::lookup(address); +} + +CKenwoodNetwork::~CKenwoodNetwork() +{ + delete[] m_sacch; +} + +bool CKenwoodNetwork::open() +{ + LogMessage("Opening Kenwood connection"); + + if (m_address.s_addr == INADDR_NONE) + return false; + + if (!m_rtcpSocket.open()) + return false; + + if (!m_rtpSocket.open()) { + m_rtcpSocket.close(); + return false; + } + + m_ssrc = m_rtpSocket.getLocalAddress(); + + return true; +} + +bool CKenwoodNetwork::write(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + + switch (data[0U]) { + case 0x81U: // Voice header or trailer + case 0x83U: + return processIcomVoiceHeader(data); + case 0xACU: // Voice data + case 0xAEU: + return processIcomVoiceData(data); + default: + return false; + } +} + +bool CKenwoodNetwork::processIcomVoiceHeader(const unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[30U]; + ::memset(outData, 0x00U, 30U); + + // SACCH + outData[0U] = inData[2U]; + outData[1U] = inData[1U]; + outData[2U] = inData[4U] & 0xC0U; + outData[3U] = inData[3U]; + + // FACCH 1+2 + outData[4U] = outData[14U] = inData[6U]; + outData[5U] = outData[15U] = inData[5U]; + outData[6U] = outData[16U] = inData[8U]; + outData[7U] = outData[17U] = inData[7U]; + outData[8U] = outData[18U] = inData[10U]; + outData[9U] = outData[19U] = inData[9U]; + outData[10U] = outData[20U] = inData[12U]; + outData[11U] = outData[21U] = inData[11U]; + + unsigned short src = (inData[8U] << 8) + (inData[9U] << 0); + unsigned short dst = (inData[10U] << 8) + (inData[11U] << 0); + unsigned char type = (inData[7U] >> 5) & 0x07U; + + switch (inData[5U] & 0x3FU) { + case 0x01U: + m_hangTimer.stop(); + m_rtcpTimer.start(); + writeRTCPStart(); + return writeRTPVoiceHeader(outData); + case 0x08U: { + m_hangTimer.start(); + bool ret = writeRTPVoiceTrailer(outData); + writeRTCPHang(type, src, dst); + return ret; + } + default: + return false; + } +} + +bool CKenwoodNetwork::processIcomVoiceData(const unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[40U], temp[10U]; + ::memset(outData, 0x00U, 40U); + + // SACCH + outData[0U] = inData[2U]; + outData[1U] = inData[1U]; + outData[2U] = inData[4U] & 0xC0U; + outData[3U] = inData[3U]; + + // Audio 1 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (5U * 8U) + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[4U] = temp[1U]; + outData[5U] = temp[0U]; + outData[6U] = temp[3U]; + outData[7U] = temp[2U]; + outData[8U] = temp[5U]; + outData[9U] = temp[4U]; + outData[10U] = temp[7U]; + outData[11U] = temp[6U]; + + // Audio 2 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (5U * 8U) + 49U + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[12U] = temp[1U]; + outData[13U] = temp[0U]; + outData[14U] = temp[3U]; + outData[15U] = temp[2U]; + outData[16U] = temp[5U]; + outData[17U] = temp[4U]; + outData[18U] = temp[7U]; + outData[19U] = temp[6U]; + + // Audio 3 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (19U * 8U) + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[20U] = temp[1U]; + outData[21U] = temp[0U]; + outData[22U] = temp[3U]; + outData[23U] = temp[2U]; + outData[24U] = temp[5U]; + outData[25U] = temp[4U]; + outData[26U] = temp[7U]; + outData[27U] = temp[6U]; + + // Audio 4 + ::memset(temp, 0x00U, 10U); + for (unsigned int i = 0U; i < 49U; i++) { + unsigned int offset = (19U * 8U) + 49U + i; + bool b = READ_BIT(inData, offset); + WRITE_BIT(temp, i, b); + } + outData[28U] = temp[1U]; + outData[29U] = temp[0U]; + outData[30U] = temp[3U]; + outData[31U] = temp[2U]; + outData[32U] = temp[5U]; + outData[33U] = temp[4U]; + outData[34U] = temp[7U]; + outData[35U] = temp[6U]; + + return writeRTPVoiceData(outData); +} + +bool CKenwoodNetwork::writeRTPVoiceHeader(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[50U]; + ::memset(buffer, 0x00U, 50U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + m_sessionId++; + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x03U; + buffer[18U] = 0x04U; + buffer[19U] = 0x04U; + buffer[20U] = 0x0AU; + buffer[21U] = 0x05U; + buffer[22U] = 0x0AU; + + ::memcpy(buffer + 23U, data, 24U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 47U); + + return m_rtpSocket.write(buffer, 47U, m_address, RTP_PORT); +} + +bool CKenwoodNetwork::writeRTPVoiceTrailer(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[50U]; + ::memset(buffer, 0x00U, 50U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x03U; + buffer[18U] = 0x04U; + buffer[19U] = 0x04U; + buffer[20U] = 0x0AU; + buffer[21U] = 0x05U; + buffer[22U] = 0x0AU; + + ::memcpy(buffer + 23U, data, 24U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 47U); + + return m_rtpSocket.write(buffer, 47U, m_address, RTP_PORT); +} + +bool CKenwoodNetwork::writeRTPVoiceData(const unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[60U]; + ::memset(buffer, 0x00U, 60U); + + buffer[0U] = 0x80U; + buffer[1U] = 0x66U; + + m_seqNo++; + buffer[2U] = (m_seqNo >> 8) & 0xFFU; + buffer[3U] = (m_seqNo >> 0) & 0xFFU; + + unsigned long timeStamp = getTimeStamp(); + buffer[4U] = (timeStamp >> 24) & 0xFFU; + buffer[5U] = (timeStamp >> 16) & 0xFFU; + buffer[6U] = (timeStamp >> 8) & 0xFFU; + buffer[7U] = (timeStamp >> 0) & 0xFFU; + + buffer[8U] = (m_ssrc >> 24) & 0xFFU; + buffer[9U] = (m_ssrc >> 16) & 0xFFU; + buffer[10U] = (m_ssrc >> 8) & 0xFFU; + buffer[11U] = (m_ssrc >> 0) & 0xFFU; + + buffer[12U] = m_sessionId; + + buffer[13U] = 0x00U; + buffer[14U] = 0x00U; + buffer[15U] = 0x00U; + buffer[16U] = 0x03U; + buffer[17U] = 0x02U; + buffer[18U] = 0x04U; + buffer[19U] = 0x07U; + buffer[20U] = 0x10U; + buffer[21U] = 0x08U; + buffer[22U] = 0x10U; + + ::memcpy(buffer + 23U, data, 36U); + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 59U); + + return m_rtpSocket.write(buffer, 59U, m_address, RTP_PORT); +} + +bool CKenwoodNetwork::writeRTCPStart() +{ +#if defined(_WIN32) || defined(_WIN64) + time_t now; + ::time(&now); + + m_startSecs = uint32_t(now); + + SYSTEMTIME st; + ::GetSystemTime(&st); + + m_startUSecs = st.wMilliseconds * 1000U; +#else + struct timeval tod; + ::gettimeofday(&tod, NULL); + + m_startSecs = tod.tv_sec; + m_startUSecs = tod.tv_usec; +#endif + + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8AU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x06U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_startSecs >> 24) & 0xFFU; + buffer[13U] = (m_startSecs >> 16) & 0xFFU; + buffer[14U] = (m_startSecs >> 8) & 0xFFU; + buffer[15U] = (m_startSecs >> 0) & 0xFFU; + + buffer[16U] = (m_startUSecs >> 24) & 0xFFU; + buffer[17U] = (m_startUSecs >> 16) & 0xFFU; + buffer[18U] = (m_startUSecs >> 8) & 0xFFU; + buffer[19U] = (m_startUSecs >> 0) & 0xFFU; + + buffer[22U] = 0x02U; + + buffer[24U] = 0x01U; + + buffer[27U] = 0x0AU; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U); + + return m_rtcpSocket.write(buffer, 28U, m_address, RTCP_PORT); +} + +bool CKenwoodNetwork::writeRTCPPing() +{ + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8AU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x06U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_startSecs >> 24) & 0xFFU; + buffer[13U] = (m_startSecs >> 16) & 0xFFU; + buffer[14U] = (m_startSecs >> 8) & 0xFFU; + buffer[15U] = (m_startSecs >> 0) & 0xFFU; + + buffer[16U] = (m_startUSecs >> 24) & 0xFFU; + buffer[17U] = (m_startUSecs >> 16) & 0xFFU; + buffer[18U] = (m_startUSecs >> 8) & 0xFFU; + buffer[19U] = (m_startUSecs >> 0) & 0xFFU; + + buffer[22U] = 0x02U; + + buffer[24U] = 0x01U; + + buffer[27U] = 0x7BU; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U); + + return m_rtcpSocket.write(buffer, 28U, m_address, RTCP_PORT); +} + +bool CKenwoodNetwork::writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst) +{ + m_hangType = type; + m_hangSrc = src; + m_hangDst = dst; + + return writeRTCPHang(); +} + +bool CKenwoodNetwork::writeRTCPHang() +{ + unsigned char buffer[30U]; + ::memset(buffer, 0x00U, 30U); + + buffer[0U] = 0x8BU; + buffer[1U] = 0xCCU; + buffer[2U] = 0x00U; + buffer[3U] = 0x04U; + + buffer[4U] = (m_ssrc >> 24) & 0xFFU; + buffer[5U] = (m_ssrc >> 16) & 0xFFU; + buffer[6U] = (m_ssrc >> 8) & 0xFFU; + buffer[7U] = (m_ssrc >> 0) & 0xFFU; + + buffer[8U] = 'K'; + buffer[9U] = 'W'; + buffer[10U] = 'N'; + buffer[11U] = 'E'; + + buffer[12U] = (m_hangSrc >> 8) & 0xFFU; + buffer[13U] = (m_hangSrc >> 0) & 0xFFU; + + buffer[14U] = (m_hangDst >> 8) & 0xFFU; + buffer[15U] = (m_hangDst >> 0) & 0xFFU; + + buffer[16U] = m_hangType; + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 20U); + + return m_rtcpSocket.write(buffer, 20U, m_address, RTCP_PORT); +} + +unsigned int CKenwoodNetwork::read(unsigned char* data) +{ + assert(data != NULL); + + unsigned char dummy[BUFFER_LENGTH]; + readRTCP(dummy); + + unsigned int len = readRTP(data); + switch (len) { + case 0U: // Nothing received + return 0U; + case 35U: // Voice header or trailer + return processKenwoodVoiceHeader(data); + case 47U: // Voice data + if (m_headerSeen) + return processKenwoodVoiceData(data); + else + return processKenwoodVoiceLateEntry(data); + case 31U: // Data + return processKenwoodData(data); + default: + CUtils::dump(5U, "Unknown data received from the Kenwood network", data, len); + return 0U; + } +} + +unsigned int CKenwoodNetwork::readRTP(unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[BUFFER_LENGTH]; + + in_addr address; + unsigned int port; + int length = m_rtpSocket.read(buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return 0U; + + // Check if the data is for us + if (m_address.s_addr != address.s_addr) { + LogMessage("Kenwood RTP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr); + return 0U; + } + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTP Data Received", buffer, length); + + ::memcpy(data, buffer + 12U, length - 12U); + + return length - 12U; +} + +unsigned int CKenwoodNetwork::readRTCP(unsigned char* data) +{ + assert(data != NULL); + + unsigned char buffer[BUFFER_LENGTH]; + + in_addr address; + unsigned int port; + int length = m_rtcpSocket.read(buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return 0U; + + // Check if the data is for us + if (m_address.s_addr != address.s_addr) { + LogMessage("Kenwood RTCP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr); + return 0U; + } + + if (m_debug) + CUtils::dump(1U, "Kenwood Network RTCP Data Received", buffer, length); + + if (::memcmp(buffer + 8U, "KWNE", 4U) != 0) { + LogError("Missing RTCP KWNE signature"); + return 0U; + } + + ::memcpy(data, buffer + 12U, length - 12U); + + return length - 12U; +} + +void CKenwoodNetwork::close() +{ + m_rtcpSocket.close(); + m_rtpSocket.close(); + + LogMessage("Closing Kenwood connection"); +} + +void CKenwoodNetwork::clock(unsigned int ms) +{ + m_rtcpTimer.clock(ms); + if (m_rtcpTimer.isRunning() && m_rtcpTimer.hasExpired()) { + if (m_hangTimer.isRunning()) + writeRTCPHang(); + else + writeRTCPPing(); + m_rtcpTimer.start(); + } + + m_hangTimer.clock(ms); + if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) { + m_rtcpTimer.stop(); + m_hangTimer.stop(); + } +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceHeader(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[50U], temp[20U]; + ::memset(outData, 0x00U, 50U); + + // LICH + outData[0U] = 0x83U; + + // SACCH + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[12U]; + temp[1U] = inData[11U]; + temp[2U] = inData[14U]; + temp[3U] = inData[13U]; + CNXDNCRC::encodeCRC6(temp, 26U); + ::memcpy(outData + 1U, temp, 4U); + + // FACCH 1+2 + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[16U]; + temp[1U] = inData[15U]; + temp[2U] = inData[18U]; + temp[3U] = inData[17U]; + temp[4U] = inData[20U]; + temp[5U] = inData[19U]; + temp[6U] = inData[22U]; + temp[7U] = inData[21U]; + temp[8U] = inData[24U]; + temp[9U] = inData[23U]; + CNXDNCRC::encodeCRC12(temp, 80U); + ::memcpy(outData + 5U, temp, 12U); + ::memcpy(outData + 19U, temp, 12U); + + switch (outData[5U] & 0x3FU) { + case 0x01U: + ::memcpy(inData, outData, 33U); + m_headerSeen = true; + m_seen1 = false; + m_seen2 = false; + m_seen3 = false; + m_seen4 = false; + return 33U; + case 0x08U: + ::memcpy(inData, outData, 33U); + m_headerSeen = false; + m_seen1 = false; + m_seen2 = false; + m_seen3 = false; + m_seen4 = false; + return 33U; + default: + return 0U; + } +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceData(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char outData[50U], temp[20U]; + ::memset(outData, 0x00U, 50U); + + // LICH + outData[0U] = 0xAEU; + + // SACCH + ::memset(temp, 0x00U, 20U); + temp[0U] = inData[12U]; + temp[1U] = inData[11U]; + temp[2U] = inData[14U]; + temp[3U] = inData[13U]; + CNXDNCRC::encodeCRC6(temp, 26U); + ::memcpy(outData + 1U, temp, 4U); + + // AMBE 1+2 + unsigned int n = 5U * 8U; + + temp[0U] = inData[16U]; + temp[1U] = inData[15U]; + temp[2U] = inData[18U]; + temp[3U] = inData[17U]; + temp[4U] = inData[20U]; + temp[5U] = inData[19U]; + temp[6U] = inData[22U]; + temp[7U] = inData[21U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + temp[0U] = inData[24U]; + temp[1U] = inData[23U]; + temp[2U] = inData[26U]; + temp[3U] = inData[25U]; + temp[4U] = inData[28U]; + temp[5U] = inData[27U]; + temp[6U] = inData[30U]; + temp[7U] = inData[29U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + // AMBE 3+4 + n = 19U * 8U; + + temp[0U] = inData[32U]; + temp[1U] = inData[31U]; + temp[2U] = inData[34U]; + temp[3U] = inData[33U]; + temp[4U] = inData[36U]; + temp[5U] = inData[35U]; + temp[6U] = inData[38U]; + temp[7U] = inData[37U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + temp[0U] = inData[40U]; + temp[1U] = inData[39U]; + temp[2U] = inData[42U]; + temp[3U] = inData[41U]; + temp[4U] = inData[44U]; + temp[5U] = inData[43U]; + temp[6U] = inData[46U]; + temp[7U] = inData[45U]; + + for (unsigned int i = 0U; i < 49U; i++, n++) { + bool b = READ_BIT(temp, i); + WRITE_BIT(outData, n, b); + } + + ::memcpy(inData, outData, 33U); + + return 33U; +} + +unsigned int CKenwoodNetwork::processKenwoodData(unsigned char* inData) +{ + if (inData[7U] != 0x09U && inData[7U] != 0x0BU && inData[7U] != 0x08U) + return 0U; + + unsigned char outData[50U]; + + if (inData[7U] == 0x09U || inData[7U] == 0x08U) { + outData[0U] = 0x90U; + outData[1U] = inData[8U]; + outData[2U] = inData[7U]; + outData[3U] = inData[10U]; + outData[4U] = inData[9U]; + outData[5U] = inData[12U]; + outData[6U] = inData[11U]; + ::memcpy(inData, outData, 7U); + return 7U; + } else { + outData[0U] = 0x90U; + outData[1U] = inData[8U]; + outData[2U] = inData[7U]; + outData[3U] = inData[10U]; + outData[4U] = inData[9U]; + outData[5U] = inData[12U]; + outData[6U] = inData[11U]; + outData[7U] = inData[14U]; + outData[8U] = inData[13U]; + outData[9U] = inData[16U]; + outData[10U] = inData[15U]; + outData[11U] = inData[18U]; + outData[12U] = inData[17U]; + outData[13U] = inData[20U]; + outData[14U] = inData[19U]; + outData[15U] = inData[22U]; + outData[16U] = inData[21U]; + outData[17U] = inData[24U]; + outData[18U] = inData[23U]; + outData[19U] = inData[26U]; + outData[20U] = inData[25U]; + outData[21U] = inData[28U]; + outData[22U] = inData[27U]; + outData[23U] = inData[29U]; + ::memcpy(inData, outData, 24U); + return 24U; + } +} + +unsigned long CKenwoodNetwork::getTimeStamp() const +{ + unsigned long timeStamp = 0UL; + +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + ::GetSystemTime(&st); + + unsigned int hh = st.wHour; + unsigned int mm = st.wMinute; + unsigned int ss = st.wSecond; + unsigned int ms = st.wMilliseconds; + + timeStamp += hh * 3600U * 1000U * 80U; + timeStamp += mm * 60U * 1000U * 80U; + timeStamp += ss * 1000U * 80U; + timeStamp += ms * 80U; +#else + struct timeval tod; + ::gettimeofday(&tod, NULL); + + unsigned int ss = tod.tv_sec; + unsigned int ms = tod.tv_usec / 1000U; + + timeStamp += ss * 1000U * 80U; + timeStamp += ms * 80U; +#endif + + return timeStamp; +} + +unsigned int CKenwoodNetwork::processKenwoodVoiceLateEntry(unsigned char* inData) +{ + assert(inData != NULL); + + unsigned char sacch[4U]; + sacch[0U] = inData[12U]; + sacch[1U] = inData[11U]; + sacch[2U] = inData[14U]; + sacch[3U] = inData[13U]; + + switch (sacch[0U] & 0xC0U) { + case 0xC0U: + if (!m_seen1) { + unsigned int offset = 0U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen1 = true; + } + break; + case 0x80U: + if (!m_seen2) { + unsigned int offset = 18U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen2 = true; + } + break; + case 0x40U: + if (!m_seen3) { + unsigned int offset = 36U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen3 = true; + } + break; + case 0x00U: + if (!m_seen4) { + unsigned int offset = 54U; + for (unsigned int i = 8U; i < 26U; i++, offset++) { + bool b = READ_BIT(sacch, i) != 0U; + WRITE_BIT(m_sacch, offset, b); + } + m_seen4 = true; + } + break; + } + + if (!m_seen1 || !m_seen2 || !m_seen3 || !m_seen4) + return 0U; + + // Create a dummy header + // Header SACCH + inData[11U] = 0x10U; + inData[12U] = 0x01U; + inData[13U] = 0x00U; + inData[14U] = 0x00U; + + // Header FACCH + inData[15U] = m_sacch[1U]; + inData[16U] = m_sacch[0U]; + inData[17U] = m_sacch[3U]; + inData[18U] = m_sacch[2U]; + inData[19U] = m_sacch[5U]; + inData[20U] = m_sacch[4U]; + inData[21U] = m_sacch[7U]; + inData[22U] = m_sacch[6U]; + inData[23U] = 0x00U; + inData[24U] = m_sacch[8U]; + + return processKenwoodVoiceHeader(inData); +} diff --git a/NXDNReflector/KenwoodNetwork.h b/NXDNReflector/KenwoodNetwork.h new file mode 100644 index 0000000..0543efe --- /dev/null +++ b/NXDNReflector/KenwoodNetwork.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009-2014,2016,2018,2020 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef KenwoodNetwork_H +#define KenwoodNetwork_H + +#include "CoreNetwork.h" +#include "UDPSocket.h" +#include "Timer.h" + +#include +#include + +class CKenwoodNetwork : public ICoreNetwork { +public: + CKenwoodNetwork(const std::string& address, bool debug); + virtual ~CKenwoodNetwork(); + + virtual bool open(); + + virtual bool write(const unsigned char* data, unsigned int length); + + virtual unsigned int read(unsigned char* data); + + virtual void close(); + + virtual void clock(unsigned int ms); + +private: + CUDPSocket m_rtpSocket; + CUDPSocket m_rtcpSocket; + in_addr m_address; + bool m_headerSeen; + bool m_seen1; + bool m_seen2; + bool m_seen3; + bool m_seen4; + unsigned char* m_sacch; + uint8_t m_sessionId; + uint16_t m_seqNo; + unsigned int m_ssrc; + bool m_debug; + uint32_t m_startSecs; + uint32_t m_startUSecs; + CTimer m_rtcpTimer; + CTimer m_hangTimer; + unsigned char m_hangType; + unsigned short m_hangSrc; + unsigned short m_hangDst; + + bool processIcomVoiceHeader(const unsigned char* data); + bool processIcomVoiceData(const unsigned char* data); + unsigned int processKenwoodVoiceHeader(unsigned char* data); + unsigned int processKenwoodVoiceData(unsigned char* data); + unsigned int processKenwoodVoiceLateEntry(unsigned char* data); + unsigned int processKenwoodData(unsigned char* data); + bool writeRTPVoiceHeader(const unsigned char* data); + bool writeRTPVoiceData(const unsigned char* data); + bool writeRTPVoiceTrailer(const unsigned char* data); + bool writeRTCPStart(); + bool writeRTCPPing(); + bool writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst); + bool writeRTCPHang(); + unsigned int readRTP(unsigned char* data); + unsigned int readRTCP(unsigned char* data); + unsigned long getTimeStamp() const; +}; + +#endif diff --git a/NXDNReflector/Makefile b/NXDNReflector/Makefile index 8cd4048..43c534b 100644 --- a/NXDNReflector/Makefile +++ b/NXDNReflector/Makefile @@ -4,7 +4,7 @@ CFLAGS = -g -O3 -Wall -std=c++0x -pthread LIBS = -lpthread LDFLAGS = -g -OBJECTS = Conf.o Log.o Mutex.o NXCoreNetwork.o NXDNLookup.o NXDNNetwork.o NXDNReflector.o StopWatch.o Thread.o Timer.o UDPSocket.o Utils.o +OBJECTS = Conf.o CoreNetwork.o IcomNetwork.o KenwoodNetwork.o Log.o Mutex.o NXDNCRC.o NXDNLookup.o NXDNNetwork.o NXDNReflector.o StopWatch.o Thread.o Timer.o UDPSocket.o Utils.o all: NXDNReflector diff --git a/NXDNReflector/NXDNCRC.cpp b/NXDNReflector/NXDNCRC.cpp new file mode 100644 index 0000000..60278ae --- /dev/null +++ b/NXDNReflector/NXDNCRC.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2018 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "NXDNCRC.h" + +#include +#include + +const uint8_t BIT_MASK_TABLE1[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE1[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE1[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE1[(i)&7]) + +bool CNXDNCRC::checkCRC6(const unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint8_t crc = createCRC6(in, length); + + uint8_t temp[1U]; + temp[0U] = 0x00U; + unsigned int j = length; + for (unsigned int i = 2U; i < 8U; i++, j++) { + bool b = READ_BIT1(in, j); + WRITE_BIT1(temp, i, b); + } + + return crc == temp[0U]; +} + +void CNXDNCRC::encodeCRC6(unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint8_t crc[1U]; + crc[0U] = createCRC6(in, length); + + unsigned int n = length; + for (unsigned int i = 2U; i < 8U; i++, n++) { + bool b = READ_BIT1(crc, i); + WRITE_BIT1(in, n, b); + } +} + +bool CNXDNCRC::checkCRC12(const unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint16_t crc = createCRC12(in, length); + uint8_t temp1[2U]; + temp1[0U] = (crc >> 8) & 0xFFU; + temp1[1U] = (crc >> 0) & 0xFFU; + + uint8_t temp2[2U]; + temp2[0U] = 0x00U; + temp2[1U] = 0x00U; + unsigned int j = length; + for (unsigned int i = 4U; i < 16U; i++, j++) { + bool b = READ_BIT1(in, j); + WRITE_BIT1(temp2, i, b); + } + + return temp1[0U] == temp2[0U] && temp1[1U] == temp2[1U]; +} + +void CNXDNCRC::encodeCRC12(unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint16_t crc = createCRC12(in, length); + + uint8_t temp[2U]; + temp[0U] = (crc >> 8) & 0xFFU; + temp[1U] = (crc >> 0) & 0xFFU; + + unsigned int n = length; + for (unsigned int i = 4U; i < 16U; i++, n++) { + bool b = READ_BIT1(temp, i); + WRITE_BIT1(in, n, b); + } +} + +bool CNXDNCRC::checkCRC15(const unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint16_t crc = createCRC15(in, length); + uint8_t temp1[2U]; + temp1[0U] = (crc >> 8) & 0xFFU; + temp1[1U] = (crc >> 0) & 0xFFU; + + uint8_t temp2[2U]; + temp2[0U] = 0x00U; + temp2[1U] = 0x00U; + unsigned int j = length; + for (unsigned int i = 1U; i < 16U; i++, j++) { + bool b = READ_BIT1(in, j); + WRITE_BIT1(temp2, i, b); + } + + return temp1[0U] == temp2[0U] && temp1[1U] == temp2[1U]; +} + +void CNXDNCRC::encodeCRC15(unsigned char* in, unsigned int length) +{ + assert(in != NULL); + + uint16_t crc = createCRC15(in, length); + + uint8_t temp[2U]; + temp[0U] = (crc >> 8) & 0xFFU; + temp[1U] = (crc >> 0) & 0xFFU; + + unsigned int n = length; + for (unsigned int i = 1U; i < 16U; i++, n++) { + bool b = READ_BIT1(temp, i); + WRITE_BIT1(in, n, b); + } +} + +uint8_t CNXDNCRC::createCRC6(const unsigned char* in, unsigned int length) +{ + uint8_t crc = 0x3FU; + + for (unsigned int i = 0U; i < length; i++) { + bool bit1 = READ_BIT1(in, i) != 0x00U; + bool bit2 = (crc & 0x20U) == 0x20U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x27U; + } + + return crc & 0x3FU; +} + +uint16_t CNXDNCRC::createCRC12(const unsigned char* in, unsigned int length) +{ + uint16_t crc = 0x0FFFU; + + for (unsigned int i = 0U; i < length; i++) { + bool bit1 = READ_BIT1(in, i) != 0x00U; + bool bit2 = (crc & 0x0800U) == 0x0800U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x080FU; + } + + return crc & 0x0FFFU; +} + +uint16_t CNXDNCRC::createCRC15(const unsigned char* in, unsigned int length) +{ + uint16_t crc = 0x7FFFU; + + for (unsigned int i = 0U; i < length; i++) { + bool bit1 = READ_BIT1(in, i) != 0x00U; + bool bit2 = (crc & 0x4000U) == 0x4000U; + + crc <<= 1; + + if (bit1 ^ bit2) + crc ^= 0x4CC5U; + } + + return crc & 0x7FFFU; +} diff --git a/NXDNReflector/NXDNCRC.h b/NXDNReflector/NXDNCRC.h new file mode 100644 index 0000000..12e2e40 --- /dev/null +++ b/NXDNReflector/NXDNCRC.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 by Jonathan Naylor G4KLX + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(NXDNCRC_H) +#define NXDNCRC_H + +#include + +class CNXDNCRC +{ +public: + static bool checkCRC6(const unsigned char* in, unsigned int length); + static void encodeCRC6(unsigned char* in, unsigned int length); + + static bool checkCRC12(const unsigned char* in, unsigned int length); + static void encodeCRC12(unsigned char* in, unsigned int length); + + static bool checkCRC15(const unsigned char* in, unsigned int length); + static void encodeCRC15(unsigned char* in, unsigned int length); + +private: + static uint8_t createCRC6(const unsigned char* in, unsigned int length); + static uint16_t createCRC12(const unsigned char* in, unsigned int length); + static uint16_t createCRC15(const unsigned char* in, unsigned int length); +}; + +#endif diff --git a/NXDNReflector/NXDNReflector.cpp b/NXDNReflector/NXDNReflector.cpp index 0e078a2..f8115f2 100644 --- a/NXDNReflector/NXDNReflector.cpp +++ b/NXDNReflector/NXDNReflector.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2016,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -16,8 +16,10 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "KenwoodNetwork.h" #include "NXDNReflector.h" #include "NXDNNetwork.h" +#include "IcomNetwork.h" #include "NXDNLookup.h" #include "StopWatch.h" #include "Version.h" @@ -410,6 +412,9 @@ void CNXDNReflector::run() dumpTimer.start(); } + if (m_nxCoreNetwork != NULL) + m_nxCoreNetwork->clock(ms); + if (ms < 5U) CThread::sleep(5U); } @@ -454,7 +459,11 @@ void CNXDNReflector::dumpRepeaters() const bool CNXDNReflector::openNXCore() { - m_nxCoreNetwork = new CNXCoreNetwork(m_conf.getNXCoreAddress(), m_conf.getNXCoreDebug()); + std::string protocol = m_conf.getNXCoreProtocol(); + if (protocol == "Kenwood") + m_nxCoreNetwork = new CKenwoodNetwork(m_conf.getNXCoreAddress(), m_conf.getNXCoreDebug()); + else + m_nxCoreNetwork = new CIcomNetwork(m_conf.getNXCoreAddress(), m_conf.getNXCoreDebug()); bool ret = m_nxCoreNetwork->open(); if (!ret) { delete m_nxCoreNetwork; diff --git a/NXDNReflector/NXDNReflector.h b/NXDNReflector/NXDNReflector.h index 4dcafe8..7b7ca65 100644 --- a/NXDNReflector/NXDNReflector.h +++ b/NXDNReflector/NXDNReflector.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2016,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -19,7 +19,7 @@ #if !defined(NXDNReflector_H) #define NXDNReflector_H -#include "NXCoreNetwork.h" +#include "CoreNetwork.h" #include "Timer.h" #include "Conf.h" @@ -65,7 +65,7 @@ public: private: CConf m_conf; - CNXCoreNetwork* m_nxCoreNetwork; + ICoreNetwork* m_nxCoreNetwork; std::vector m_repeaters; CNXDNRepeater* findRepeater(const in_addr& address, unsigned int port) const; diff --git a/NXDNReflector/NXDNReflector.ini b/NXDNReflector/NXDNReflector.ini index a59ccd5..bc0e4b6 100644 --- a/NXDNReflector/NXDNReflector.ini +++ b/NXDNReflector/NXDNReflector.ini @@ -19,6 +19,7 @@ Debug=0 [NXCore] Enabled=0 +Protocol=Icom # Address=208.111.3.45 Address=44.131.4.1 # TGEnable=1234 diff --git a/NXDNReflector/NXDNReflector.vcxproj b/NXDNReflector/NXDNReflector.vcxproj index d72db0b..73d79ac 100644 --- a/NXDNReflector/NXDNReflector.vcxproj +++ b/NXDNReflector/NXDNReflector.vcxproj @@ -20,7 +20,10 @@ - + + + + @@ -35,7 +38,10 @@ - + + + + @@ -52,32 +58,32 @@ {C68ABEB3-5CDD-4B26-8D66-77FE81EC6BB5} Win32Proj NXDNReflector - 10.0.16299.0 + 10.0 Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode diff --git a/NXDNReflector/NXDNReflector.vcxproj.filters b/NXDNReflector/NXDNReflector.vcxproj.filters index e361874..4f06cff 100644 --- a/NXDNReflector/NXDNReflector.vcxproj.filters +++ b/NXDNReflector/NXDNReflector.vcxproj.filters @@ -47,7 +47,16 @@ Header Files - + + Header Files + + + Header Files + + + Header Files + + Header Files @@ -85,7 +94,16 @@ Source Files - + + Source Files + + + Source Files + + + Source Files + + Source Files diff --git a/NXDNReflector/UDPSocket.cpp b/NXDNReflector/UDPSocket.cpp index 396f1f7..d651ab8 100644 --- a/NXDNReflector/UDPSocket.cpp +++ b/NXDNReflector/UDPSocket.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2016 by Jonathan Naylor G4KLX + * Copyright (C) 2006-2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -260,3 +260,31 @@ void CUDPSocket::close() ::close(m_fd); #endif } + +unsigned long CUDPSocket::getLocalAddress() const +{ + unsigned long address = 0UL; + + char hostname[80U]; + int ret = ::gethostname(hostname, 80); + if (ret == -1) + return 0UL; + + struct hostent* phe = ::gethostbyname(hostname); + if (phe == NULL) + return 0UL; + + if (phe->h_addrtype != AF_INET) + return 0UL; + + for (unsigned int i = 0U; phe->h_addr_list[i] != NULL; i++) { + struct in_addr addr; + ::memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); + if (addr.s_addr != INADDR_LOOPBACK) { + address = addr.s_addr; + break; + } + } + + return address; +} diff --git a/NXDNReflector/UDPSocket.h b/NXDNReflector/UDPSocket.h index e0af272..4c21a43 100644 --- a/NXDNReflector/UDPSocket.h +++ b/NXDNReflector/UDPSocket.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2011,2013,2015,2016,2020 by Jonathan Naylor G4KLX * * 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 @@ -47,7 +47,9 @@ public: void close(); - static in_addr lookup(const std::string& hostName); + unsigned long getLocalAddress() const; + + static in_addr lookup(const std::string& hostName); private: std::string m_address; diff --git a/NXDNReflector/Version.h b/NXDNReflector/Version.h index 2bd351f..0a67dd6 100644 --- a/NXDNReflector/Version.h +++ b/NXDNReflector/Version.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2018,2020 by Jonathan Naylor G4KLX * * 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 @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20180517"; +const char* VERSION = "20200427"; #endif diff --git a/README.md b/README.md index 6f32ee5..ed437e2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,28 @@ -These programs are clients for the NXDN networking now built into the MMDVM Host. +These programs are clients for the NXDN networking built into the MMDVM Host. -The Parrot is very simple minded and can only handle one client at a time and is therefore not suitable for use as a shared resource via the Internet. +The Parrot is very simple minded and can only handle one client at a time and +is therefore not suitable for use as a shared resource via the Internet. -The Reflector is used as a single talk group in the same way that it is with P25. It also includes the option to link it to NXCore to allow for interchange of audio between the two. At the NXCore end, it should be set up to receive the traffic from only one talk group. +The Reflector is used as a single talk group in the same way that it is with +P25. It also includes the option to link it to NXCore to allow for interchange +of audio between the two. At the NXCore end, it should be set up to receive the +traffic from only one talk group. -The Gateway allows for use of NXDN Talk Groups to control the access to the various NXDN reflectors. It speaks the same language as Icom repeaters to the MMDVM so could theoretically be used as a gateway for a real Icom NXDN repeater. This has not been tested. +The Gateway allows for use of NXDN Talk Groups to control the access to the +various NXDN reflectors. It speaks the same language as Icom repeaters to the +MMDVM so can be used as a gateway for Icom NXDN repeaters. It also +includes experimental support for Kenwood NXDN repeaters. -The Gateway has an ini file that contain the parameters for running the software. The filename of the ini file is passed as a parameter on the command line. The Parrot takes the UDP port number to listen on as an argument. +The Gateway has an ini file that contain the parameters for running the +software. The filename of the ini file is passed as a parameter on the command +line. The Parrot takes the UDP port number to listen on as an argument. -The MMDVM .ini file should have the IP address and port number of the client in the [NXDN Network] settings. +The MMDVM .ini file should have the IP address and port number of the client in +the [NXDN Network] settings. -They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64. +These programs build on 32-bit and 64-bit Linux as well as on Windows using +Visual Studio 2019 on x86 and x64. -This software is licenced under the GPL v2 and is intended for amateur and educational use only. Use of this software for commercial purposes is strictly forbidden. +This software is licenced under the GPL v2 and is intended for amateur and +educational use only. Use of this software for commercial purposes is strictly +forbidden. diff --git a/Update NXDNHosts.txt b/Update NXDNHosts.txt deleted file mode 100644 index d0ef425..0000000 --- a/Update NXDNHosts.txt +++ /dev/null @@ -1,2 +0,0 @@ -# 911 911 Cop Talk -911 NXDN.k2cop.com 41400