mirror of
https://github.com/ShaYmez/NXDNClients.git
synced 2025-02-03 09:44:25 -05:00
Add a basic Kenwood network handler.
This commit is contained in:
parent
f51115c1d0
commit
001bc4a836
@ -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
|
||||
@ -42,6 +42,7 @@ CConf::CConf(const std::string& file) :
|
||||
m_file(file),
|
||||
m_callsign(),
|
||||
m_suffix(),
|
||||
m_rptProtocol("Icom"),
|
||||
m_rptAddress(),
|
||||
m_rptPort(0U),
|
||||
m_myPort(0U),
|
||||
@ -143,7 +144,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);
|
||||
@ -248,6 +251,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;
|
||||
|
@ -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;
|
||||
@ -92,6 +93,7 @@ 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;
|
||||
|
@ -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,19 +84,26 @@ 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)
|
||||
bool 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;
|
||||
|
||||
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 false;
|
||||
}
|
||||
|
||||
// Invalid packet type?
|
||||
if (::memcmp(buffer, "ICOM", 4U) != 0)
|
||||
return false;
|
||||
|
@ -21,21 +21,20 @@
|
||||
|
||||
#include "RptNetwork.h"
|
||||
#include "UDPSocket.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
class CIcomNetwork : public IRptNetwork {
|
||||
public:
|
||||
CIcomNetwork(unsigned int localPort, bool debug);
|
||||
CIcomNetwork(unsigned int localPort, const std::string& rptAddress, unsigned int rptPort, bool debug);
|
||||
virtual ~CIcomNetwork();
|
||||
|
||||
virtual bool open();
|
||||
|
||||
virtual 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);
|
||||
|
||||
virtual bool read(unsigned char* data, in_addr& address, unsigned int& port);
|
||||
virtual bool read(unsigned char* data);
|
||||
|
||||
virtual void close();
|
||||
|
||||
|
644
NXDNGateway/KenwoodNetwork.cpp
Normal file
644
NXDNGateway/KenwoodNetwork.cpp
Normal file
@ -0,0 +1,644 @@
|
||||
/*
|
||||
* 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 <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
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_rtcpSocket(localPort + 1U),
|
||||
m_rtpSocket(localPort + 0U),
|
||||
m_stopWatch(),
|
||||
m_address(),
|
||||
m_rtcpPort(rptPort + 1U),
|
||||
m_rtpPort(rptPort + 0U),
|
||||
m_seqNo(0U),
|
||||
m_timeStamp(0U),
|
||||
m_ssrc(0U),
|
||||
m_debug(debug),
|
||||
m_timer(1000U, 0U, 200U)
|
||||
{
|
||||
assert(localPort > 0U);
|
||||
assert(!rptAddress.empty());
|
||||
assert(rptPort > 0U);
|
||||
|
||||
m_address = CUDPSocket::lookup(rptAddress);
|
||||
|
||||
::srand((unsigned int)m_stopWatch.time());
|
||||
}
|
||||
|
||||
CKenwoodNetwork::~CKenwoodNetwork()
|
||||
{
|
||||
}
|
||||
|
||||
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_timer.start();
|
||||
|
||||
m_ssrc = ::rand();
|
||||
|
||||
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];
|
||||
|
||||
switch (outData[4U] & 0x3FU) {
|
||||
case 0x01U: {
|
||||
uint16_t uid = (outData[7U] << 8) + (outData[8U] << 0);
|
||||
uint16_t gid = (outData[9U] << 8) + (outData[10U] << 0);
|
||||
return writeRTPVoiceHeader(outData);
|
||||
}
|
||||
case 0x08U:
|
||||
return writeRTPVoiceTrailer(outData);
|
||||
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;
|
||||
|
||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||
m_seqNo++;
|
||||
|
||||
m_timeStamp = (unsigned long)m_stopWatch.time();
|
||||
|
||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
||||
buffer[7U] = (m_timeStamp >> 0) & 0xFFU;
|
||||
m_timeStamp += 640U;
|
||||
|
||||
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[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;
|
||||
|
||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||
|
||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
||||
buffer[7U] = (m_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[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;
|
||||
|
||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||
m_seqNo++;
|
||||
|
||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
||||
buffer[7U] = (m_timeStamp >> 0) & 0xFFU;
|
||||
m_timeStamp += 640U;
|
||||
|
||||
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[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::writeRTCPPing()
|
||||
{
|
||||
unsigned char buffer[30U];
|
||||
::memset(buffer, 0x00U, 30U);
|
||||
|
||||
buffer[0U] = 0x8AU;
|
||||
buffer[1U] = 0xCCU;
|
||||
|
||||
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[22U] = 0x02U;
|
||||
|
||||
buffer[24U] = 0x01U;
|
||||
buffer[25U] = 0x01U;
|
||||
|
||||
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::writeRTCPData(unsigned short src, unsigned short dst)
|
||||
{
|
||||
unsigned char buffer[20U];
|
||||
::memset(buffer, 0x00U, 20U);
|
||||
|
||||
buffer[0U] = 0x8BU;
|
||||
buffer[1U] = 0xCCU;
|
||||
|
||||
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] = (src >> 8) & 0xFFU;
|
||||
buffer[13U] = (src >> 0) & 0xFFU;
|
||||
|
||||
buffer[14U] = (dst >> 8) & 0xFFU;
|
||||
buffer[15U] = (dst >> 0) & 0xFFU;
|
||||
|
||||
buffer[16U] = 0x01U;
|
||||
|
||||
if (m_debug)
|
||||
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 20U);
|
||||
|
||||
return m_rtcpSocket.write(buffer, 20U, m_address, m_rtcpPort);
|
||||
}
|
||||
|
||||
bool CKenwoodNetwork::read(unsigned char* data)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
unsigned char dummy[BUFFER_LENGTH];
|
||||
readRTCP(dummy);
|
||||
|
||||
unsigned int len = readRTP(data);
|
||||
|
||||
switch (data[9U]) {
|
||||
case 0x05U: { // Voice header or trailer
|
||||
bool ret = processKenwoodVoiceHeader(data);
|
||||
if (!ret)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
case 0x08U: // Voice data
|
||||
processKenwoodVoiceData(data);
|
||||
return true;
|
||||
default:
|
||||
CUtils::dump(5U, "Unknown data received from the Kenwood network", data, len);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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 || port != m_rtpPort) {
|
||||
LogMessage("Kenwood RTP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_rtpPort, port);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
if (m_debug)
|
||||
CUtils::dump(1U, "Kenwood Network RTP Data Received", buffer, length);
|
||||
|
||||
if (length != 47 && length != 59) {
|
||||
LogError("Invalid RTP length of %d", length);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
::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 || port != m_rtcpPort) {
|
||||
LogMessage("Kenwood RTCP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_rtcpPort, port);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
if (m_debug)
|
||||
CUtils::dump(1U, "Kenwood Network RTCP Data Received", buffer, length);
|
||||
|
||||
if (length != 20 && length != 28) {
|
||||
LogError("Invalid RTCP length of %d", length);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
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_timer.clock(ms);
|
||||
if (m_timer.hasExpired()) {
|
||||
writeRTCPPing();
|
||||
m_timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
bool 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[15U];
|
||||
temp[1U] = inData[14U];
|
||||
temp[2U] = inData[17U];
|
||||
temp[3U] = inData[16U];
|
||||
temp[4U] = inData[19U];
|
||||
temp[5U] = inData[18U];
|
||||
temp[6U] = inData[21U];
|
||||
temp[7U] = inData[20U];
|
||||
temp[8U] = inData[23U];
|
||||
temp[9U] = inData[22U];
|
||||
CNXDNCRC::encodeCRC12(temp, 80U);
|
||||
::memcpy(outData + 5U, temp, 12U);
|
||||
::memcpy(outData + 19U, temp, 12U);
|
||||
|
||||
switch (outData[5U] & 0x3FU) {
|
||||
case 0x01U: {
|
||||
unsigned short uid = (outData[8U] << 8) + (outData[9U] << 0);
|
||||
unsigned short gid = (outData[10U] << 8) + (outData[11U] << 0);
|
||||
::memcpy(inData, outData, 33U);
|
||||
return true;
|
||||
}
|
||||
case 0x08U:
|
||||
::memcpy(inData, outData, 33U);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void 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[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);
|
||||
}
|
||||
|
||||
// AMBE 3+4
|
||||
n = 19U * 8U;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
::memcpy(inData, outData, 33U);
|
||||
}
|
71
NXDNGateway/KenwoodNetwork.h
Normal file
71
NXDNGateway/KenwoodNetwork.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 "StopWatch.h"
|
||||
#include "UDPSocket.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
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 bool read(unsigned char* data);
|
||||
|
||||
virtual void close();
|
||||
|
||||
virtual void clock(unsigned int ms);
|
||||
|
||||
private:
|
||||
CUDPSocket m_rtpSocket;
|
||||
CUDPSocket m_rtcpSocket;
|
||||
CStopWatch m_stopWatch;
|
||||
in_addr m_address;
|
||||
unsigned int m_rtcpPort;
|
||||
unsigned int m_rtpPort;
|
||||
unsigned short m_seqNo;
|
||||
unsigned long m_timeStamp;
|
||||
unsigned int m_ssrc;
|
||||
bool m_debug;
|
||||
CTimer m_timer;
|
||||
|
||||
bool processIcomVoiceHeader(const unsigned char* data);
|
||||
bool processIcomVoiceData(const unsigned char* data);
|
||||
bool processKenwoodVoiceHeader(unsigned char* data);
|
||||
void processKenwoodVoiceData(unsigned char* data);
|
||||
bool writeRTPVoiceHeader(const unsigned char* data);
|
||||
bool writeRTPVoiceData(const unsigned char* data);
|
||||
bool writeRTPVoiceTrailer(const unsigned char* data);
|
||||
bool writeRTCPPing();
|
||||
bool writeRTCPData(unsigned short src, unsigned short dst);
|
||||
unsigned int readRTP(unsigned char* data);
|
||||
unsigned int readRTCP(unsigned char* data);
|
||||
};
|
||||
|
||||
#endif
|
@ -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 \
|
||||
RptNetwork.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
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "KenwoodNetwork.h"
|
||||
#include "IcomNetwork.h"
|
||||
#include "NXDNNetwork.h"
|
||||
#include "NXDNGateway.h"
|
||||
@ -114,7 +115,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);
|
||||
}
|
||||
|
||||
@ -175,12 +177,16 @@ void CNXDNGateway::run()
|
||||
}
|
||||
#endif
|
||||
|
||||
in_addr rptAddr = CUDPSocket::lookup(m_conf.getRptAddress());
|
||||
unsigned int rptPort = m_conf.getRptPort();
|
||||
|
||||
createGPS();
|
||||
|
||||
IRptNetwork* localNetwork = new CIcomNetwork(m_conf.getMyPort(), m_conf.getRptDebug());
|
||||
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();
|
||||
@ -274,7 +280,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
|
||||
@ -283,7 +289,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)) {
|
||||
@ -383,7 +389,7 @@ startupId = 9999U;
|
||||
if (voice != NULL) {
|
||||
unsigned int length = voice->read(buffer);
|
||||
if (length > 0U)
|
||||
localNetwork->write(buffer, length, rptAddr, rptPort);
|
||||
localNetwork->write(buffer, length);
|
||||
}
|
||||
|
||||
unsigned int ms = stopWatch.elapsed();
|
||||
|
@ -1,6 +1,7 @@
|
||||
[General]
|
||||
Callsign=G4KLX
|
||||
Suffix=NXDN
|
||||
Protocol=Icom
|
||||
RptAddress=127.0.0.1
|
||||
RptPort=14021
|
||||
LocalPort=14020
|
||||
|
@ -151,6 +151,7 @@
|
||||
<ClInclude Include="Conf.h" />
|
||||
<ClInclude Include="GPSHandler.h" />
|
||||
<ClInclude Include="IcomNetwork.h" />
|
||||
<ClInclude Include="KenwoodNetwork.h" />
|
||||
<ClInclude Include="Log.h" />
|
||||
<ClInclude Include="Mutex.h" />
|
||||
<ClInclude Include="NXDNCRC.h" />
|
||||
@ -175,6 +176,7 @@
|
||||
<ClCompile Include="Conf.cpp" />
|
||||
<ClCompile Include="GPSHandler.cpp" />
|
||||
<ClCompile Include="IcomNetwork.cpp" />
|
||||
<ClCompile Include="KenwoodNetwork.cpp" />
|
||||
<ClCompile Include="Log.cpp" />
|
||||
<ClCompile Include="Mutex.cpp" />
|
||||
<ClCompile Include="NXDNCRC.cpp" />
|
||||
|
@ -77,6 +77,9 @@
|
||||
<ClInclude Include="RptNetwork.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="KenwoodNetwork.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Conf.cpp">
|
||||
@ -139,5 +142,8 @@
|
||||
<ClCompile Include="RptNetwork.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="KenwoodNetwork.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -41,9 +41,9 @@ public:
|
||||
|
||||
virtual bool open() = 0;
|
||||
|
||||
virtual bool write(const unsigned char* data, unsigned int length, const in_addr& address, unsigned int port) = 0;
|
||||
virtual bool write(const unsigned char* data, unsigned int length) = 0;
|
||||
|
||||
virtual bool read(unsigned char* data, in_addr& address, unsigned int& port) = 0;
|
||||
virtual bool read(unsigned char* data) = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
|
@ -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 = "20200406";
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user