[EXPERIMENTAL] implement experimental NXDN CC transmit stream (no incoming data processing, yet)...;
parent
99d1de340e
commit
b8200f8a2b
@ -0,0 +1,406 @@
|
||||
/**
|
||||
* Digital Voice Modem - Host Software
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Host Software
|
||||
*
|
||||
*/
|
||||
//
|
||||
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
|
||||
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||
//
|
||||
/*
|
||||
* Copyright (C) 2022 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* 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 "nxdn/NXDNDefines.h"
|
||||
#include "nxdn/lc/RCCH.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace nxdn;
|
||||
using namespace nxdn::lc;
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a copy instance of the RCCH class.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
RCCH::RCCH(const RCCH& data) : RCCH(SiteData())
|
||||
{
|
||||
copy(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RCCH class.
|
||||
/// </summary>
|
||||
/// <param name="siteData"></param>
|
||||
/// <param name="entry"></param>
|
||||
RCCH::RCCH(SiteData siteData, lookups::IdenTable entry) : RCCH(siteData)
|
||||
{
|
||||
m_siteIdenEntry = entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RCCH class.
|
||||
/// </summary>
|
||||
/// <param name="siteData"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="verbose"></param>
|
||||
RCCH::RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose) : RCCH(siteData)
|
||||
{
|
||||
m_verbose = verbose;
|
||||
m_siteIdenEntry = entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes a instance of RCCH class.
|
||||
/// </summary>
|
||||
RCCH::~RCCH()
|
||||
{
|
||||
delete[] m_data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equals operator.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
RCCH& RCCH::operator=(const RCCH& data)
|
||||
{
|
||||
if (&data != this) {
|
||||
::memcpy(m_data, data.m_data, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
decodeLC(m_data);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode call link control data.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns>True, if RCCH was decoded, otherwise false.</returns>
|
||||
void RCCH::decode(const uint8_t* data, uint32_t length, uint32_t offset)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
for (uint32_t i = 0U; i < length; i++, offset++) {
|
||||
bool b = READ_BIT(data, i);
|
||||
WRITE_BIT(m_data, offset, b);
|
||||
}
|
||||
|
||||
if (m_verbose) {
|
||||
Utils::dump(2U, "Decoded RCCH Data", m_data, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
decodeLC(m_data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode call link control data.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <param name="offset"></param>
|
||||
void RCCH::encode(uint8_t* data, uint32_t length, uint32_t offset)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
encodeLC(m_data);
|
||||
|
||||
for (uint32_t i = 0U; i < length; i++, offset++) {
|
||||
bool b = READ_BIT(m_data, offset);
|
||||
WRITE_BIT(data, i, b);
|
||||
}
|
||||
|
||||
if (m_verbose) {
|
||||
Utils::dump(2U, "Encoded RCCH Data", data, length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void RCCH::reset()
|
||||
{
|
||||
::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
|
||||
m_messageType = MESSAGE_TYPE_IDLE;
|
||||
|
||||
m_srcId = 0U;
|
||||
m_dstId = 0U;
|
||||
|
||||
m_locId = 0U;
|
||||
m_regOption = 0U;
|
||||
|
||||
m_version = 0U;
|
||||
|
||||
m_causeRsp = NXDN_CAUSE_MM_NORMAL_1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw layer 3 data.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
void RCCH::getData(uint8_t* data) const
|
||||
{
|
||||
::memcpy(data, m_data, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the raw layer 3 data.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="length"></param>
|
||||
void RCCH::setData(const uint8_t* data, uint32_t length)
|
||||
{
|
||||
::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
::memcpy(m_data, data, length);
|
||||
|
||||
decodeLC(m_data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the callsign.
|
||||
/// </summary>
|
||||
/// <param name="callsign">Callsign.</param>
|
||||
void RCCH::setCallsign(std::string callsign)
|
||||
{
|
||||
uint32_t idLength = callsign.length();
|
||||
if (idLength > 0) {
|
||||
::memset(m_siteCallsign, 0x20U, NXDN_CALLSIGN_LENGTH_BYTES);
|
||||
|
||||
if (idLength > NXDN_CALLSIGN_LENGTH_BYTES)
|
||||
idLength = NXDN_CALLSIGN_LENGTH_BYTES;
|
||||
for (uint32_t i = 0; i < idLength; i++)
|
||||
m_siteCallsign[i] = callsign[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// ---------------------------------------------------------------------------
|
||||
// Private Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RCCH class.
|
||||
/// </summary>
|
||||
RCCH::RCCH() : RCCH(SiteData())
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RCCH class.
|
||||
/// </summary>
|
||||
/// <param name="siteData"></param>
|
||||
RCCH::RCCH(SiteData siteData) :
|
||||
m_verbose(false),
|
||||
m_messageType(MESSAGE_TYPE_IDLE),
|
||||
m_srcId(0U),
|
||||
m_dstId(0U),
|
||||
m_locId(0U),
|
||||
m_regOption(0U),
|
||||
m_version(0U),
|
||||
m_causeRsp(NXDN_CAUSE_MM_NORMAL_1),
|
||||
m_siteData(siteData),
|
||||
m_siteIdenEntry(),
|
||||
m_data(NULL)
|
||||
{
|
||||
m_data = new uint8_t[NXDN_RCCH_LC_LENGTH_BYTES];
|
||||
::memset(m_data, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
|
||||
m_siteCallsign = new uint8_t[NXDN_CALLSIGN_LENGTH_BYTES];
|
||||
::memset(m_siteCallsign, 0x00U, NXDN_CALLSIGN_LENGTH_BYTES);
|
||||
setCallsign(siteData.callsign());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode link control.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
bool RCCH::decodeLC(const uint8_t* data)
|
||||
{
|
||||
if (m_verbose) {
|
||||
Utils::dump(2U, "Decoded RCCH", data, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
m_messageType = data[0U] & 0x3FU; // Message Type
|
||||
|
||||
// message type opcodes
|
||||
switch (m_messageType) {
|
||||
case MESSAGE_TYPE_IDLE:
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_REG:
|
||||
m_regOption = data[1U] >> 3; // Registration Option
|
||||
m_locId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Location ID
|
||||
m_srcId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Source Radio Address
|
||||
m_dstId = (uint16_t)((data[6U] << 8) | data[7U]) & 0xFFFFU; // Target Radio Address
|
||||
// bryanb: maybe process subscriber type? (byte 8 and 9)
|
||||
m_version = data[10U]; // Version
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_REG_C:
|
||||
m_regOption = data[1U] >> 3; // Registration Option
|
||||
m_locId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Location ID
|
||||
m_srcId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Source Radio Address
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_GRP_REG:
|
||||
m_regOption = data[1U]; // Group Registration Option
|
||||
m_srcId = (uint16_t)((data[2U] << 8) | data[3U]) & 0xFFFFU; // Source Radio Address
|
||||
m_dstId = (uint16_t)((data[4U] << 8) | data[5U]) & 0xFFFFU; // Target Radio Address
|
||||
break;
|
||||
default:
|
||||
LogError(LOG_NXDN, "RCCH::decodeRCCH(), unknown RCCH value, messageType = $%02X", m_messageType);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode link control.
|
||||
/// </summary>
|
||||
/// <param name="rs"></param>
|
||||
void RCCH::encodeLC(uint8_t* data)
|
||||
{
|
||||
m_messageType = m_data[0U] & 0x3FU; // Message Type
|
||||
|
||||
// message type opcodes
|
||||
switch (m_messageType) {
|
||||
case MESSAGE_TYPE_IDLE:
|
||||
break;
|
||||
case MESSAGE_TYPE_DST_ID_INFO:
|
||||
m_data[1U] = 0xC0U + NXDN_CALLSIGN_LENGTH_BYTES; // Station ID Option - Start / End / Character Count
|
||||
m_data[2U] = (m_siteCallsign[0]); // Character 0
|
||||
for (uint8_t i = 1; i < NXDN_CALLSIGN_LENGTH_BYTES; i++) {
|
||||
m_data[i + 2U] = m_siteCallsign[i]; // Character 1 - 7
|
||||
}
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_SITE_INFO:
|
||||
{
|
||||
m_data[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID
|
||||
m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ...
|
||||
m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
|
||||
// bryanb: this is currently fixed -- maybe dynamic in the future
|
||||
m_data[4U] = (1 << 6) + // Channel Structure - Number of BCCH (1)
|
||||
(1 << 3) + // ... - Number of Grouping (1)
|
||||
(1 << 0); // ... - Number of Paging Frames (1)
|
||||
m_data[5U] = (1 << 0); // ... - Number of Iteration (1)
|
||||
|
||||
m_data[6U] = m_siteData.serviceClass(); // Service Information
|
||||
m_data[7U] = (m_siteData.netActive() ? NXDN_SIF2_IP_NETWORK : 0x00U); // ...
|
||||
|
||||
// bryanb: this is currently fixed -- maybe dynamic in the future
|
||||
m_data[8U] = 0U; // Restriction Information - No access restriction / No cycle restriction
|
||||
m_data[9U] = 0x08U; // ... - No group restriction / GMS; Location Registration Restriction
|
||||
m_data[10U] = (!m_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO
|
||||
|
||||
// bryanb: this is currently fixed -- maybe dynamic in the future
|
||||
m_data[11U] = NXDN_CH_ACCESS_BASE_FREQ_SYS_DEFINED; // Channel Access Information - Channel Version / Sys Defined Step / Sys Defined Base Freq
|
||||
|
||||
m_data[14U] = 1U; // Version
|
||||
|
||||
uint32_t channelNo = m_siteData.channelNo();
|
||||
m_data[15U] = (channelNo >> 6) & 0x0FU; // 1st Control Channel
|
||||
m_data[16U] = (channelNo & 0x3FU) << 2; // ...
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_SRV_INFO:
|
||||
m_data[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID
|
||||
m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ...
|
||||
m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
m_data[4U] = m_siteData.serviceClass(); // Service Information
|
||||
m_data[5U] = (m_siteData.netActive() ? NXDN_SIF2_IP_NETWORK : 0x00U); // ...
|
||||
|
||||
// bryanb: this is currently fixed -- maybe dynamic in the future
|
||||
m_data[6U] = 0U; // Restriction Information - No access restriction / No cycle restriction
|
||||
m_data[7U] = 0x08U; // ... - No group restriction / GMS; Location Registration Restriction
|
||||
m_data[8U] = (!m_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_REG:
|
||||
m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ...
|
||||
m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
m_data[4U] = (m_srcId >> 8U) & 0xFFU; // Source Radio Address
|
||||
m_data[5U] = (m_srcId >> 0U) & 0xFFU; // ...
|
||||
m_data[6U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address
|
||||
m_data[7U] = (m_dstId >> 0U) & 0xFFU; // ...
|
||||
m_data[8U] = m_causeRsp; // Cause (MM)
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_REG_C:
|
||||
m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID
|
||||
m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address
|
||||
m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ...
|
||||
m_data[6U] = m_causeRsp; // Cause (MM)
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_REG_COMM:
|
||||
m_data[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID
|
||||
m_data[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address
|
||||
m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ...
|
||||
break;
|
||||
case RCCH_MESSAGE_TYPE_GRP_REG:
|
||||
m_data[2U] = (m_srcId >> 8U) & 0xFFU; // Source Radio Address
|
||||
m_data[3U] = (m_srcId >> 0U) & 0xFFU; // ...
|
||||
m_data[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address
|
||||
m_data[5U] = (m_dstId >> 0U) & 0xFFU; // ...
|
||||
m_data[6U] = m_causeRsp; // Cause (MM)
|
||||
m_data[8U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID
|
||||
m_data[9U] = (m_siteData.locId() >> 0) & 0xFFU; // ...
|
||||
break;
|
||||
default:
|
||||
LogError(LOG_NXDN, "RCCH::encodeRCCH(), unknown RCCH value, messageType = $%02X", m_messageType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_verbose) {
|
||||
Utils::dump(2U, "Encoded RCCH", m_data, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Internal helper to copy the the class.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
void RCCH::copy(const RCCH& data)
|
||||
{
|
||||
m_data = new uint8_t[22U];
|
||||
::memcpy(m_data, data.m_data, 22U);
|
||||
|
||||
m_verbose = data.m_verbose;
|
||||
|
||||
m_siteData = data.m_siteData;
|
||||
m_siteIdenEntry = data.m_siteIdenEntry;
|
||||
|
||||
delete[] m_siteCallsign;
|
||||
|
||||
uint8_t* callsign = new uint8_t[NXDN_CALLSIGN_LENGTH_BYTES];
|
||||
::memcpy(callsign, data.m_siteCallsign, NXDN_CALLSIGN_LENGTH_BYTES);
|
||||
|
||||
m_siteCallsign = callsign;
|
||||
|
||||
decodeLC(m_data);
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Digital Voice Modem - Host Software
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Host Software
|
||||
*
|
||||
*/
|
||||
//
|
||||
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
|
||||
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||
//
|
||||
/*
|
||||
* Copyright (C) 2022 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* 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(__NXDN_LC__RCCH_H__)
|
||||
#define __NXDN_LC__RCCH_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "nxdn/lc/PacketInformation.h"
|
||||
#include "nxdn/SiteData.h"
|
||||
#include "lookups/IdenTableLookup.h"
|
||||
|
||||
namespace nxdn
|
||||
{
|
||||
namespace lc
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// Represents link control data for control channel NXDN calls.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API RCCH {
|
||||
public:
|
||||
/// <summary>Initializes a copy instance of the RCCH class.</summary>
|
||||
RCCH(const RCCH& data);
|
||||
/// <summary>Initializes a new instance of the TSBK class.</summary>
|
||||
RCCH(SiteData siteData, lookups::IdenTable entry);
|
||||
/// <summary>Initializes a new instance of the TSBK class.</summary>
|
||||
RCCH(SiteData siteData, lookups::IdenTable entry, bool verbose);
|
||||
/// <summary>Finalizes a instance of the RCCH class.</summary>
|
||||
~RCCH();
|
||||
|
||||
/// <summary>Equals operator.</summary>
|
||||
RCCH& operator=(const RCCH& data);
|
||||
|
||||
/// <summary>Decode layer 3 data.</summary>
|
||||
void decode(const uint8_t* data, uint32_t length, uint32_t offset = 0U);
|
||||
/// <summary>Encode layer 3 data.</summary>
|
||||
void encode(uint8_t* data, uint32_t length, uint32_t offset = 0U);
|
||||
|
||||
/// <summary></summary>
|
||||
void reset();
|
||||
|
||||
/// <summary>Gets the raw layer 3 data.</summary>
|
||||
void getData(uint8_t* data) const;
|
||||
/// <summary>Sets the raw layer 3 data.</summary>
|
||||
void setData(const uint8_t* data, uint32_t length);
|
||||
|
||||
/// <summary>Sets the callsign.</summary>
|
||||
void setCallsign(std::string callsign);
|
||||
|
||||
public:
|
||||
/// <summary>Flag indicating verbose log output.</summary>
|
||||
__PROPERTY(bool, verbose, Verbose);
|
||||
|
||||
/** Common Data */
|
||||
/// <summary>Message Type</summary>
|
||||
__PROPERTY(uint8_t, messageType, MessageType);
|
||||
|
||||
/// <summary>Source ID.</summary>
|
||||
__PROPERTY(uint16_t, srcId, SrcId);
|
||||
/// <summary>Destination ID.</summary>
|
||||
__PROPERTY(uint16_t, dstId, DstId);
|
||||
|
||||
/// <summary>Location ID.</summary>
|
||||
__PROPERTY(uint32_t, locId, LocId);
|
||||
/// <summary>Registration Option.</summary>
|
||||
__PROPERTY(uint8_t, regOption, RegOption);
|
||||
|
||||
/// <summary>Version Number.</summary>
|
||||
__PROPERTY(uint8_t, version, Version);
|
||||
|
||||
/// <summary>Cause Response.</summary>
|
||||
__PROPERTY(uint8_t, causeRsp, CauseResponse);
|
||||
|
||||
/** Local Site data */
|
||||
/// <summary>Local Site Data.</summary>
|
||||
__PROPERTY_PLAIN(SiteData, siteData, siteData);
|
||||
/// <summary>Local Site Identity Entry.</summary>
|
||||
__PROPERTY_PLAIN(lookups::IdenTable, siteIdenEntry, siteIdenEntry);
|
||||
|
||||
private:
|
||||
/// <summary>Initializes a new instance of the RCCH class.</summary>
|
||||
RCCH();
|
||||
/// <summary>Initializes a new instance of the RCCH class.</summary>
|
||||
RCCH(SiteData siteData);
|
||||
|
||||
uint8_t* m_data;
|
||||
|
||||
/** Local Site data */
|
||||
uint8_t* m_siteCallsign;
|
||||
|
||||
/// <summary>Decode link control.</summary>
|
||||
bool decodeLC(const uint8_t* data);
|
||||
/// <summary>Encode link control.</summary>
|
||||
void encodeLC(uint8_t* data);
|
||||
|
||||
/// <summary>Internal helper to copy the class.</summary>
|
||||
void copy(const RCCH& data);
|
||||
};
|
||||
} // namespace lc
|
||||
} // namespace nxdn
|
||||
|
||||
#endif // __NXDN_LC__RCCH_H__
|
||||
@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Digital Voice Modem - Host Software
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Host Software
|
||||
*
|
||||
*/
|
||||
//
|
||||
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
|
||||
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||
//
|
||||
/*
|
||||
* Copyright (C) 2022 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* 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 "Defines.h"
|
||||
#include "nxdn/NXDNDefines.h"
|
||||
#include "nxdn/channel/CAC.h"
|
||||
#include "nxdn/packet/Trunk.h"
|
||||
#include "nxdn/acl/AccessControl.h"
|
||||
#include "nxdn/Sync.h"
|
||||
#include "edac/CRC.h"
|
||||
#include "HostMain.h"
|
||||
#include "Log.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace nxdn;
|
||||
using namespace nxdn::packet;
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Resets the data states for the RF interface.
|
||||
/// </summary>
|
||||
void Trunk::resetRF()
|
||||
{
|
||||
lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH);
|
||||
m_rfLC = lc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the data states for the network.
|
||||
/// </summary>
|
||||
void Trunk::resetNet()
|
||||
{
|
||||
lc::RCCH lc = lc::RCCH(m_nxdn->m_siteData, m_nxdn->m_idenEntry, m_dumpRCCH);
|
||||
m_netLC = lc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a data frame from the RF interface.
|
||||
/// </summary>
|
||||
/// <param name="fct">Functional channel type.</param>
|
||||
/// <param name="option">Channel options.</param>
|
||||
/// <param name="data">Buffer containing data frame.</param>
|
||||
/// <param name="len">Length of data frame.</param>
|
||||
/// <returns></returns>
|
||||
bool Trunk::process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
channel::CAC cac;
|
||||
bool validCAC = cac.decode(data + 2U);
|
||||
if (m_nxdn->m_rfState == RS_RF_LISTENING && !validCAC)
|
||||
return false;
|
||||
|
||||
if (validCAC) {
|
||||
uint8_t ran = cac.getRAN();
|
||||
if (ran != m_nxdn->m_ran && ran != 0U)
|
||||
return false;
|
||||
}
|
||||
|
||||
// The layer3 data will only be correct if valid is true
|
||||
uint8_t buffer[NXDN_CAC_LENGTH_BYTES];
|
||||
cac.getData(buffer);
|
||||
|
||||
m_rfLC.decode(buffer, NXDN_CAC_SHORT_IN_CRC_BITS);
|
||||
|
||||
// TODO TODO -- process incoming data
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a data frame from the RF interface.
|
||||
/// </summary>
|
||||
/// <param name="fct">Functional channel type.</param>
|
||||
/// <param name="option">Channel options.</param>
|
||||
/// <param name="netLC"></param>
|
||||
/// <param name="data">Buffer containing data frame.</param>
|
||||
/// <param name="len">Length of data frame.</param>
|
||||
/// <returns></returns>
|
||||
bool Trunk::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
if (m_nxdn->m_netState == RS_NET_IDLE) {
|
||||
m_nxdn->m_queue.clear();
|
||||
|
||||
resetRF();
|
||||
resetNet();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the processor by the passed number of milliseconds.
|
||||
/// </summary>
|
||||
/// <param name="ms"></param>
|
||||
void Trunk::clock(uint32_t ms)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protected Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Trunk class.
|
||||
/// </summary>
|
||||
/// <param name="nxdn">Instance of the Control class.</param>
|
||||
/// <param name="network">Instance of the BaseNetwork class.</param>
|
||||
/// <param name="dumpRCCHData">Flag indicating whether RCCH data is dumped to the log.</param>
|
||||
/// <param name="debug">Flag indicating whether NXDN debug is enabled.</param>
|
||||
/// <param name="verbose">Flag indicating whether NXDN verbose logging is enabled.</param>
|
||||
Trunk::Trunk(Control* nxdn, network::BaseNetwork* network, bool dumpRCCHData, bool debug, bool verbose) :
|
||||
m_nxdn(nxdn),
|
||||
m_network(network),
|
||||
m_rfLC(SiteData(), lookups::IdenTable()),
|
||||
m_netLC(SiteData(), lookups::IdenTable()),
|
||||
m_lastRejectId(0U),
|
||||
m_dumpRCCH(dumpRCCHData),
|
||||
m_verbose(verbose),
|
||||
m_debug(debug)
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes a instance of the Trunk class.
|
||||
/// </summary>
|
||||
Trunk::~Trunk()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data processed from RF to the network.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="len"></param>
|
||||
void Trunk::writeNetwork(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
assert(data != NULL);
|
||||
|
||||
if (m_network == NULL)
|
||||
return;
|
||||
|
||||
if (m_nxdn->m_rfTimeout.isRunning() && m_nxdn->m_rfTimeout.hasExpired())
|
||||
return;
|
||||
|
||||
m_network->writeNXDN(m_nxdn->m_rfLC, data, len);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to write control channel packet data.
|
||||
/// </summary>
|
||||
/// <param name="frameCnt"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <param name="adjSS"></param>
|
||||
void Trunk::writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS)
|
||||
{
|
||||
uint8_t i = 0U, seqCnt = 0U;
|
||||
|
||||
if (!m_nxdn->m_control)
|
||||
return;
|
||||
|
||||
// don't add any frames if the queue is full
|
||||
uint8_t len = NXDN_FRAME_LENGTH_BYTES + 2U;
|
||||
uint32_t space = m_nxdn->m_queue.freeSpace();
|
||||
if (space < (len + 1U)) {
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (m_debug) {
|
||||
LogDebug(LOG_DMR, "writeRF_ControlData, frameCnt = %u, seq = %u", frameCnt, n);
|
||||
}
|
||||
|
||||
switch (n)
|
||||
{
|
||||
case 6:
|
||||
writeRF_CC_Site_Info();
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
writeRF_CC_Service_Info();
|
||||
break;
|
||||
}
|
||||
|
||||
if (seqCnt > 0U)
|
||||
n++;
|
||||
i++;
|
||||
} while (i <= seqCnt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to write a CC SITE_INFO broadcast packet on the RF interface.
|
||||
/// </summary>
|
||||
void Trunk::writeRF_CC_Site_Info()
|
||||
{
|
||||
if (m_debug) {
|
||||
LogMessage(LOG_RF, "NXDN, RCCH_MESSAGE_TYPE_SITE_INFO (Site Information)");
|
||||
}
|
||||
|
||||
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
|
||||
::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES);
|
||||
|
||||
Sync::addNXDNSync(data + 2U);
|
||||
|
||||
channel::LICH lich;
|
||||
lich.setRFCT(NXDN_LICH_RFCT_RCCH);
|
||||
lich.setFCT(NXDN_LICH_CAC_OUTBOUND);
|
||||
lich.setOption(NXDN_LICH_DATA_NORMAL);
|
||||
lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND);
|
||||
lich.encode(data + 2U);
|
||||
|
||||
uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES];
|
||||
::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
|
||||
m_rfLC.setMessageType(RCCH_MESSAGE_TYPE_SITE_INFO);
|
||||
m_rfLC.encode(buffer, NXDN_CAC_OUT_CRC_BITS);
|
||||
|
||||
channel::CAC cac;
|
||||
cac.setRAN(m_nxdn->m_ran);
|
||||
cac.setData(buffer);
|
||||
cac.encode(data + 2U);
|
||||
|
||||
data[0U] = modem::TAG_DATA;
|
||||
data[1U] = 0x00U;
|
||||
|
||||
m_nxdn->scrambler(data + 2U);
|
||||
|
||||
if (m_nxdn->m_duplex) {
|
||||
m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to write a CC SRV_INFO broadcast packet on the RF interface.
|
||||
/// </summary>
|
||||
void Trunk::writeRF_CC_Service_Info()
|
||||
{
|
||||
if (m_debug) {
|
||||
LogMessage(LOG_RF, "NXDN, MESSAGE_TYPE_SRV_INFO (Service Information)");
|
||||
}
|
||||
|
||||
uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U];
|
||||
::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES);
|
||||
|
||||
Sync::addNXDNSync(data + 2U);
|
||||
|
||||
channel::LICH lich;
|
||||
lich.setRFCT(NXDN_LICH_RFCT_RCCH);
|
||||
lich.setFCT(NXDN_LICH_CAC_OUTBOUND);
|
||||
lich.setOption(NXDN_LICH_DATA_NORMAL);
|
||||
lich.setDirection(NXDN_LICH_DIRECTION_OUTBOUND);
|
||||
lich.encode(data + 2U);
|
||||
|
||||
uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES];
|
||||
::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES);
|
||||
|
||||
m_rfLC.setMessageType(MESSAGE_TYPE_SRV_INFO);
|
||||
m_rfLC.encode(buffer, NXDN_CAC_OUT_CRC_BITS);
|
||||
|
||||
channel::CAC cac;
|
||||
cac.setRAN(m_nxdn->m_ran);
|
||||
cac.setData(buffer);
|
||||
cac.encode(data + 2U);
|
||||
|
||||
data[0U] = modem::TAG_DATA;
|
||||
data[1U] = 0x00U;
|
||||
|
||||
m_nxdn->scrambler(data + 2U);
|
||||
|
||||
if (m_nxdn->m_duplex) {
|
||||
m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Digital Voice Modem - Host Software
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Host Software
|
||||
*
|
||||
*/
|
||||
//
|
||||
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
|
||||
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||
//
|
||||
/*
|
||||
* Copyright (C) 2022 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* 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(__NXDN_PACKET_TRUNK_H__)
|
||||
#define __NXDN_PACKET_TRUNK_H__
|
||||
|
||||
#include "Defines.h"
|
||||
#include "nxdn/Control.h"
|
||||
#include "nxdn/lc/RCCH.h"
|
||||
#include "network/BaseNetwork.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace nxdn
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Prototypes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API Control;
|
||||
|
||||
namespace packet
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// This class implements handling logic for NXDN control signalling packets.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API Trunk {
|
||||
public:
|
||||
/// <summary>Resets the data states for the RF interface.</summary>
|
||||
virtual void resetRF();
|
||||
/// <summary>Resets the data states for the network.</summary>
|
||||
virtual void resetNet();
|
||||
|
||||
/// <summary>Process a data frame from the RF interface.</summary>
|
||||
virtual bool process(uint8_t fct, uint8_t option, uint8_t* data, uint32_t len);
|
||||
/// <summary>Process a data frame from the network.</summary>
|
||||
virtual bool processNetwork(uint8_t fct, uint8_t option, lc::RTCH& netLC, uint8_t* data, uint32_t len);
|
||||
|
||||
/// <summary>Updates the processor by the passed number of milliseconds.</summary>
|
||||
void clock(uint32_t ms);
|
||||
|
||||
protected:
|
||||
friend class nxdn::Control;
|
||||
Control* m_nxdn;
|
||||
|
||||
network::BaseNetwork* m_network;
|
||||
|
||||
lc::RCCH m_rfLC;
|
||||
lc::RCCH m_netLC;
|
||||
|
||||
uint16_t m_lastRejectId;
|
||||
|
||||
bool m_dumpRCCH;
|
||||
|
||||
bool m_verbose;
|
||||
bool m_debug;
|
||||
|
||||
/// <summary>Initializes a new instance of the Trunk class.</summary>
|
||||
Trunk(Control* nxdn, network::BaseNetwork* network, bool dumpRCCHData, bool debug, bool verbose);
|
||||
/// <summary>Finalizes a instance of the Trunk class.</summary>
|
||||
virtual ~Trunk();
|
||||
|
||||
/// <summary>Write data processed from RF to the network.</summary>
|
||||
void writeNetwork(const uint8_t* data, uint32_t len);
|
||||
|
||||
/// <summary>Helper to write control channel packet data.</summary>
|
||||
void writeRF_ControlData(uint8_t frameCnt, uint8_t n, bool adjSS);
|
||||
|
||||
/// <summary>Helper to write a CC SITE_INFO broadcast packet on the RF interface.</summary>
|
||||
void writeRF_CC_Site_Info();
|
||||
/// <summary>Helper to write a CC SRV_INFO broadcast packet on the RF interface.</summary>
|
||||
void writeRF_CC_Service_Info();
|
||||
};
|
||||
} // namespace packet
|
||||
} // namespace nxdn
|
||||
|
||||
#endif // __NXDN_PACKET_TRUNK_H__
|
||||
Loading…
Reference in new issue