Basic implementation of dvmdfsi in dvmhost C++ ecosystem (#59)

* initial bootstrap of CPP dvmdfsi

* more work on serial service, the basics are there now

* updated TODOs

* more work on dfsi, getting there, just a few more things to implement

* rough code finished, totally untested, hope it works

* fixes for malloc errors, still not totally working

* almost working, P25 voice from FNE is garbled but we're getting there

* verified v24 decode/encode is working, still cleaning up serial TX

* dvmdfsi ready for beta testing! A few gremlins to find but it works.

* added configurable source flag option

* fixed diu source flag config entry name

* small update to Mot VHDR1

* fixed serial initialization, flags work now, config & code cleanup

* log cleanups, added basic call collision logic, fixed kid being truncated to uint8_t

* fixed LDU2 MI not getting copied properly

* bring add-dvmdfsi up-to-date with master;

* Add syslog and some project file cleanup (#58)

* initial bootstrap of CPP dvmdfsi

* more work on serial service, the basics are there now

* updated TODOs

* more work on dfsi, getting there, just a few more things to implement

* rough code finished, totally untested, hope it works

* fixes for malloc errors, still not totally working

* almost working, P25 voice from FNE is garbled but we're getting there

* verified v24 decode/encode is working, still cleaning up serial TX

* dvmdfsi ready for beta testing! A few gremlins to find but it works.

* added configurable source flag option

* fixed diu source flag config entry name

* small update to Mot VHDR1

* fixed serial initialization, flags work now, config & code cleanup

* log cleanups, added basic call collision logic, fixed kid being truncated to uint8_t

* fixed LDU2 MI not getting copied properly

* add support for syslog logging in dvmdfsi;

* add useSyslog parameter to log section of configuration file;

* project cleanup: split MotRtpFrames.cpp/.h into separate files properly; ensure decode() functions pass the pointer as const to prevent accidental modification of input buffer; move common enums to a separate RtpDefines.h header; reuse MotRtpFrames.h (renamed RtpFrames.h) as a quick way of including all the RTP frames;

---------

Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com>

---------

Co-authored-by: W3AXL <29879554+W3AXL@users.noreply.github.com>
Co-authored-by: Bryan Biedenkapp <gatekeep@gmail.com>
pull/61/head
Patrick W3AXL 2 years ago committed by GitHub
parent 243696855c
commit 0bbc69d237
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
.gitignore vendored

@ -2,6 +2,7 @@
build/
dvmcmd
dvmhost
dvmdfsi
# Ignore CMake temporary files
CMakeLists.txt.user

@ -230,6 +230,7 @@ endif (ENABLE_TESTS)
install(TARGETS dvmhost DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dvmcmd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dvmfne DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dvmdfsi DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
install(TARGETS dvmmon DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
@ -251,12 +252,14 @@ if (NOT TARGET strip)
COMMAND arm-linux-gnueabihf-strip -s dvmhost
COMMAND arm-linux-gnueabihf-strip -s dvmfne
COMMAND arm-linux-gnueabihf-strip -s dvmcmd
COMMAND arm-linux-gnueabihf-strip -s dvmmon)
COMMAND arm-linux-gnueabihf-strip -s dvmmon
COMMAND arm-linux-gnueabihf-strip -s dvmdfsi)
else()
add_custom_target(strip
COMMAND arm-linux-gnueabihf-strip -s dvmhost
COMMAND arm-linux-gnueabihf-strip -s dvmfne
COMMAND arm-linux-gnueabihf-strip -s dvmcmd)
COMMAND arm-linux-gnueabihf-strip -s dvmcmd
COMMAND arm-linux-gnueabihf-strip -s dvmdfsi)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
elseif (CROSS_COMPILE_AARCH64)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
@ -264,24 +267,28 @@ if (NOT TARGET strip)
COMMAND aarch64-linux-gnu-strip -s dvmhost
COMMAND aarch64-linux-gnu-strip -s dvmfne
COMMAND aarch64-linux-gnu-strip -s dvmcmd
COMMAND aarch64-linux-gnu-strip -s dvmmon)
COMMAND aarch64-linux-gnu-strip -s dvmmon
COMMAND aarch64-linux-gnu-strip -s dvmdfsi)
else()
add_custom_target(strip
COMMAND aarch64-linux-gnu-strip -s dvmhost
COMMAND aarch64-linux-gnu-strip -s dvmfne
COMMAND aarch64-linux-gnu-strip -s dvmcmd)
COMMAND aarch64-linux-gnu-strip -s dvmcmd
COMMAND aarch64-linux-gnu-strip -s dvmdfsi)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
elseif (CROSS_COMPILE_RPI_ARM)
if (NOT WITH_RPI_ARM_TOOLS)
add_custom_target(strip
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd)
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmdfsi)
else()
add_custom_target(strip
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmhost
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmfne
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd)
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmcmd
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmdfsi)
endif ()
else()
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
@ -289,12 +296,14 @@ if (NOT TARGET strip)
COMMAND strip -s dvmhost
COMMAND strip -s dvmfne
COMMAND strip -s dvmcmd
COMMAND strip -s dvmmon)
COMMAND strip -s dvmmon
COMMAND strip -s dvmdfsi)
else()
add_custom_target(strip
COMMAND strip -s dvmhost
COMMAND strip -s dvmfne
COMMAND strip -s dvmcmd)
COMMAND strip -s dvmcmd
COMMAND strip -s dvmdfsi)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR))
endif (CROSS_COMPILE_ARM)
endif (NOT TARGET strip)
@ -320,6 +329,7 @@ if (NOT TARGET tarball)
COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmmon ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmdfsi ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp ../tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND chmod +x ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/*.sh
COMMAND cp -v ../configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
@ -348,6 +358,7 @@ if (NOT TARGET tarball)
COMMAND cp -v dvmhost ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmdfsi ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp ../tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
COMMAND chmod +x ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/*.sh
COMMAND cp -v ../configs/*.yml ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
@ -386,6 +397,7 @@ add_custom_target(old_install
COMMAND install -m 755 dvmcmd ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmmon ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmfne ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmdfsi ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 644 ../configs/config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml
COMMAND install -m 644 ../configs/fne-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/fne-config.example.yml
COMMAND install -m 644 ../configs/monitor-config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/monitor-config.example.yml

@ -0,0 +1,99 @@
#
# Digital Voice Modem - DFSI Software Configuration
#
# @package DVM / DFSI Software
#
# Flag indicating whether the host will run as a background or foreground task.
daemon: false
#
# Network Configuration
#
network:
# Textual Name
identity: DFSI
# Network Peer ID
peerId: 9000123
# Hostname/IP address of FNE master to connect to.
address: 127.0.0.1
# Port number to connect to.
port: 62031
# FNE access password.
password: RPT1234
# Additional debug logging
debug: false
# Encrypted endpoint networking
encrypted: false
# Pre-shared key for encrypted networking (AES-256 32-byte hex keystring)
presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
#
# DFSI Configuration
#
dfsi:
# Mode
# 1 - UDP DFSI to DVM FNE (TODO: Implement this)
# 2 - Serial DFSI to DVM FNE (Using DVM-V24 adapter board)
# 3 - Serial DFSI to UDP DFSI (TODO: Implement this)
mode: 2
# Flag enabling "the" Manufacturer standard of RTP.
theManufacturer: false
# P25 buffer size in bytes (don't change this unless you have a good reason to)
# Default of 3060 is 10 full LDU1/2s worth of data
p25BufferSize: 3060
udp:
# Time in seconds between heartbets to DFSI peers.
heartbeat: 5
# Flag disabling control connection establishment.
noConnectionEstablishment: false
# Local DFSI RTP Port number.
localRtpPort: 27500
# Remote RFSS DFSI Hostname/IP address of FNE master to connect to.
remoteDfsiAddress: 127.0.0.2
# Remote DFSI Control Port number to connect to.
remoteControlPort: 27000
# Remote DFSI RTP Port number to connect to.
remoteRtpPort: 27500
serial:
# Serial configuration for serial DFSI
port: "/dev/ttyACM0"
baudrate: 115200
# RT/RT flag enabled (0x02) or disabled (0x04)
rtrt: false
# Use the DIU source flag (0x00) instead of the quantar source flag (0x02)
diu: false
# Jitter buffer length in ms
jitter: 200
# Debug logging
debug: false
# Trace logging (prints lots of data)
trace: false
#
# Logging Configuration
# Logging Levels:
# 1 - Debug
# 2 - Message
# 3 - Informational
# 4 - Warning
# 5 - Error
# 6 - Fatal
#
log:
# Console display logging level (used when in foreground).
displayLevel: 1
# File logging level.
fileLevel: 1
# Flag indicating file logs should be sent to syslog instead of a file.
useSyslog: false
# Full path for the directory to store the log files.
filePath: .
# Full path for the directory to store the activity log files.
activityFilePath: .
# Log filename prefix.
fileRoot: dvmdfsi

@ -84,3 +84,11 @@ include(src/remote/CMakeLists.txt)
add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC})
target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote)
#
## dvmdfsi
#
include(src/dfsi/CMakeLists.txt)
add_executable(dvmdfsi ${common_INCLUDE} ${dvmdfsi_SRC})
target_link_libraries(dvmdfsi PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads)
target_include_directories(dvmdfsi PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/dfsi)

@ -33,6 +33,7 @@
#define LOG_DMR "DMR"
#define LOG_CAL "CAL"
#define LOG_SETUP "SETUP"
#define LOG_SERIAL "SERIAL"
// ---------------------------------------------------------------------------
// Macros

@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#include "ActivityLog.h"
#include "common/network/BaseNetwork.h"
#include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork()
#include <sys/time.h>
#if defined(CATCH2_TEST_COMPILATION)
#include <catch2/catch_test_macros.hpp>
#endif
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <ctime>
#include <cassert>
#include <cstring>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define EOL "\r\n"
const uint32_t ACT_LOG_BUFFER_LEN = 501U;
// ---------------------------------------------------------------------------
// Global Variables
// ---------------------------------------------------------------------------
static std::string m_actFilePath;
static std::string m_actFileRoot;
static FILE* m_actFpLog = nullptr;
static struct tm m_actTm;
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/// <summary>
/// Helper to open the activity log file, file handle.
/// </summary>
/// <returns>True, if log file is opened, otherwise false.
static bool ActivityLogOpen()
{
if (CurrentLogFileLevel() == 0U)
return true;
time_t now;
::time(&now);
struct tm* tm = ::gmtime(&now);
if (tm->tm_mday == m_actTm.tm_mday && tm->tm_mon == m_actTm.tm_mon && tm->tm_year == m_actTm.tm_year) {
if (m_actFpLog != nullptr)
return true;
}
else {
if (m_actFpLog != nullptr)
::fclose(m_actFpLog);
}
char filename[200U];
::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", LogGetFilePath().c_str(), LogGetFileRoot().c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
m_actFpLog = ::fopen(filename, "a+t");
m_actTm = *tm;
return m_actFpLog != nullptr;
}
/// <summary>
/// Initializes the activity log.
/// </summary>
/// <param name="filePath">Full-path to the activity log file.</param>
/// <param name="fileRoot">Prefix of the activity log file name.</param>
bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot)
{
#if defined(CATCH2_TEST_COMPILATION)
return true;
#endif
m_actFilePath = filePath;
m_actFileRoot = fileRoot;
return ::ActivityLogOpen();
}
/// <summary>
/// Finalizes the activity log.
/// </summary>
void ActivityLogFinalise()
{
#if defined(CATCH2_TEST_COMPILATION)
return;
#endif
if (m_actFpLog != nullptr)
::fclose(m_actFpLog);
}
/// <summary>
/// Writes a new entry to the activity log.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="msg">Formatted string to write to activity log.</param>
void ActivityLog(const char* msg, ...)
{
#if defined(CATCH2_TEST_COMPILATION)
return;
#endif
assert(msg != nullptr);
char buffer[ACT_LOG_BUFFER_LEN];
struct timeval now;
::gettimeofday(&now, NULL);
struct tm* tm = ::gmtime(&now.tv_sec);
::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U);
va_list vl, vl_len;
va_start(vl, msg);
va_copy(vl_len, vl);
size_t len = ::vsnprintf(nullptr, 0U, msg, vl_len);
::vsnprintf(buffer + ::strlen(buffer), len + 1U, msg, vl);
va_end(vl_len);
va_end(vl);
bool ret = ::ActivityLogOpen();
if (!ret)
return;
if (LogGetNetwork() != nullptr) {
network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork();;
network->writeActLog(buffer);
}
if (CurrentLogFileLevel() == 0U)
return;
::fprintf(m_actFpLog, "%s\n", buffer);
::fflush(m_actFpLog);
if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) {
::fprintf(stdout, "%s" EOL, buffer);
::fflush(stdout);
}
}

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__ACTIVITY_LOG_H__)
#define __ACTIVITY_LOG_H__
#include "Defines.h"
#include <string>
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/// <summary>Initializes the activity log.</summary>
extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot);
/// <summary>Finalizes the activity log.</summary>
extern HOST_SW_API void ActivityLogFinalise();
/// <summary>Writes a new entry to the activity log.</summary>
extern HOST_SW_API void ActivityLog(const char* msg, ...);
#endif // __ACTIVITY_LOG_H__

@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-only
#/**
#* Digital Voice Modem - 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 / DFSI peer application
#* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
#* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
#*
#* Copyright (C) 2024 Patrick McDonnell, W3AXL
#*
#*/
file(GLOB dvmdfsi_SRC
# Modem libs for serial communication
"src/host/modem/*.h"
"src/host/modem/*.cpp"
"src/host/modem/port/*.h"
"src/host/modem/port/*.cpp"
# Core network libs
"src/host/network/Network.h"
"src/host/network/Network.cpp"
# DFSI network libs
"src/dfsi/network/*.h"
"src/dfsi/network/*.cpp"
# DFSI rtp libs
"src/dfsi/rtp/*.h"
"src/dfsi/rtp/*.cpp"
# Core DFSI
"src/dfsi/*.h"
"src/dfsi/*.cpp"
)

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__DEFINES_H__)
#define __DEFINES_H__
#include "common/Defines.h"
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#undef __PROG_NAME__
#define __PROG_NAME__ "Digital Voice Modem (DVM) DFSI Peer"
#undef __EXE_NAME__
#define __EXE_NAME__ "dvmdfsi"
#undef __NETVER__
#define __NETVER__ "DFSI" VERSION_MAJOR VERSION_REV VERSION_MINOR
#undef DEFAULT_CONF_FILE
#define DEFAULT_CONF_FILE "dfsi-config.yml"
#undef DEFAULT_LOCK_FILE
#define DEFAULT_LOCK_FILE "/tmp/dvmdfsi.lock"
#endif // __DEFINES_H__

@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#include "Defines.h"
#include "common/dmr/DMRDefines.h"
#include "common/p25/P25Utils.h"
#include "common/network/udp/Socket.h"
#include "common/Log.h"
#include "common/StopWatch.h"
#include "common/Thread.h"
#include "common/Utils.h"
#include "host/ActivityLog.h"
#include "Dfsi.h"
#include "DfsiMain.h"
using namespace network;
using namespace lookups;
#include <cstdio>
#include <algorithm>
#include <functional>
#include <random>
#include <unistd.h>
#include <pwd.h>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define IDLE_WARMUP_MS 5U
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the HostTest class.
/// </summary>
/// <param name="confFile">Full-path to the configuration file.</param>
Dfsi::Dfsi(const std::string& confFile) :
m_confFile(confFile),
m_conf(),
m_network(nullptr),
m_ridLookup(nullptr),
m_tidLookup(nullptr),
m_pingTime(5U),
m_maxMissedPings(5U),
m_updateLookupTime(10U),
m_debug(false),
m_repeatTraffic(true),
m_serial(nullptr)
{
/* stub */
}
/// <summary>
/// Finalizes a instance of the HostTest class.
/// </summary>
Dfsi::~Dfsi() = default;
/// <summary>
/// Executes the main FNE processing loop.
/// </summary>
/// <returns>Zero if successful, otherwise error occurred.</returns>
int Dfsi::run()
{
bool ret = false;
// Try and parse the config yaml
try {
ret = yaml::Parse(m_conf, m_confFile.c_str());
if (!ret) {
::fatal("cannot read the configuration file, %s\n", m_confFile.c_str());
}
}
catch (yaml::OperationException const& e) {
::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message());
}
// Check if we should run as a daemn or not
bool m_daemon = m_conf["daemon"].as<bool>(false);
if (m_daemon && g_foreground)
m_daemon = false;
// initialize system logging
yaml::Node logConf = m_conf["log"];
ret = ::LogInitialise(logConf["filePath"].as<std::string>(), logConf["fileRoot"].as<std::string>(),
logConf["fileLevel"].as<uint32_t>(0U), logConf["displayLevel"].as<uint32_t>(0U), false, logConf["useSyslog"].as<bool>(false));
if (!ret) {
::fatal("unable to open the log file\n");
}
// Init activity logging
ret = ::ActivityLogInitialise(logConf["activityFilePath"].as<std::string>(), logConf["fileRoot"].as<std::string>());
if (!ret) {
::fatal("unable to open the activity log file\n");
}
// handle POSIX process forking
if (m_daemon) {
// create new process
pid_t pid = ::fork();
if (pid == -1) {
::fprintf(stderr, "%s: Couldn't fork() , exiting\n", g_progExe.c_str());
::LogFinalise();
return EXIT_FAILURE;
}
else if (pid != 0) {
::LogFinalise();
exit(EXIT_SUCCESS);
}
// create new session and process group
if (::setsid() == -1) {
::fprintf(stderr, "%s: Couldn't setsid(), exiting\n", g_progExe.c_str());
::LogFinalise();
return EXIT_FAILURE;
}
// set the working directory to the root directory
if (::chdir("/") == -1) {
::fprintf(stderr, "%s: Couldn't cd /, exiting\n", g_progExe.c_str());
::LogFinalise();
return EXIT_FAILURE;
}
::close(STDIN_FILENO);
::close(STDOUT_FILENO);
::close(STDERR_FILENO);
}
::LogInfo(__BANNER__ "\r\n" __PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
"Copyright (c) 2024 DVMProject (https://github.com/dvmproject) Authors.\r\n" \
">> DFSI Network Peer\r\n");
// read base parameters from configuration
ret = readParams();
if (!ret)
return EXIT_FAILURE;
// initialize peer networking
ret = createPeerNetwork();
if (!ret)
return EXIT_FAILURE;
::LogInfoEx(LOG_HOST, "DFSI peer network is up and running");
// Read DFSI config
yaml::Node dfsi_conf = m_conf["dfsi"];
uint32_t p25BufferSize = dfsi_conf["p25BufferSize"].as<uint32_t>();
// Read serial config
yaml::Node serial_conf = dfsi_conf["serial"];
std::string port = serial_conf["port"].as<std::string>();
uint32_t baudrate = serial_conf["baudrate"].as<uint32_t>();
bool rtrt = serial_conf["rtrt"].as<bool>();
bool diu = serial_conf["diu"].as<bool>();
uint16_t jitter = serial_conf["jitter"].as<uint16_t>();
bool serial_debug = serial_conf["debug"].as<bool>();
bool serial_trace = serial_conf["trace"].as<bool>();
LogInfo("Serial Parameters");
LogInfo(" Port: %s", port.c_str());
LogInfo(" Baudrate: %u", baudrate);
LogInfo(" RT/RT: %s", rtrt ? "Enabled" : "Disabled");
LogInfo(" DIU Flag: %s", diu ? "Enabled" : "Disabled");
LogInfo(" Jitter Size: %u ms", jitter);
LogInfo(" Debug: %s", serial_debug ? "Enabled" : "Disabled");
LogInfo(" Trace: %s", serial_trace ? "Enabled" : "Disabled");
// Create serial service
m_serial = new SerialService(port, baudrate, rtrt, diu, jitter, m_network, p25BufferSize, p25BufferSize, serial_debug, serial_trace);
// Open serial
ret = m_serial->open();
if (!ret)
return EXIT_FAILURE;
StopWatch stopWatch;
stopWatch.start();
///
/// main execution loop
///
while (!g_killed) {
uint32_t ms = stopWatch.elapsed();
ms = stopWatch.elapsed();
stopWatch.start();
// ------------------------------------------------------
// -- Network RX Clocking --
// ------------------------------------------------------
if (m_network != nullptr)
m_network->clock(ms);
uint32_t length = 0U;
bool netReadRet = false;
UInt8Array p25Buffer = m_network->readP25(netReadRet, length);
if (netReadRet) {
uint8_t duid = p25Buffer[22U];
uint8_t MFId = p25Buffer[15U];
uint8_t lco = p25Buffer[4U];
uint32_t srcId = __GET_UINT16(p25Buffer, 5U);
uint32_t dstId = __GET_UINT16(p25Buffer, 8U);
if (!g_hideMessages)
LogMessage(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length);
// Send the data to the serial handler
m_serial->processP25FromNet(std::move(p25Buffer), length);
}
// We keep DMR & NXDN in so nothing breaks, even though DFSI doesn't do DMR or NXDNS
UInt8Array dmrBuffer = m_network->readDMR(netReadRet, length);
if (netReadRet) {
uint8_t seqNo = dmrBuffer[4U];
uint32_t srcId = __GET_UINT16(dmrBuffer, 5U);
uint32_t dstId = __GET_UINT16(dmrBuffer, 8U);
uint8_t flco = (dmrBuffer[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP;
uint32_t slotNo = (dmrBuffer[15U] & 0x80U) == 0x80U ? 2U : 1U;
if (!g_hideMessages)
LogMessage(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length);
}
UInt8Array nxdnBuffer = m_network->readNXDN(netReadRet, length);
if (netReadRet) {
uint8_t messageType = nxdnBuffer[4U];
uint32_t srcId = __GET_UINT16(nxdnBuffer, 5U);
uint32_t dstId = __GET_UINT16(nxdnBuffer, 8U);
if (!g_hideMessages)
LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length);
}
// ------------------------------------------------------
// -- Network TX Clocking --
// ------------------------------------------------------
// Processes data in the serial rx P25 buffer and sends it to the network buffer for sending
if (m_serial != nullptr) {
m_serial->processP25ToNet();
}
// ------------------------------------------------------
// -- Serial Clocking --
// ------------------------------------------------------
if (m_serial != nullptr) {
m_serial->clock(ms);
}
// Timekeeping
if (ms < 2U)
Thread::sleep(1U);
}
::LogSetNetwork(nullptr);
if (m_network != nullptr) {
m_network->close();
delete m_network;
}
if (m_serial != nullptr) {
m_serial->close();
delete m_serial;
}
if (m_tidLookup != nullptr) {
m_tidLookup->stop();
delete m_tidLookup;
}
if (m_ridLookup != nullptr) {
m_ridLookup->stop();
delete m_ridLookup;
}
return EXIT_SUCCESS;
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Reads basic configuration parameters from the YAML configuration file.
/// </summary>
/// <returns></returns>
bool Dfsi::readParams()
{
// No basic config params right now
return true;
}
/// <summary>
/// Initializes peer network connectivity.
/// </summary>
/// <returns></returns>
bool Dfsi::createPeerNetwork()
{
yaml::Node networkConf = m_conf["network"];
std::string password = networkConf["password"].as<std::string>();
std::string address = networkConf["address"].as<std::string>();
uint16_t port = networkConf["port"].as<uint16_t>();
uint32_t id = networkConf["peerId"].as<uint32_t>();
bool encrypted = networkConf["encrypted"].as<bool>(false);
std::string key = networkConf["presharedKey"].as<std::string>();
uint8_t presharedKey[AES_WRAPPED_PCKT_KEY_LEN];
if (!key.empty()) {
if (key.size() == 32) {
// bryanb: shhhhhhh....dirty nasty hacks
key = key.append(key); // since the key is 32 characters (16 hex pairs), double it on itself for 64 characters (32 hex pairs)
LogWarning(LOG_HOST, "Half-length network preshared encryption key detected, doubling key on itself.");
}
if (key.size() == 64) {
if ((key.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string::npos)) {
const char* keyPtr = key.c_str();
::memset(presharedKey, 0x00U, AES_WRAPPED_PCKT_KEY_LEN);
for (uint8_t i = 0; i < AES_WRAPPED_PCKT_KEY_LEN; i++) {
char t[4] = {keyPtr[0], keyPtr[1], 0};
presharedKey[i] = (uint8_t)::strtoul(t, NULL, 16);
keyPtr += 2 * sizeof(char);
}
}
else {
LogWarning(LOG_HOST, "Invalid characters in the network preshared encryption key. Encryption disabled.");
encrypted = false;
}
}
else {
LogWarning(LOG_HOST, "Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled.");
encrypted = false;
}
}
std::string identity = networkConf["identity"].as<std::string>();
bool netDebug = networkConf["debug"].as<bool>();
LogInfo("Network Parameters");
LogInfo(" Identity: %s", identity.c_str());
LogInfo(" Peer ID: %u", id);
LogInfo(" Address: %s", address.c_str());
LogInfo(" Port: %u", port);
LogInfo(" Encrypted: %s", encrypted ? "yes" : "no");
if (id > 999999999U) {
::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
return false;
}
// initialize networking
m_network = new DfsiPeerNetwork(address, port, 0U, id, password, true, netDebug, false, true, false, true, true, true, true, true, false);
m_network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, 0.0F, 0.0F, 0, "");
m_network->setLookups(m_ridLookup, m_tidLookup);
::LogSetNetwork(m_network);
if (encrypted) {
m_network->setPresharedKey(presharedKey);
}
m_network->enable(true);
bool ret = m_network->open();
if (!ret) {
delete m_network;
m_network = nullptr;
LogError(LOG_HOST, "failed to initialize traffic networking for PEER %u", id);
return false;
}
::LogSetNetwork(m_network);
return true;
}

@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__DFSI_H__)
#define __DFSI_H__
#include "Defines.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/yaml/Yaml.h"
#include "common/Timer.h"
#include "network/DfsiPeerNetwork.h"
#include "network/SerialService.h"
#include <string>
#include <unordered_map>
#include <vector>
// ---------------------------------------------------------------------------
// Class Declaration
// This class implements the core service logic.
// ---------------------------------------------------------------------------
class HOST_SW_API Dfsi {
public:
/// <summary>Initializes a new instance of the HostTest class.</summary>
Dfsi(const std::string& confFile);
/// <summary>Finalizes a instance of the HostTest class.</summary>
~Dfsi();
/// <summary>Executes the main host processing loop.</summary>
int run();
private:
const std::string& m_confFile;
yaml::Node m_conf;
network::DfsiPeerNetwork* m_network;
lookups::RadioIdLookup* m_ridLookup;
lookups::TalkgroupRulesLookup* m_tidLookup;
uint32_t m_pingTime;
uint32_t m_maxMissedPings;
uint32_t m_updateLookupTime;
bool m_debug;
bool m_repeatTraffic;
network::SerialService* m_serial;
/// <summary>Reads basic configuration parameters from the INI.</summary>
bool readParams();
/// <summary>Initializes peer network connectivity.</summary>
bool createPeerNetwork();
};
#endif // __DFSI_H__

@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#include "Defines.h"
#include "common/Log.h"
#include "dfsi/ActivityLog.h"
#include "DfsiMain.h"
#include "Dfsi.h"
using namespace network;
using namespace lookups;
#include <cstdio>
#include <cstdarg>
#include <vector>
#include <signal.h>
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
#define IS(s) (::strcmp(argv[i], s) == 0)
// ---------------------------------------------------------------------------
// Global Variables
// ---------------------------------------------------------------------------
int g_signal = 0;
std::string g_progExe = std::string(__EXE_NAME__);
std::string g_iniFile = std::string(DEFAULT_CONF_FILE);
std::string g_lockFile = std::string(DEFAULT_LOCK_FILE);
std::string g_masterAddress = std::string("127.0.0.1");
uint16_t g_masterPort = 63031;
uint32_t g_peerId = 9000999;
bool g_foreground = false;
bool g_killed = false;
bool g_hideMessages = false;
uint8_t* g_gitHashBytes = nullptr;
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
#if !defined(CATCH2_TEST_COMPILATION)
/// <summary>
/// Internal signal handler.
/// </summary>
/// <param name="signum"></param>
static void sigHandler(int signum)
{
g_signal = signum;
g_killed = true;
}
#endif
/// <summary>
/// Helper to print a fatal error message and exit.
/// </summary>
/// <remarks>This is a variable argument function.</remarks>
/// <param name="msg">Message.</param>
void fatal(const char* msg, ...)
{
char buffer[400U];
::memset(buffer, 0x20U, 400U);
va_list vl;
va_start(vl, msg);
::vsprintf(buffer, msg, vl);
va_end(vl);
::fprintf(stderr, "%s: %s\n", g_progExe.c_str(), buffer);
exit(EXIT_FAILURE);
}
/// <summary>
/// Helper to pring usage the command line arguments. (And optionally an error.)
/// </summary>
/// <param name="message">Error message.</param>
/// <param name="arg">Error message arguments.</param>
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2024 DVMProject (https://github.com/dvmproject) Authors.\n");
if (message != nullptr) {
::fprintf(stderr, "%s: ", g_progExe.c_str());
::fprintf(stderr, message, arg);
::fprintf(stderr, "\n\n");
}
::fprintf(stdout,
"usage: %s [-vhf]"
"[--syslog]"
"[-c <configuration file>]"
"[-a <address>] [-p <port>] [-P <peer id>]"
"\n\n"
" -v show version information\n"
" -h show this screen\n"
" -f foreground mode\n"
"\n"
" -c <file> specifies the configuration file to use\n"
"\n"
" -- stop handling options\n",
g_progExe.c_str());
exit(EXIT_FAILURE);
}
/// <summary>
/// Helper to validate the command line arguments.
/// </summary>
/// <param name="argc">Argument count.</param>
/// <param name="argv">Array of argument strings.</param>
/// <returns>Count of remaining unprocessed arguments.</returns>
int checkArgs(int argc, char* argv[])
{
int i, p = 0;
// iterate through arguments
for (i = 1; i <= argc; i++)
{
if (argv[i] == nullptr) {
break;
}
if (*argv[i] != '-') {
continue;
}
else if (IS("--")) {
++p;
break;
}
else if (IS("-f")) {
g_foreground = true;
}
else if (IS("--syslog")) {
g_useSyslog = true;
}
else if (IS("-s")) {
g_hideMessages = true;
}
else if (IS("-c")) {
if (argc-- <= 0)
usage("error: %s", "must specify the configuration file to use");
g_iniFile = std::string(argv[++i]);
if (g_iniFile.empty())
usage("error: %s", "configuration file cannot be blank!");
p += 2;
}
else if (IS("-v")) {
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Patrick McDonnell, W3AXL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (argc == 2)
exit(EXIT_SUCCESS);
}
else if (IS("-h")) {
usage(nullptr, nullptr);
if (argc == 2)
exit(EXIT_SUCCESS);
}
else {
usage("unrecognized option `%s'", argv[i]);
}
}
if (p < 0 || p > argc) {
p = 0;
}
return ++p;
}
// ---------------------------------------------------------------------------
// Program Entry Point
// ---------------------------------------------------------------------------
#if !defined(CATCH2_TEST_COMPILATION)
int main(int argc, char** argv)
{
g_gitHashBytes = new uint8_t[4U];
::memset(g_gitHashBytes, 0x00U, 4U);
uint32_t hash = ::strtoul(__GIT_VER_HASH__, 0, 16);
__SET_UINT32(hash, g_gitHashBytes, 0U);
if (argv[0] != nullptr && *argv[0] != 0)
g_progExe = std::string(argv[0]);
if (argc > 1) {
// check arguments
int i = checkArgs(argc, argv);
if (i < argc) {
argc -= i;
argv += i;
}
else {
argc--;
argv++;
}
}
::signal(SIGINT, sigHandler);
::signal(SIGTERM, sigHandler);
::signal(SIGHUP, sigHandler);
int ret = 0;
do {
g_signal = 0;
g_killed = false;
Dfsi *dfsi = new Dfsi(g_iniFile);
ret = dfsi->run();
delete dfsi;
if (g_signal == 2)
::LogInfoEx(LOG_HOST, "Exited on receipt of SIGINT");
if (g_signal == 15)
::LogInfoEx(LOG_HOST, "Exited on receipt of SIGTERM");
if (g_signal == 1)
::LogInfoEx(LOG_HOST, "Restarting on receipt of SIGHUP");
} while (g_signal == 1);
::LogFinalise();
::ActivityLogFinalise();
return ret;
}
#endif

@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__DFSI_MAIN_H__)
#define __DFSI_MAIN_H__
#include "Defines.h"
#include <string>
// ---------------------------------------------------------------------------
// Externs
// ---------------------------------------------------------------------------
extern int g_signal;
extern std::string g_progExe;
extern std::string g_iniFile;
extern std::string g_lockFile;
extern bool g_foreground;
extern bool g_killed;
extern bool g_hideMessages;
extern std::string g_masterAddress;
extern uint16_t g_masterPort;
extern uint32_t g_peerId;
extern uint8_t* g_gitHashBytes;
extern HOST_SW_API void fatal(const char* msg, ...);
#endif // __DFSI_MAIN_H__

@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
* Borrowed from work by Bryan Biedenkapp, N2PLL
*
*/
#include "CallData.h"
using namespace network;
using namespace modem;
using namespace p25;
using namespace dfsi;
VoiceCallData::VoiceCallData() :
srcId(0U),
dstId(0U),
lco(0U),
mfId(P25_MFG_STANDARD),
serviceOptions(0U),
lsd1(0U),
lsd2(0U),
mi(),
algoId(P25_ALGO_UNENCRYPT),
kId(0U),
VHDR1(),
VHDR2(),
netLDU1(),
netLDU2(),
seqNo(0U),
n(0U),
streamId(0U)
{
mi = new uint8_t[P25_MI_LENGTH_BYTES];
VHDR1 = new uint8_t[MotVoiceHeader1::HCW_LENGTH];
VHDR2 = new uint8_t[MotVoiceHeader2::HCW_LENGTH];
netLDU1 = new uint8_t[9U * 25U];
netLDU2 = new uint8_t[9U * 25U];
::memset(netLDU1, 0x00U, 9U * 25U);
::memset(netLDU2, 0x00U, 9U * 25U);
}
VoiceCallData::~VoiceCallData() {
delete[] mi;
delete[] VHDR1;
delete[] VHDR2;
delete[] netLDU1;
delete[] netLDU2;
}
void VoiceCallData::resetCallData() {
srcId = 0U;
dstId = 0U;
lco = 0U;
mfId = P25_MFG_STANDARD;
serviceOptions = 0U;
lsd1 = 0U;
lsd2 = 0U;
::memset(mi, 0x00U, P25_MI_LENGTH_BYTES);
algoId = P25_ALGO_UNENCRYPT;
kId = 0U;
::memset(VHDR1, 0x00U, MotVoiceHeader1::HCW_LENGTH);
::memset(VHDR2, 0x00U, MotVoiceHeader2::HCW_LENGTH);
::memset(netLDU1, 0x00U, 9U * 25U);
::memset(netLDU2, 0x00U, 9U * 25U);
n = 0U;
seqNo = 0U;
streamId = 0U;
}
void VoiceCallData::newStreamId() {
std::uniform_int_distribution<uint32_t> dist(DVM_RAND_MIN, DVM_RAND_MAX);
streamId = dist(random);
}

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
* Borrowed from work by Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__DFSI_CALL_DATA_H__)
#define __DFSI_CALL_DATA_H__
// DVM Includes
#include "Defines.h"
#include "common/edac/RS634717.h"
#include "common/network/RawFrameQueue.h"
#include "common/p25/data/LowSpeedData.h"
#include "common/p25/P25Defines.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/yaml/Yaml.h"
#include "common/RingBuffer.h"
#include "network/DfsiPeerNetwork.h"
#include "rtp/RtpFrames.h"
#include "host/modem/Modem.h"
#include "host/modem/port/IModemPort.h"
#include "host/modem/port/UARTPort.h"
// CPP includes
#include <random>
namespace network {
class HOST_SW_API VoiceCallData {
public:
VoiceCallData();
~VoiceCallData();
void resetCallData();
void newStreamId();
// Call Data
uint32_t srcId;
uint32_t dstId;
uint8_t lco;
uint8_t mfId;
uint8_t serviceOptions;
uint8_t lsd1;
uint8_t lsd2;
uint8_t* mi;
uint8_t algoId;
uint32_t kId;
uint8_t* VHDR1;
uint8_t* VHDR2;
uint8_t* netLDU1;
uint8_t* netLDU2;
uint32_t seqNo;
uint8_t n;
uint32_t streamId;
private:
// Used for stream ID generation
std::mt19937 random;
};
}
#endif // __DFSI_CALL_DATA_H__

@ -0,0 +1,337 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#include "dfsi/Defines.h"
#include "common/network/json/json.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h"
#include "common/Utils.h"
#include "network/DfsiPeerNetwork.h"
using namespace network;
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the PeerNetwork class.
/// </summary>
/// <param name="address">Network Hostname/IP address to connect to.</param>
/// <param name="port">Network port number.</param>
/// <param name="local"></param>
/// <param name="peerId">Unique ID on the network.</param>
/// <param name="password">Network authentication password.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="debug">Flag indicating whether network debug is enabled.</param>
/// <param name="dmr">Flag indicating whether DMR is enabled.</param>
/// <param name="p25">Flag indicating whether P25 is enabled.</param>
/// <param name="nxdn">Flag indicating whether NXDN is enabled.</param>
/// <param name="slot1">Flag indicating whether DMR slot 1 is enabled for network traffic.</param>
/// <param name="slot2">Flag indicating whether DMR slot 2 is enabled for network traffic.</param>
/// <param name="allowActivityTransfer">Flag indicating that the system activity logs will be sent to the network.</param>
/// <param name="allowDiagnosticTransfer">Flag indicating that the system diagnostic logs will be sent to the network.</param>
/// <param name="updateLookup">Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network.</param>
DfsiPeerNetwork::DfsiPeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) :
Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup)
{
assert(!address.empty());
assert(port > 0U);
assert(!password.empty());
}
/// <summary>
/// Writes P25 LDU1 frame data to the network.
/// </summary>
/// <param name="control"></param>
/// <param name="lsd"></param>
/// <param name="data"></param>
/// <param name="frameType"></param>
/// <returns></returns>
bool DfsiPeerNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data, uint8_t frameType)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
bool resetSeq = false;
if (m_p25StreamId == 0U) {
resetSeq = true;
m_p25StreamId = createStreamId();
}
uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU1Message_Raw(messageLength, control, lsd, data, frameType);
if (message == nullptr) {
return false;
}
return writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq(resetSeq), m_p25StreamId);
}
/// <summary>
/// Writes P25 LDU2 frame data to the network.
/// </summary>
/// <param name="control"></param>
/// <param name="lsd"></param>
/// <param name="data"></param>
/// <returns></returns>
bool DfsiPeerNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data)
{
if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING)
return false;
bool resetSeq = false;
if (m_p25StreamId == 0U) {
resetSeq = true;
m_p25StreamId = createStreamId();
}
uint32_t messageLength = 0U;
UInt8Array message = createP25_LDU2Message_Raw(messageLength, control, lsd, data);
if (message == nullptr) {
return false;
}
return writeMaster({ NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq(resetSeq), m_p25StreamId);
}
// ---------------------------------------------------------------------------
// Protected Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Writes configuration to the network.
/// </summary>
/// <returns></returns>
bool DfsiPeerNetwork::writeConfig()
{
if (m_loginStreamId == 0U) {
LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?");
return false;
}
const char* software = __NETVER__;
json::object config = json::object();
// identity and frequency
config["identity"].set<std::string>(m_identity); // Identity
config["rxFrequency"].set<uint32_t>(m_rxFrequency); // Rx Frequency
config["txFrequency"].set<uint32_t>(m_txFrequency); // Tx Frequency
// system info
json::object sysInfo = json::object();
sysInfo["latitude"].set<float>(m_latitude); // Latitude
sysInfo["longitude"].set<float>(m_longitude); // Longitude
sysInfo["height"].set<int>(m_height); // Height
sysInfo["location"].set<std::string>(m_location); // Location
config["info"].set<json::object>(sysInfo);
// channel data
json::object channel = json::object();
channel["txPower"].set<uint32_t>(m_power); // Tx Power
channel["txOffsetMhz"].set<float>(m_txOffsetMhz); // Tx Offset (Mhz)
channel["chBandwidthKhz"].set<float>(m_chBandwidthKhz); // Ch. Bandwidth (khz)
channel["channelId"].set<uint8_t>(m_channelId); // Channel ID
channel["channelNo"].set<uint32_t>(m_channelNo); // Channel No
config["channel"].set<json::object>(channel);
// RCON
json::object rcon = json::object();
rcon["password"].set<std::string>(m_restApiPassword); // REST API Password
rcon["port"].set<uint16_t>(m_restApiPort); // REST API Port
config["rcon"].set<json::object>(rcon);
config["software"].set<std::string>(std::string(software)); // Software ID
json::value v = json::value(config);
std::string json = v.serialize();
char buffer[json.length() + 8U];
::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U);
::sprintf(buffer + 8U, "%s", json.c_str());
if (m_debug) {
Utils::dump(1U, "Network Message, Configuration", (uint8_t*)buffer, json.length() + 8U);
}
return writeMaster({ NET_FUNC_RPTC, NET_SUBFUNC_NOP }, (uint8_t*)buffer, json.length() + 8U, pktSeq(), m_loginStreamId);
}
// ---------------------------------------------------------------------------
// Private Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Creates an P25 LDU1 frame message.
/// </summary>
/// <param name="length"></param>
/// <param name="control"></param>
/// <param name="lsd"></param>
/// <param name="data"></param>
/// <param name="frameType"></param>
/// <returns></returns>
UInt8Array DfsiPeerNetwork::createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, uint8_t frameType)
{
assert(data != nullptr);
p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd);
uint8_t* buffer = new uint8_t[P25_LDU1_PACKET_LENGTH + PACKET_PAD];
::memset(buffer, 0x00U, P25_LDU1_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header
createP25_MessageHdr(buffer, p25::P25_DUID_LDU1, control, lsd, frameType);
// pack DFSI data
uint32_t count = MSG_HDR_SIZE;
uint8_t imbe[p25::P25_RAW_IMBE_LENGTH_BYTES];
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE1);
::memcpy(imbe, data + 10U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 24U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE2);
::memcpy(imbe, data + 26U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 46U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE3);
::memcpy(imbe, data + 55U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 60U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE4);
::memcpy(imbe, data + 80U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 77U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE5);
::memcpy(imbe, data + 105U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 94U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE6);
::memcpy(imbe, data + 130U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 111U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE7);
::memcpy(imbe, data + 155U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 128U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE8);
::memcpy(imbe, data + 180U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 145U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU1_VOICE9);
::memcpy(imbe, data + 204U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU1(buffer + 162U, imbe);
count += p25::dfsi::P25_DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES;
buffer[23U] = count;
if (m_debug)
Utils::dump(1U, "Network Message, P25 LDU1", buffer, (P25_LDU1_PACKET_LENGTH + PACKET_PAD));
length = (P25_LDU1_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer);
}
/// <summary>
/// Creates an P25 LDU2 frame message.
/// </summary>
/// <param name="length"></param>
/// <param name="control"></param>
/// <param name="lsd"></param>
/// <param name="data"></param>
/// <returns></returns>
UInt8Array DfsiPeerNetwork::createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data)
{
assert(data != nullptr);
p25::dfsi::LC dfsiLC = p25::dfsi::LC(control, lsd);
uint8_t* buffer = new uint8_t[P25_LDU2_PACKET_LENGTH + PACKET_PAD];
::memset(buffer, 0x00U, P25_LDU2_PACKET_LENGTH + PACKET_PAD);
// construct P25 message header
createP25_MessageHdr(buffer, p25::P25_DUID_LDU2, control, lsd, p25::P25_FT_DATA_UNIT);
// pack DFSI data
uint32_t count = MSG_HDR_SIZE;
uint8_t imbe[p25::P25_RAW_IMBE_LENGTH_BYTES];
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE10);
::memcpy(imbe, data + 10U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 24U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE11);
::memcpy(imbe, data + 26U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 46U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE12);
::memcpy(imbe, data + 55U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 60U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE13);
::memcpy(imbe, data + 80U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 77U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE14);
::memcpy(imbe, data + 105U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 94U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE15);
::memcpy(imbe, data + 130U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 111U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE16);
::memcpy(imbe, data + 155U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 128U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE17);
::memcpy(imbe, data + 180U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 145U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES;
dfsiLC.setFrameType(p25::dfsi::P25_DFSI_LDU2_VOICE18);
::memcpy(imbe, data + 204U, p25::P25_RAW_IMBE_LENGTH_BYTES);
dfsiLC.encodeLDU2(buffer + 162U, imbe);
count += p25::dfsi::P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES;
buffer[23U] = count;
if (m_debug)
Utils::dump(1U, "Network Message, P25 LDU2", buffer, (P25_LDU2_PACKET_LENGTH + PACKET_PAD));
length = (P25_LDU2_PACKET_LENGTH + PACKET_PAD);
return UInt8Array(buffer);
}

@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__DFSI_PEER_NETWORK_H__)
#define __DFSI_PEER_NETWORK_H__
#include "Defines.h"
#include "host/network/Network.h"
#include <string>
#include <cstdint>
namespace network
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements the core peer networking logic.
// ---------------------------------------------------------------------------
class HOST_SW_API DfsiPeerNetwork : public Network {
public:
/// <summary>Initializes a new instance of the PeerNetwork class.</summary>
DfsiPeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup);
/// <summary>Writes P25 LDU1 frame data to the network.</summary>
bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data,
uint8_t frameType) override;
/// <summary>Writes P25 LDU2 frame data to the network.</summary>
bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) override;
protected:
/// <summary>Writes configuration to the network.</summary>
bool writeConfig() override;
private:
/// <summary>Creates an P25 LDU1 frame message.</summary>
UInt8Array createP25_LDU1Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data, uint8_t frameType);
/// <summary>Creates an P25 LDU2 frame message.</summary>
UInt8Array createP25_LDU2Message_Raw(uint32_t& length, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd,
const uint8_t* data);
};
} // namespace network
#endif // __DFSI_PEER_NETWORK_H__

File diff suppressed because it is too large Load Diff

@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__SERIAL_SERVICE_H__)
#define __SERIAL_SERVICE_H__
// DVM Includes
#include "Defines.h"
#include "common/edac/RS634717.h"
#include "common/network/RawFrameQueue.h"
#include "common/p25/data/LowSpeedData.h"
#include "common/p25/P25Defines.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "common/yaml/Yaml.h"
#include "common/RingBuffer.h"
#include "network/DfsiPeerNetwork.h"
#include "network/CallData.h"
#include "rtp/RtpFrames.h"
#include "host/modem/Modem.h"
#include "host/modem/port/IModemPort.h"
#include "host/modem/port/UARTPort.h"
// System Includes
#include <string>
#include <cstdint>
#include <map>
#include <unordered_map>
#include <random>
#include <pthread.h>
using namespace modem;
using namespace p25;
using namespace dfsi;
namespace network
{
// DFSI serial tx flags used to determine proper jitter handling of data in ringbuffer
enum SERIAL_TX_TYPE {
NONIMBE,
IMBE
};
// ---------------------------------------------------------------------------
// Class Declaration
// Serial V24 service
// ---------------------------------------------------------------------------
class HOST_SW_API SerialService {
public:
SerialService(const std::string& portName, uint32_t baudrate, bool rtrt, bool diu, uint16_t jitter, DfsiPeerNetwork* network, uint32_t p25TxQueueSize, uint32_t p25RxQueueSize, bool debug, bool trace);
~SerialService();
void clock(uint32_t ms);
bool open();
void close();
// Handle P25 data from network to V24
void processP25FromNet(UInt8Array p25Buffer, uint32_t length);
// Handle P25 data from V24 to network
void processP25ToNet();
private:
std::string m_portName;
uint32_t m_baudrate;
bool m_rtrt;
bool m_diu;
port::IModemPort* m_port;
uint16_t m_jitter;
bool m_debug;
bool m_trace;
DfsiPeerNetwork* m_network;
uint8_t* m_lastIMBE;
// Tables to keep track of calls
std::unordered_map<uint32_t, uint64_t> m_lastHeard;
std::unordered_map<uint32_t, uint32_t> m_sequences;
uint8_t* m_msgBuffer;
RESP_STATE m_msgState;
uint16_t m_msgLength;
uint16_t m_msgOffset;
DVM_COMMANDS m_msgType;
bool m_msgDoubleLength;
uint32_t m_netFrames;
uint32_t m_netLost;
RingBuffer<uint8_t> m_rxP25Queue;
RingBuffer<uint8_t> m_txP25Queue;
uint64_t m_lastP25Tx;
edac::RS634717 m_rs;
// Counter for assembling a full LDU from individual frames in the RX queue
uint8_t m_rxP25LDUCounter;
// Flags to indicate if calls to/from the FNE are already in progress
bool m_netCallInProgress;
bool m_lclCallInProgress;
// Control and LSD objects for current RX P25 data being built to send to the net
lc::LC* m_rxVoiceControl;
data::LowSpeedData* m_rxVoiceLsd;
// Call Data object for current RX P25 call (VHDR, LDU, etc)
VoiceCallData* m_rxVoiceCallData;
// Functions called by clock() to read/write from/to the serial port
RESP_TYPE_DVM readSerial();
int writeSerial();
uint32_t readP25Frame(uint8_t* data);
void writeP25Frame(uint8_t duid, dfsi::LC& lc, uint8_t* ldu);
// Helpers for TX stream data
void startOfStream(const LC& lc);
void endOfStream();
// Helper for timing TX data appropriately based on frame type
void addTxToQueue(uint8_t* data, uint16_t length, SERIAL_TX_TYPE msgType);
/// <summary>Helper to insert IMBE silence frames for missing audio.</summary>
void insertMissingAudio(uint8_t* data, uint32_t& lost);
void printDebug(const uint8_t* buffer, uint16_t length);
};
// Defines for Mot DFSI
} // namespace network
#endif // __SERIAL_SERVICE_H__

@ -0,0 +1,211 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "rtp/MotFullRateVoice.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a instance of the MotFullRateVoice class.
/// </summary>
MotFullRateVoice::MotFullRateVoice()
{
frameType = P25_DFSI_LDU1_VOICE1;
additionalData = nullptr;
source = SOURCE_QUANTAR;
imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN);
}
/// <summary>
/// Initializes a instance of the MotFullRateVoice class.
/// </summary>
/// <param name="data"></param>
MotFullRateVoice::MotFullRateVoice(uint8_t* data)
{
decode(data);
}
/// <summary>
/// Finalizes a instance of the MotFullRateVoice class.
/// </summary>
MotFullRateVoice::~MotFullRateVoice()
{
delete[] imbeData;
delete[] additionalData;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
uint32_t MotFullRateVoice::size()
{
uint32_t length = 0;
// Set length appropriately based on frame type
if (isVoice1or2or10or11()) {
length += SHORTENED_LENGTH;
} else {
length += LENGTH;
}
// These are weird
if (isVoice9or18()) {
length -= 1;
}
return length;
}
/// <summary>
/// Decode a full rate voice frame.
/// </summary>
/// <param name="data"></param>
/// <param name="shortened"></param>
/// <returns></returns>
bool MotFullRateVoice::decode(const uint8_t* data, bool shortened)
{
assert(data != nullptr);
imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN);
frameType = data[0U];
if (isVoice2or11()) {
shortened = true;
}
if (shortened) {
::memcpy(imbeData, data + 1U, IMBE_BUF_LEN);
source = (SourceFlag)data[12U];
// Forgot to set this originally and left additionalData uninitialized, whoops!
additionalData = nullptr;
} else {
// Frames 0x6A and 0x73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier
uint8_t imbeStart = 5U;
if (isVoice9or18()) {
imbeStart = 4U;
}
additionalData = new uint8_t[ADDITIONAL_LENGTH];
::memset(additionalData, 0x00U, ADDITIONAL_LENGTH);
::memcpy(additionalData, data + 1U, ADDITIONAL_LENGTH);
// Copy IMBE data based on our imbe start position
::memcpy(imbeData, data + imbeStart, IMBE_BUF_LEN);
source = (SourceFlag)data[IMBE_BUF_LEN + imbeStart];
}
return true;
}
/// <summary>
/// Encode a full rate voice frame.
/// </summary>
/// <param name="data"></param>
/// <param name="shortened"></param>
void MotFullRateVoice::encode(uint8_t* data, bool shortened)
{
assert(data != nullptr);
// Check if we're a shortened frame
data[0U] = frameType;
if (isVoice2or11()) {
shortened = true;
}
// Copy based on shortened frame or not
if (shortened) {
::memcpy(data + 1U, imbeData, IMBE_BUF_LEN);
data[12U] = (uint8_t)source;
}
// If not shortened, our IMBE data start position depends on frame type
else {
// Starting index for the IMBE data
uint8_t imbeStart = 5U;
if (isVoice9or18()) {
imbeStart = 4U;
}
// Check if we have additional data
if (additionalData != nullptr) {
::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH);
}
// Copy rest of data
::memcpy(data + imbeStart, imbeData, IMBE_BUF_LEN);
// Source byte at the end
data[11U + imbeStart] = (uint8_t)source;
}
}
// ---------------------------------------------------------------------------
// Protected Class Members
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
bool MotFullRateVoice::isVoice1or2or10or11()
{
if ( (frameType == P25_DFSI_LDU1_VOICE1) || (frameType == P25_DFSI_LDU1_VOICE2) || (frameType == P25_DFSI_LDU2_VOICE10) || (frameType == P25_DFSI_LDU2_VOICE11) ) {
return true;
} else {
return false;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool MotFullRateVoice::isVoice2or11()
{
if ( (frameType == P25_DFSI_LDU1_VOICE2) || (frameType == P25_DFSI_LDU2_VOICE11) ) {
return true;
} else {
return false;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool MotFullRateVoice::isVoice9or18()
{
if ( (frameType == P25_DFSI_LDU1_VOICE9) || (frameType == P25_DFSI_LDU2_VOICE18) ) {
return true;
} else {
return false;
}
}

@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__MOT_FULL_RATE_VOICE_H__)
#define __MOT_FULL_RATE_VOICE_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "rtp/RtpDefines.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a P25 Motorola full rate voice packet.
//
// Byte 0 1 2 3
// Bit 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | FT | Addtl Data | Addtl Data | Addtl Data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Reserved | IMBE 1 | IMBE 2 | IMBE 3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IMBE 4 | IMBE 5 | IMBE 6 | IMBE 7 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IMBE 8 | IMBE 9 | IMBE 10 | IMBE 11 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Src Flag |
// +=+=+=+=+=+=+=+=+
// ---------------------------------------------------------------------------
class HOST_SW_API MotFullRateVoice {
public:
// Frame information
static const uint8_t LENGTH = 17;
static const uint8_t SHORTENED_LENGTH = 13;
static const uint8_t ADDITIONAL_LENGTH = 4;
static const uint8_t IMBE_BUF_LEN = 11;
uint8_t frameType;
uint8_t* imbeData;
uint8_t* additionalData;
SourceFlag source;
/// <summary>Initializes a copy instance of the MotFullRateVoice class.</summary>
MotFullRateVoice();
/// <summary>Initializes a copy instance of the MotFullRateVoice class.</summary>
MotFullRateVoice(uint8_t* data);
/// <summary>Finalizes a instance of the MotFullRateVoice class.</summary>
~MotFullRateVoice();
/// <summary></summary>
uint32_t size();
/// <summary>Decode a full rate voice frame.</summary>
bool decode(const uint8_t* data, bool shortened = false);
/// <summary>Encode a full rate voice frame.</summary>
void encode(uint8_t* data, bool shortened = false);
private:
/// <summary></summary>
bool isVoice1or2or10or11();
/// <summary></summary>
bool isVoice2or11();
/// <summary></summary>
bool isVoice9or18();
};
} // namespace dfsi
} // namespace p25
#endif // __MOT_FULL_RATE_VOICE_H__

@ -0,0 +1,432 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#include "rtp/MotRtpFrames.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Motorola full rate voice data
// ---------------------------------------------------------------------------
MotFullRateVoice::MotFullRateVoice()
{
frameType = P25_DFSI_LDU1_VOICE1;
additionalData = nullptr;
source = SOURCE_QUANTAR;
imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN);
}
MotFullRateVoice::MotFullRateVoice(uint8_t* data)
{
decode(data);
}
MotFullRateVoice::~MotFullRateVoice()
{
delete[] imbeData;
delete[] additionalData;
}
uint32_t MotFullRateVoice::size()
{
uint32_t length = 0;
// Set length appropriately based on frame type
if (isVoice1or2or10or11()) {
length += SHORTENED_LENGTH;
} else {
length += LENGTH;
}
// These are weird
if (isVoice9or18()) {
length -= 1;
}
return length;
}
/// <summary>Decode a block of bytes into a full rate voice IMBE block</summary>
bool MotFullRateVoice::decode(uint8_t* data, bool shortened)
{
// Sanity check
assert(data != nullptr);
imbeData = new uint8_t[IMBE_BUF_LEN];
::memset(imbeData, 0x00U, IMBE_BUF_LEN);
frameType = data[0U];
if (isVoice2or11()) {
shortened = true;
}
if (shortened) {
::memcpy(imbeData, data + 1U, IMBE_BUF_LEN);
source = (SourceFlag)data[12U];
// Forgot to set this originally and left additionalData uninitialized, whoops!
additionalData = nullptr;
} else {
// Frames 0x6A and 0x73 are missing the 0x00 padding byte, so we start IMBE data 1 byte earlier
uint8_t imbeStart = 5U;
if (isVoice9or18()) {
imbeStart = 4U;
}
additionalData = new uint8_t[ADDITIONAL_LENGTH];
::memset(additionalData, 0x00U, ADDITIONAL_LENGTH);
::memcpy(additionalData, data + 1U, ADDITIONAL_LENGTH);
// Copy IMBE data based on our imbe start position
::memcpy(imbeData, data + imbeStart, IMBE_BUF_LEN);
source = (SourceFlag)data[IMBE_BUF_LEN + imbeStart];
}
return true;
}
void MotFullRateVoice::encode(uint8_t* data, bool shortened)
{
// Sanity check
assert(data != nullptr);
// Check if we're a shortened frame
data[0U] = frameType;
if (isVoice2or11()) {
shortened = true;
}
// Copy based on shortened frame or not
if (shortened) {
::memcpy(data + 1U, imbeData, IMBE_BUF_LEN);
data[12U] = (uint8_t)source;
}
// If not shortened, our IMBE data start position depends on frame type
else {
// Starting index for the IMBE data
uint8_t imbeStart = 5U;
if (isVoice9or18()) {
imbeStart = 4U;
}
// Check if we have additional data
if (additionalData != nullptr) {
::memcpy(data + 1U, additionalData, ADDITIONAL_LENGTH);
}
// Copy rest of data
::memcpy(data + imbeStart, imbeData, IMBE_BUF_LEN);
// Source byte at the end
data[11U + imbeStart] = (uint8_t)source;
}
}
bool MotFullRateVoice::isVoice1or2or10or11()
{
if ( (frameType == P25_DFSI_LDU1_VOICE1) || (frameType == P25_DFSI_LDU1_VOICE2) || (frameType == P25_DFSI_LDU2_VOICE10) || (frameType == P25_DFSI_LDU2_VOICE11) ) {
return true;
} else {
return false;
}
}
bool MotFullRateVoice::isVoice2or11()
{
if ( (frameType == P25_DFSI_LDU1_VOICE2) || (frameType == P25_DFSI_LDU2_VOICE11) ) {
return true;
} else {
return false;
}
}
bool MotFullRateVoice::isVoice9or18()
{
if ( (frameType == P25_DFSI_LDU1_VOICE9) || (frameType == P25_DFSI_LDU2_VOICE18) ) {
return true;
} else {
return false;
}
}
// ---------------------------------------------------------------------------
// Motorola start of stream frame (10 bytes long)
// ---------------------------------------------------------------------------
MotStartOfStream::MotStartOfStream()
{
rt = DISABLED;
startStop = START;
streamType = VOICE;
}
MotStartOfStream::MotStartOfStream(uint8_t* data)
{
decode(data);
}
bool MotStartOfStream::decode(uint8_t* data)
{
// Sanity check
assert(data != nullptr);
// Get parameters
rt = (RTFlag)data[2U];
startStop = (StartStopFlag)data[3U];
streamType = (StreamTypeFlag)data[4U];
return true;
}
void MotStartOfStream::encode(uint8_t* data)
{
// Sanity check
assert(data != nullptr);
// Copy data
data[0U] = P25_DFSI_MOT_START_STOP;
data[1U] = FIXED_MARKER;
data[2U] = rt;
data[3U] = startStop;
data[4U] = streamType;
}
// ---------------------------------------------------------------------------
// Motorola start voice frame
// ---------------------------------------------------------------------------
MotStartVoiceFrame::MotStartVoiceFrame()
{
icw = ICW_DIU;
rssi = 0;
rssiValidity = INVALID;
nRssi = 0;
adjMM = 0;
startOfStream = nullptr;
fullRateVoice = nullptr;
}
MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data)
{
decode(data);
}
MotStartVoiceFrame::~MotStartVoiceFrame()
{
delete startOfStream;
delete fullRateVoice;
}
bool MotStartVoiceFrame::decode(uint8_t* data)
{
assert(data != nullptr);
// Create a new startOfStream
startOfStream = new MotStartOfStream();
// Create a buffer to decode the start record skipping the 10th byte (adjMM)
uint8_t startBuffer[startOfStream->LENGTH];
::memset(startBuffer, 0x00U, startOfStream->LENGTH);
::memcpy(startBuffer, data, 9U);
// Decode start of stream
startOfStream->decode(startBuffer);
// Decode the full rate voice frames
fullRateVoice = new MotFullRateVoice();
uint8_t voiceBuffer[fullRateVoice->SHORTENED_LENGTH];
::memset(voiceBuffer, 0x00U, fullRateVoice->SHORTENED_LENGTH);
voiceBuffer[0U] = data[0U];
::memcpy(voiceBuffer + 1U, data + 10U, fullRateVoice->SHORTENED_LENGTH - 1);
fullRateVoice->decode(voiceBuffer, true);
// Get rest of data
icw = (ICWFlag)data[5U];
rssi = data[6U];
rssiValidity = (RssiValidityFlag)data[7U];
nRssi = data[8U];
adjMM = data[9U];
return true;
}
void MotStartVoiceFrame::encode(uint8_t* data)
{
// Sanity checks
assert(data != nullptr);
assert(startOfStream != nullptr);
assert(fullRateVoice != nullptr);
// Encode start of stream
if (startOfStream != nullptr) {
uint8_t buffer[startOfStream->LENGTH];
startOfStream->encode(buffer);
// Copy to data array (skipping first and last bytes)
::memcpy(data + 1U, buffer + 1U, startOfStream->LENGTH - 2);
}
// Encode full rate voice
if (fullRateVoice != nullptr) {
uint8_t buffer[fullRateVoice->SHORTENED_LENGTH];
fullRateVoice->encode(buffer, true);
data[0U] = fullRateVoice->frameType;
::memcpy(data + 10U, buffer + 1U, fullRateVoice->SHORTENED_LENGTH - 1);
}
// Copy the rest
data[5U] = icw;
data[6U] = rssi;
data[7U] = rssiValidity;
data[8U] = nRssi;
data[9U] = adjMM;
}
// ---------------------------------------------------------------------------
// Motorola voice header 1
// ---------------------------------------------------------------------------
MotVoiceHeader1::MotVoiceHeader1()
{
icw = ICW_DIU;
rssi = 0;
rssiValidity = INVALID;
nRssi = 0;
startOfStream = nullptr;
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
}
MotVoiceHeader1::MotVoiceHeader1(uint8_t* data)
{
decode(data);
}
MotVoiceHeader1::~MotVoiceHeader1()
{
delete startOfStream;
delete[] header;
}
bool MotVoiceHeader1::decode(uint8_t* data)
{
assert(data != nullptr);
// Create a start of stream
startOfStream = new MotStartOfStream();
uint8_t buffer[startOfStream->LENGTH];
::memset(buffer, 0x00U, startOfStream->LENGTH);
// We copy the bytes from [1:4]
::memcpy(buffer + 1U, data + 1U, 4);
startOfStream->decode(buffer);
// Decode the other stuff
icw = (ICWFlag)data[5U];
rssi = data[6U];
rssiValidity = (RssiValidityFlag)data[7U];
nRssi = data[8U];
// Our header includes the trailing source and check bytes
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
::memcpy(header, data + 9U, HCW_LENGTH);
return true;
}
void MotVoiceHeader1::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
data[0U] = P25_DFSI_MOT_VHDR_1;
if (startOfStream != nullptr) {
uint8_t buffer[startOfStream->LENGTH];
::memset(buffer, 0x00U, startOfStream->LENGTH);
startOfStream->encode(buffer);
// Copy the 4 start record bytes from the start of stream frame
::memcpy(data + 1U, buffer + 1U, 4U);
}
data[5U] = icw;
data[6U] = rssi;
data[7U] = (uint8_t)rssiValidity;
data[8U] = nRssi;
// Our header includes the trailing source and check bytes
if (header != nullptr) {
::memcpy(data + 9U, header, HCW_LENGTH);
}
}
// ---------------------------------------------------------------------------
// Motorola voice header 2
// ---------------------------------------------------------------------------
MotVoiceHeader2::MotVoiceHeader2()
{
source = SOURCE_QUANTAR;
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
}
MotVoiceHeader2::MotVoiceHeader2(uint8_t* data)
{
decode(data);
}
MotVoiceHeader2::~MotVoiceHeader2()
{
delete[] header;
}
bool MotVoiceHeader2::decode(uint8_t* data)
{
assert(data != nullptr);
source = (SourceFlag)data[21];
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
::memcpy(header, data + 1U, HCW_LENGTH);
return true;
}
void MotVoiceHeader2::encode(uint8_t* data)
{
assert(data != nullptr);
data[0U] = P25_DFSI_MOT_VHDR_2;
if (header != nullptr) {
::memcpy(data + 1U, header, HCW_LENGTH);
}
data[LENGTH - 1U] = (uint8_t)source;
}

@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
*
*/
#if !defined(__MOT_RTP_FRAMES_H__)
#define __MOT_RTP_FRAMES_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
namespace p25
{
namespace dfsi
{
enum RTFlag {
ENABLED = 0x02U,
DISABLED = 0x04U
};
enum StartStopFlag {
START = 0x0CU,
STOP = 0x25U
};
enum StreamTypeFlag {
VOICE = 0x0BU
};
enum RssiValidityFlag {
INVALID = 0x00U,
VALID = 0x1A
};
enum SourceFlag {
SOURCE_DIU = 0x00U,
SOURCE_QUANTAR = 0x02U
};
enum ICWFlag {
ICW_DIU = 0x00U,
ICW_QUANTAR = 0x1B
};
// ---------------------------------------------------------------------------
// Classes for Motorola-specific DFSI Frames (aka "THE" manufacturer)
// ---------------------------------------------------------------------------
class MotFullRateVoice {
public:
// Frame information
static const uint8_t LENGTH = 17;
static const uint8_t SHORTENED_LENGTH = 13;
static const uint8_t ADDITIONAL_LENGTH = 4;
static const uint8_t IMBE_BUF_LEN = 11;
uint8_t frameType;
uint8_t* imbeData;
uint8_t* additionalData;
SourceFlag source;
MotFullRateVoice();
MotFullRateVoice(uint8_t* data);
~MotFullRateVoice();
uint32_t size();
bool decode(uint8_t* data, bool shortened = false);
void encode(uint8_t* data, bool shortened = false);
private:
bool isVoice1or2or10or11();
bool isVoice2or11();
bool isVoice9or18();
};
class MotStartOfStream {
public:
static const uint8_t LENGTH = 10;
static const uint8_t FIXED_MARKER = 0x02;
uint8_t marker = FIXED_MARKER;
RTFlag rt;
StartStopFlag startStop;
StreamTypeFlag streamType;
MotStartOfStream();
MotStartOfStream(uint8_t* data);
bool decode(uint8_t* data);
void encode(uint8_t* data);
};
class MotStartVoiceFrame {
public:
static const uint8_t LENGTH = 22;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
uint8_t adjMM;
MotStartOfStream* startOfStream;
MotFullRateVoice* fullRateVoice;
MotStartVoiceFrame();
MotStartVoiceFrame(uint8_t* data);
~MotStartVoiceFrame();
bool decode(uint8_t* data);
void encode(uint8_t* data);
};
class MotVoiceHeader1 {
public:
static const uint8_t LENGTH = 30;
static const uint8_t HCW_LENGTH = 21;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
uint8_t* header;
MotStartOfStream* startOfStream;
MotVoiceHeader1();
MotVoiceHeader1(uint8_t* data);
~MotVoiceHeader1();
bool decode(uint8_t* data);
void encode(uint8_t* data);
};
class MotVoiceHeader2 {
public:
static const uint8_t LENGTH = 22;
static const uint8_t HCW_LENGTH = 20;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
MotStartOfStream startOfStream;
SourceFlag source;
uint8_t* header;
MotVoiceHeader2();
MotVoiceHeader2(uint8_t* data);
~MotVoiceHeader2();
bool decode(uint8_t* data);
void encode(uint8_t* data);
};
}
}
#endif // __MOT_RTP_FRAMES_H__

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "rtp/MotStartOfStream.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a instance of the MotStartOfStream class.
/// </summary>
MotStartOfStream::MotStartOfStream()
{
rt = DISABLED;
startStop = START;
streamType = VOICE;
}
/// <summary>
/// Initializes a instance of the MotStartOfStream class.
/// </summary>
/// <param name="data"></param>
MotStartOfStream::MotStartOfStream(uint8_t* data)
{
decode(data);
}
/// <summary>
/// Decode a start of stream frame.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
bool MotStartOfStream::decode(const uint8_t* data)
{
assert(data != nullptr);
// Get parameters
rt = (RTFlag)data[2U];
startStop = (StartStopFlag)data[3U];
streamType = (StreamTypeFlag)data[4U];
return true;
}
/// <summary>
/// Encode a start of stream frame.
/// </summary>
/// <param name="data"></param>
void MotStartOfStream::encode(uint8_t* data)
{
assert(data != nullptr);
// Copy data
data[0U] = P25_DFSI_MOT_START_STOP;
data[1U] = FIXED_MARKER;
data[2U] = rt;
data[3U] = startStop;
data[4U] = streamType;
}

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__MOT_START_OF_STREAM_H__)
#define __MOT_START_OF_STREAM_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "rtp/RtpDefines.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a P25 Motorola start of stream packet.
//
// Byte 0 1 2 3
// Bit 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Fixed Mark | RT Mode Flag | Start/Stop | Type Flag |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Reserved |
// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// +-+-+-+-+-+-+-+-+
// ---------------------------------------------------------------------------
class HOST_SW_API MotStartOfStream {
public:
static const uint8_t LENGTH = 10;
static const uint8_t FIXED_MARKER = 0x02;
uint8_t marker = FIXED_MARKER;
RTFlag rt;
StartStopFlag startStop;
StreamTypeFlag streamType;
/// <summary>Initializes a copy instance of the MotStartOfStream class.</summary>
MotStartOfStream();
/// <summary>Initializes a copy instance of the MotStartOfStream class.</summary>
MotStartOfStream(uint8_t* data);
/// <summary>Decode a start of stream frame.</summary>
bool decode(const uint8_t* data);
/// <summary>Encode a start of stream frame.</summary>
void encode(uint8_t* data);
};
} // namespace dfsi
} // namespace p25
#endif // __MOT_START_OF_STREAM_H__

@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "rtp/MotStartVoiceFrame.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a instance of the MotStartVoiceFrame class.
/// </summary>
MotStartVoiceFrame::MotStartVoiceFrame()
{
icw = ICW_DIU;
rssi = 0;
rssiValidity = INVALID;
nRssi = 0;
adjMM = 0;
startOfStream = nullptr;
fullRateVoice = nullptr;
}
/// <summary>
/// Initializes a instance of the MotStartVoiceFrame class.
/// </summary>
/// <param name="data"></param>
MotStartVoiceFrame::MotStartVoiceFrame(uint8_t* data)
{
decode(data);
}
/// <summary>
/// Finalizes a instance of the MotStartVoiceFrame class.
/// </summary>
MotStartVoiceFrame::~MotStartVoiceFrame()
{
delete startOfStream;
delete fullRateVoice;
}
/// <summary>
/// Decode a start voice frame.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
bool MotStartVoiceFrame::decode(const uint8_t* data)
{
assert(data != nullptr);
// Create a new startOfStream
startOfStream = new MotStartOfStream();
// Create a buffer to decode the start record skipping the 10th byte (adjMM)
uint8_t startBuffer[startOfStream->LENGTH];
::memset(startBuffer, 0x00U, startOfStream->LENGTH);
::memcpy(startBuffer, data, 9U);
// Decode start of stream
startOfStream->decode(startBuffer);
// Decode the full rate voice frames
fullRateVoice = new MotFullRateVoice();
uint8_t voiceBuffer[fullRateVoice->SHORTENED_LENGTH];
::memset(voiceBuffer, 0x00U, fullRateVoice->SHORTENED_LENGTH);
voiceBuffer[0U] = data[0U];
::memcpy(voiceBuffer + 1U, data + 10U, fullRateVoice->SHORTENED_LENGTH - 1);
fullRateVoice->decode(voiceBuffer, true);
// Get rest of data
icw = (ICWFlag)data[5U];
rssi = data[6U];
rssiValidity = (RssiValidityFlag)data[7U];
nRssi = data[8U];
adjMM = data[9U];
return true;
}
/// <summary>
/// Encode a start voice frame.
/// </summary>
/// <param name="data"></param>
void MotStartVoiceFrame::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
assert(fullRateVoice != nullptr);
// Encode start of stream
if (startOfStream != nullptr) {
uint8_t buffer[startOfStream->LENGTH];
startOfStream->encode(buffer);
// Copy to data array (skipping first and last bytes)
::memcpy(data + 1U, buffer + 1U, startOfStream->LENGTH - 2);
}
// Encode full rate voice
if (fullRateVoice != nullptr) {
uint8_t buffer[fullRateVoice->SHORTENED_LENGTH];
fullRateVoice->encode(buffer, true);
data[0U] = fullRateVoice->frameType;
::memcpy(data + 10U, buffer + 1U, fullRateVoice->SHORTENED_LENGTH - 1);
}
// Copy the rest
data[5U] = icw;
data[6U] = rssi;
data[7U] = rssiValidity;
data[8U] = nRssi;
data[9U] = adjMM;
}

@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__MOT_START_VOICE_FRAME_H__)
#define __MOT_START_VOICE_FRAME_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "rtp/RtpDefines.h"
#include "rtp/MotStartOfStream.h"
#include "rtp/MotFullRateVoice.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a P25 Motorola voice frame 1/10 start.
//
// Byte 0 1 2 3
// Bit 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Encoded Motorola Start of Stream |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ICW Flag ? | RSSI | RSSI Valid | RSSI |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Adj MM ? | Full Rate Voice Frame |
// +-+-+-+-+-+-+-+-+ +
// | |
// + +
// | |
// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// +=+=+=+=+=+=+=+=+
// ---------------------------------------------------------------------------
class HOST_SW_API MotStartVoiceFrame {
public:
static const uint8_t LENGTH = 22;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
uint8_t adjMM;
MotStartOfStream* startOfStream;
MotFullRateVoice* fullRateVoice;
/// <summary>Initializes a copy instance of the MotStartVoiceFrame class.</summary>
MotStartVoiceFrame();
/// <summary>Initializes a copy instance of the MotStartVoiceFrame class.</summary>
MotStartVoiceFrame(uint8_t* data);
/// <summary>Finalizes a instance of the MotStartVoiceFrame class.</summary>
~MotStartVoiceFrame();
/// <summary>Decode a start voice frame.</summary>
bool decode(const uint8_t* data);
/// <summary>Encode a start voice frame.</summary>
void encode(uint8_t* data);
};
} // namespace dfsi
} // namespace p25
#endif // __MOT_START_VOICE_FRAME_H__

@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "rtp/MotVoiceHeader1.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a instance of the MotVoiceHeader1 class.
/// </summary>
MotVoiceHeader1::MotVoiceHeader1()
{
icw = ICW_DIU;
rssi = 0;
rssiValidity = INVALID;
nRssi = 0;
startOfStream = nullptr;
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
}
/// <summary>
/// Initializes a instance of the MotVoiceHeader1 class.
/// </summary>
/// <param name="data"></param>
MotVoiceHeader1::MotVoiceHeader1(uint8_t* data)
{
decode(data);
}
/// <summary>
/// Finalizes a instance of the MotVoiceHeader1 class.
/// </summary>
MotVoiceHeader1::~MotVoiceHeader1()
{
delete startOfStream;
delete[] header;
}
/// <summary>
/// Decode a voice header 1 frame.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
bool MotVoiceHeader1::decode(const uint8_t* data)
{
assert(data != nullptr);
// Create a start of stream
startOfStream = new MotStartOfStream();
uint8_t buffer[startOfStream->LENGTH];
::memset(buffer, 0x00U, startOfStream->LENGTH);
// We copy the bytes from [1:4]
::memcpy(buffer + 1U, data + 1U, 4);
startOfStream->decode(buffer);
// Decode the other stuff
icw = (ICWFlag)data[5U];
rssi = data[6U];
rssiValidity = (RssiValidityFlag)data[7U];
nRssi = data[8U];
// Our header includes the trailing source and check bytes
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
::memcpy(header, data + 9U, HCW_LENGTH);
return true;
}
/// <summary>
/// Encode a voice header 1 frame.
/// </summary>
/// <param name="data"></param>
void MotVoiceHeader1::encode(uint8_t* data)
{
assert(data != nullptr);
assert(startOfStream != nullptr);
data[0U] = P25_DFSI_MOT_VHDR_1;
if (startOfStream != nullptr) {
uint8_t buffer[startOfStream->LENGTH];
::memset(buffer, 0x00U, startOfStream->LENGTH);
startOfStream->encode(buffer);
// Copy the 4 start record bytes from the start of stream frame
::memcpy(data + 1U, buffer + 1U, 4U);
}
data[5U] = icw;
data[6U] = rssi;
data[7U] = (uint8_t)rssiValidity;
data[8U] = nRssi;
// Our header includes the trailing source and check bytes
if (header != nullptr) {
::memcpy(data + 9U, header, HCW_LENGTH);
}
}

@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__MOT_VOICE_HEADER_1_H__)
#define __MOT_VOICE_HEADER_1_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "rtp/RtpDefines.h"
#include "rtp/MotStartOfStream.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a P25 Motorola voice header frame 1.
//
// Byte 0 1 2 3
// Bit 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Encoded Motorola Start of Stream |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ICW Flag ? | RSSI | RSSI Valid | RSSI |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Header Control Word |
// + +
// | |
// + +
// | |
// + +
// | |
// + +
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Src Flag |
// +-+-+-+-+-+-+-+-+
// ---------------------------------------------------------------------------
class HOST_SW_API MotVoiceHeader1 {
public:
static const uint8_t LENGTH = 30;
static const uint8_t HCW_LENGTH = 21;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
uint8_t* header;
MotStartOfStream* startOfStream;
/// <summary>Initializes a copy instance of the MotVoiceHeader1 class.</summary>
MotVoiceHeader1();
/// <summary>Initializes a copy instance of the MotVoiceHeader1 class.</summary>
MotVoiceHeader1(uint8_t* data);
/// <summary>Finalizes a instance of the MotVoiceHeader1 class.</summary>
~MotVoiceHeader1();
/// <summary>Decode a voice header 1 frame.</summary>
bool decode(const uint8_t* data);
/// <summary>Encode a voice header 1 frame.</summary>
void encode(uint8_t* data);
};
} // namespace dfsi
} // namespace p25
#endif // __MOT_VOICE_HEADER_1_H__

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "rtp/MotVoiceHeader2.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/Utils.h"
#include "common/Log.h"
#include <cassert>
#include <cstring>
using namespace p25;
using namespace dfsi;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a instance of the MotVoiceHeader2 class.
/// </summary>
MotVoiceHeader2::MotVoiceHeader2()
{
source = SOURCE_QUANTAR;
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
}
/// <summary>
/// Initializes a instance of the MotVoiceHeader2 class.
/// </summary>
/// <param name="data"></param>
MotVoiceHeader2::MotVoiceHeader2(uint8_t* data)
{
decode(data);
}
/// <summary>
/// Finalizes a instance of the MotVoiceHeader2 class.
/// </summary>
MotVoiceHeader2::~MotVoiceHeader2()
{
delete[] header;
}
/// <summary>
/// Decode a voice header 2 frame.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
bool MotVoiceHeader2::decode(const uint8_t* data)
{
assert(data != nullptr);
source = (SourceFlag)data[21];
header = new uint8_t[HCW_LENGTH];
::memset(header, 0x00U, HCW_LENGTH);
::memcpy(header, data + 1U, HCW_LENGTH);
return true;
}
/// <summary>
/// Encode a voice header 2 frame.
/// </summary>
/// <param name="data"></param>
void MotVoiceHeader2::encode(uint8_t* data)
{
assert(data != nullptr);
data[0U] = P25_DFSI_MOT_VHDR_2;
if (header != nullptr) {
::memcpy(data + 1U, header, HCW_LENGTH);
}
data[LENGTH - 1U] = (uint8_t)source;
}

@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__MOT_VOICE_HEADER_2_H__)
#define __MOT_VOICE_HEADER_2_H__
#include "Defines.h"
#include "common/Defines.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "rtp/RtpDefines.h"
#include "rtp/MotStartOfStream.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a P25 Motorola voice header frame 2.
//
// Byte 0 1 2 3
// Bit 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Header Control Word |
// + +
// | |
// + +
// | |
// + +
// | |
// + +
// | |
// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | | Reserved |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ---------------------------------------------------------------------------
class HOST_SW_API MotVoiceHeader2 {
public:
static const uint8_t LENGTH = 22;
static const uint8_t HCW_LENGTH = 20;
ICWFlag icw;
uint8_t rssi;
RssiValidityFlag rssiValidity;
uint8_t nRssi;
MotStartOfStream startOfStream;
SourceFlag source;
uint8_t* header;
/// <summary>Initializes a copy instance of the MotVoiceHeader2 class.</summary>
MotVoiceHeader2();
/// <summary>Initializes a copy instance of the MotVoiceHeader2 class.</summary>
MotVoiceHeader2(uint8_t* data);
/// <summary>Finalizes a instance of the MotVoiceHeader2 class.</summary>
~MotVoiceHeader2();
/// <summary>Decode a voice header 2 frame.</summary>
bool decode(const uint8_t* data);
/// <summary>Encode a voice header 2 frame.</summary>
void encode(uint8_t* data);
};
} // namespace dfsi
} // namespace p25
#endif // __MOT_VOICE_HEADER_2_H__

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - Common Library
* GPLv2 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__RTP_DEFINES_H__)
#define __RTP_DEFINES_H__
#include "common/Defines.h"
namespace p25
{
namespace dfsi
{
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/// <summary>
///
/// </summary>
enum RTFlag {
ENABLED = 0x02U,
DISABLED = 0x04U
};
/// <summary>
///
/// </summary>
enum StartStopFlag {
START = 0x0CU,
STOP = 0x25U
};
/// <summary>
///
/// </summary>
enum StreamTypeFlag {
VOICE = 0x0BU
};
/// <summary>
///
/// </summary>
enum RssiValidityFlag {
INVALID = 0x00U,
VALID = 0x1A
};
/// <summary>
///
/// </summary>
enum SourceFlag {
SOURCE_DIU = 0x00U,
SOURCE_QUANTAR = 0x02U
};
/// <summary>
///
/// </summary>
enum ICWFlag {
ICW_DIU = 0x00U,
ICW_QUANTAR = 0x1B
};
} // namespace dfsi
} // namespace p25
#endif // __RTP_DEFINES_H__

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-only
/**
* Digital Voice Modem - 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 / DFSI peer application
* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost)
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Patrick McDonnell, W3AXL
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__RTP_FRAMES_H__)
#define __RTP_FRAMES_H__
#include "Defines.h"
#include "rtp/MotFullRateVoice.h"
#include "rtp/MotStartOfStream.h"
#include "rtp/MotStartVoiceFrame.h"
#include "rtp/MotVoiceHeader1.h"
#include "rtp/MotVoiceHeader2.h"
#endif // __RTP_FRAMES_H__

@ -1 +1 @@
Subproject commit 8d6611ef703abd74d08bce2afe46d0e09676383a
Subproject commit 882244795b9bec1f2c86d596d4ab4ecd4346c8b9

@ -1 +1 @@
Subproject commit d79b1d7b8d4b175be86cd0e5dc844c6ca5ae6733
Subproject commit a9ac06bc01309355b0da3530a5009c2c42cf5de5

@ -167,7 +167,7 @@ int UARTPort::write(const uint8_t* buffer, uint32_t length)
n = ::write(m_fd, buffer + ptr, length - ptr);
if (n < 0) {
if (errno != EAGAIN) {
::LogError(LOG_HOST, "Error returned from write(), errno=%d", errno);
::LogError(LOG_HOST, "Error returned from write(), errno=%d (%s)", errno, strerror(errno));
return -1;
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.