/* * 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 "NXDNKenwoodNetwork.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; CNXDNKenwoodNetwork::CNXDNKenwoodNetwork(const std::string& localAddress, unsigned int localPort, const std::string& gwyAddress, unsigned int gwyPort, bool debug) : m_rtpSocket(localAddress, localPort + 0U), m_rtcpSocket(localAddress, localPort + 1U), m_rtcpAddr(), m_rtpAddr(), m_rtcpAddrLen(0U), m_rtpAddrLen(0U), m_enabled(false), 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), m_random() { assert(localPort > 0U); assert(!gwyAddress.empty()); assert(gwyPort > 0U); m_sacch = new unsigned char[10U]; if (CUDPSocket::lookup(gwyAddress, gwyPort + 1U, m_rtcpAddr, m_rtcpAddrLen) != 0) m_rtcpAddrLen = 0U; if (CUDPSocket::lookup(gwyAddress, gwyPort + 0U, m_rtpAddr, m_rtpAddrLen) != 0) m_rtpAddrLen = 0U; std::random_device rd; std::mt19937 mt(rd()); m_random = mt; } CNXDNKenwoodNetwork::~CNXDNKenwoodNetwork() { delete[] m_sacch; } bool CNXDNKenwoodNetwork::open() { if (m_rtcpAddrLen == 0U || m_rtpAddrLen == 0U) { LogError("Unable to resolve the address of the NXDN Gateway"); return false; } LogMessage("Opening Kenwood connection"); if (!m_rtcpSocket.open(m_rtcpAddr)) return false; if (!m_rtpSocket.open(m_rtpAddr)) { m_rtcpSocket.close(); return false; } std::uniform_int_distribution dist(0x00000001, 0xfffffffe); m_ssrc = dist(m_random); return true; } bool CNXDNKenwoodNetwork::write(const unsigned char* data, NXDN_NETWORK_MESSAGE_TYPE type) { assert(data != NULL); switch (type) { case NNMT_VOICE_HEADER: // Voice header or trailer case NNMT_VOICE_TRAILER: return processIcomVoiceHeader(data); case NNMT_VOICE_BODY: // Voice data return processIcomVoiceData(data); case NNMT_DATA_HEADER: // Data header or trailer case NNMT_DATA_TRAILER: return processIcomDataHeader(data); case NNMT_DATA_BODY: // Data data return processIcomDataData(data); default: return false; } } bool CNXDNKenwoodNetwork::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 CNXDNKenwoodNetwork::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 CNXDNKenwoodNetwork::processIcomDataHeader(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 0x09U: m_hangTimer.stop(); m_rtcpTimer.start(); writeRTCPStart(); return writeRTPDataHeader(outData); case 0x08U: { m_hangTimer.start(); bool ret = writeRTPDataTrailer(outData); writeRTCPHang(type, src, dst); return ret; } default: return false; } } bool CNXDNKenwoodNetwork::processIcomDataData(const unsigned char* inData) { assert(inData != NULL); unsigned char outData[40U]; ::memset(outData, 0x00U, 40U); outData[0U] = inData[2U]; outData[1U] = inData[1U]; outData[2U] = inData[4U]; outData[3U] = inData[3U]; outData[4U] = inData[6U]; outData[5U] = inData[5U]; outData[6U] = inData[8U]; outData[7U] = inData[7U]; outData[8U] = inData[10U]; outData[9U] = inData[9U]; outData[10U] = inData[12U]; outData[11U] = inData[11U]; outData[12U] = inData[14U]; outData[13U] = inData[13U]; outData[14U] = inData[16U]; outData[15U] = inData[15U]; outData[16U] = inData[18U]; outData[17U] = inData[17U]; outData[18U] = inData[20U]; outData[19U] = inData[19U]; outData[20U] = inData[22U]; outData[21U] = inData[21U]; return writeRTPDataData(outData); } bool CNXDNKenwoodNetwork::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_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::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_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::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_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::writeRTPDataHeader(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] = 0x01U; buffer[17U] = 0x01U; ::memcpy(buffer + 18U, data, 24U); if (m_debug) CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 42U); return m_rtpSocket.write(buffer, 42U, m_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::writeRTPDataTrailer(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] = 0x01U; buffer[17U] = 0x06U; ::memcpy(buffer + 18U, data, 24U); if (m_debug) CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 42U); return m_rtpSocket.write(buffer, 42U, m_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::writeRTPDataData(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] = 0x01U; buffer[17U] = 0x01U; ::memcpy(buffer + 18U, data, 24U); if (m_debug) CUtils::dump(1U, "Kenwood Network RTP Data Sent", buffer, 42U); return m_rtpSocket.write(buffer, 42U, m_rtpAddr, m_rtpAddrLen); } bool CNXDNKenwoodNetwork::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_rtcpAddr, m_rtcpAddrLen); } bool CNXDNKenwoodNetwork::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_rtcpAddr, m_rtcpAddrLen); } bool CNXDNKenwoodNetwork::writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst) { m_hangType = type; m_hangSrc = src; m_hangDst = dst; return writeRTCPHang(); } bool CNXDNKenwoodNetwork::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_rtcpAddr, m_rtcpAddrLen); } bool CNXDNKenwoodNetwork::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 false; 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 false; } } unsigned int CNXDNKenwoodNetwork::readRTP(unsigned char* data) { assert(data != NULL); unsigned char buffer[BUFFER_LENGTH]; sockaddr_storage address; unsigned int addrLen; int length = m_rtpSocket.read(buffer, BUFFER_LENGTH, address, addrLen); if (length <= 0) return 0U; if (!CUDPSocket::match(m_rtpAddr, address, IMT_ADDRESS_ONLY)) { LogMessage("NXDN, RTP packet received from an invalid source"); return 0U; } if (!m_enabled) 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 CNXDNKenwoodNetwork::readRTCP(unsigned char* data) { assert(data != NULL); unsigned char buffer[BUFFER_LENGTH]; sockaddr_storage address; unsigned int addrlen; int length = m_rtcpSocket.read(buffer, BUFFER_LENGTH, address, addrlen); if (length <= 0) return 0U; if (!CUDPSocket::match(m_rtpAddr, address, IMT_ADDRESS_ONLY)) { LogMessage("NXDN, RTCP packet received from an invalid source"); return 0U; } if (!m_enabled) 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 CNXDNKenwoodNetwork::reset() { m_rtcpTimer.stop(); m_hangTimer.stop(); m_headerSeen = false; m_seen1 = false; m_seen2 = false; m_seen3 = false; m_seen4 = false; } bool CNXDNKenwoodNetwork::isConnected() const { return (m_enabled && (m_rtcpAddrLen != 0U) && (m_rtpAddrLen != 0U)); } void CNXDNKenwoodNetwork::close() { m_rtcpSocket.close(); m_rtpSocket.close(); LogMessage("Closing Kenwood connection"); } void CNXDNKenwoodNetwork::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(); } } bool CNXDNKenwoodNetwork::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 true; case 0x08U: ::memcpy(inData, outData, 33U); m_headerSeen = false; m_seen1 = false; m_seen2 = false; m_seen3 = false; m_seen4 = false; return true; default: return false; } } bool CNXDNKenwoodNetwork::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 true; } bool CNXDNKenwoodNetwork::processKenwoodData(unsigned char* inData) { if (inData[7U] != 0x09U && inData[7U] != 0x0BU && inData[7U] != 0x08U) return false; 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]; 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]; ::memcpy(inData, outData, 20U); return true; } 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 true; } } unsigned long CNXDNKenwoodNetwork::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; } bool CNXDNKenwoodNetwork::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 false; // 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); } void CNXDNKenwoodNetwork::enable(bool enabled) { if (enabled && !m_enabled) reset(); m_enabled = enabled; }