diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a22ae44c5..4cf4da487 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: needs: [setup] strategy: matrix: - arch: ["amd64", "arm", "aarch64", "armhf"] + arch: ["amd64", "arm", "aarch64"] runs-on: ubuntu-22.04 env: PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }} @@ -105,12 +105,8 @@ jobs: build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'" fi - if [[ "${{ matrix.arch }}" == 'armhf' ]]; then - cmake $(echo $build_args) -DCROSS_COMPILE_RPI_ARM=1 . - else - cmake $(echo $build_args) \ - -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . - fi + cmake $(echo $build_args) \ + -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . make -j $(nproc) make tarball @@ -146,7 +142,7 @@ jobs: needs: [setup, build, create-release] strategy: matrix: - arch: ["amd64", "arm", "aarch64", "armhf"] + arch: ["amd64", "arm", "aarch64"] runs-on: ubuntu-22.04 env: PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.DATE }}-${{ matrix.arch }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d22595b9..6badffa1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: needs: [setup] strategy: matrix: - arch: ["amd64", "arm", "aarch64", "armhf"] + arch: ["amd64", "arm", "aarch64"] runs-on: ubuntu-22.04 env: PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }} @@ -77,12 +77,8 @@ jobs: build_args="$build_args -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-s' -DCMAKE_CXX_FLAGS='-s'" - if [[ "${{ matrix.arch }}" == 'armhf' ]]; then - cmake $(echo $build_args) -DCROSS_COMPILE_RPI_ARM=1 . - else - cmake $(echo $build_args) \ - -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . - fi + cmake $(echo $build_args) \ + -D "CROSS_COMPILE_$(echo '${{ matrix.arch }}' | tr '[:lower:]' '[:upper:]')=1" . make -j $(nproc) make strip @@ -118,7 +114,7 @@ jobs: needs: [setup, build, create-release] strategy: matrix: - arch: ["amd64", "arm", "aarch64", "armhf"] + arch: ["amd64", "arm", "aarch64"] runs-on: ubuntu-22.04 env: PACKAGENAME: ${{ needs.setup.outputs.APPNAME }}-${{ needs.setup.outputs.VERSION }}-${{ matrix.arch }} diff --git a/CMakeLists.txt b/CMakeLists.txt index d41d1f288..c91577517 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,11 @@ endif (DISABLE_WEBSOCKETS) # Cross-compile options option(CROSS_COMPILE_ARM "Cross-compile for 32-bit ARM" off) option(CROSS_COMPILE_AARCH64 "Cross-compile for 64-bit ARM" off) -option(CROSS_COMPILE_RPI_ARM "Cross-compile for (old RPi) 32-bit ARM" off) + option(COMPILE_WIN32 "Compile for Win32" off) +set(ENABLE_LIBDW_SUPPORT ON) + if (COMPILE_WIN32) set(ARCH amd64) set(CMAKE_SYSTEM_PROCESSOR amd64) @@ -73,69 +75,37 @@ else() endif (COMPILE_WIN32) if (CROSS_COMPILE_ARM) + set(CMAKE_CROSSCOMPILING TRUE) + set(ARCH armhf CACHE STRING "Architecture" FORCE) + set(CMAKE_SYSTEM_PROCESSOR armhf CACHE STRING "System Processor" FORCE) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf CACHE STRING "Package Architecture" FORCE) + message(CHECK_START "Target Architecture - ${ARCH}") + set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE) set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE) - set(ARCH armhf) - set(CMAKE_SYSTEM_PROCESSOR armhf) - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf) + set(OPENSSL_ROOT_DIR /usr/lib/arm-linux-gnueabihf) - message(CHECK_START "Target Architecture - ${ARCH}") + set(CMAKE_INCLUDE_PATH /usr/arm-linux-gnueabihf/include) + set(CMAKE_LIBRARY_PATH /usr/arm-linux-gnueabihf/lib) + message(CHECK_START "Cross compiling for 32-bit ARM - ${CMAKE_C_COMPILER}") endif (CROSS_COMPILE_ARM) if (CROSS_COMPILE_AARCH64) + set(CMAKE_CROSSCOMPILING TRUE) + set(ARCH aarch64 CACHE STRING "Architecture" FORCE) + set(CMAKE_SYSTEM_PROCESSOR aarch64 CACHE STRING "System Processor" FORCE) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE aarch64 CACHE STRING "Package Architecture" FORCE) + message(CHECK_START "Target Architecture - ${ARCH}") + set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc CACHE STRING "C compiler" FORCE) set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++ CACHE STRING "C++ compiler" FORCE) - set(ARCH arm64) - set(CMAKE_SYSTEM_PROCESSOR arm64) - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64) + set(OPENSSL_ROOT_DIR /usr/lib/aarch64-linux-gnu) - message(CHECK_START "Target Architecture - ${ARCH}") + set(CMAKE_INCLUDE_PATH /usr/aarch64-linux-gnu/include) + set(CMAKE_LIBRARY_PATH /usr/aarch64-linux-gnu/lib) message(CHECK_START "Cross compiling for 64-bit ARM - ${CMAKE_C_COMPILER}") endif (CROSS_COMPILE_AARCH64) -option(WITH_RPI_ARM_TOOLS "Specifies the location for the RPI ARM tools" off) -if (WITH_RPI_ARM_TOOLS) - set(RPI_ARM_TOOLS ${WITH_RPI_ARM_TOOLS}) - message(CHECK_START "With RPi 1 Tools: ${RPI_ARM_TOOLS}") -endif (WITH_RPI_ARM_TOOLS) - -if (CROSS_COMPILE_RPI_ARM) - if (NOT WITH_RPI_ARM_TOOLS) - message("-- Cloning legacy Raspberry Pi compilation toolchain") - include(FetchContent) - FetchContent_Declare( - RPiTools - GIT_REPOSITORY https://github.com/raspberrypi/tools.git - ) - FetchContent_MakeAvailable(RPiTools) - set(CMAKE_C_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE) - set(CMAKE_CXX_COMPILER ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE) - - message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src") - execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - - set(OPENSSL_ROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib) - else() - set(CMAKE_C_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc CACHE STRING "C compiler" FORCE) - set(CMAKE_CXX_COMPILER ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ CACHE STRING "C++ compiler" FORCE) - - message(CHECK_START "Apply OpenSSL library binaries for cross compling (old RPi) 32-bit ARM - ${RPI_ARM_TOOLS}") - execute_process(COMMAND tar xzf contrib/openssl_cross_patch.RPI_ARM.tar.gz -C ${RPI_ARM_TOOLS} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) - - set(OPENSSL_ROOT_DIR ${RPI_ARM_TOOLS}/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/usr/lib) - endif () - - set(ARCH armhf) - set(CMAKE_SYSTEM_PROCESSOR armhf) - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf) - message(CHECK_START "Target Architecture - ${ARCH}") - message(CHECK_START "Cross compiling for (old RPi) 32-bit ARM - ${CMAKE_C_COMPILER}") - - # No TUI for this - set(ENABLE_TUI_SUPPORT OFF) - message(CHECK_START "Enable TUI support - no; for simplicity RPI_ARM cross-compiling does not support TUI.") -endif (CROSS_COMPILE_RPI_ARM) - # search for programs in the build host directories set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) @@ -174,11 +144,6 @@ if (CROSS_COMPILE_ARM) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-psabi") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-psabi") endif (CROSS_COMPILE_ARM) -if (CROSS_COMPILE_RPI_ARM) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -std=c99") -endif (CROSS_COMPILE_RPI_ARM) if (COMPILE_WIN32) set(CMAKE_C_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR") set(CMAKE_CXX_FLAGS "/EHsc /D_WIN32 /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR") @@ -191,6 +156,7 @@ set(CMAKE_INSTALL_PREFIX "/usr/local") # # Library Inclusions # +# ASIO if (NOT ASIO_INCLUDED) option(WITH_ASIO "Manually specify the location for the ASIO library" off) if (WITH_ASIO) @@ -210,6 +176,7 @@ if (NOT ASIO_INCLUDED) endif (WITH_ASIO) endif (NOT ASIO_INCLUDED) +# WebSocket++ if (NOT DISABLE_WEBSOCKETS) if (NOT WEBSOCKETPP_INCLUDED) message("-- Cloning WebSocket++") @@ -217,7 +184,7 @@ if (NOT DISABLE_WEBSOCKETS) FetchContent_Declare( WEBSOCKETPP GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git - GIT_TAG 56123c87598f8b1dd471be83ca841ceae07f95ba # 0.8.2 + GIT_TAG 4dfe1be74e684acca19ac1cf96cce0df9eac2a2d # 0.8.2 with modified CMake version ) FetchContent_MakeAvailable(WEBSOCKETPP) add_subdirectory(${websocketpp_SOURCE_DIR} EXCLUDE_FROM_ALL) @@ -225,6 +192,33 @@ if (NOT DISABLE_WEBSOCKETS) endif (NOT WEBSOCKETPP_INCLUDED) endif (NOT DISABLE_WEBSOCKETS) +# elfutils (libdw-dev) +if (ENABLE_LIBDW_SUPPORT) + find_path(LIBDW_INCLUDE_DIR NAMES elfutils/libdw.h elfutils/libdwfl.h HINTS /usr /usr/local PATH_SUFFIXES include) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libdw DEFAULT_MSG LIBDW_INCLUDE_DIR) + + if (LIBDW_FOUND) + message("-- libdw (libdw-dev) found, detailed backtrace support enabled") + add_definitions(-DBACKWARD_HAS_DW=1) + set(LIBDW_LIBRARY "dw") + else() + message("-- libdw (libdw-dev) not found, simple backtrace only") + add_definitions(-DBACKWARD_HAS_DW=0 -DBACKWARD_HAS_BACKTRACE_SYMBOL=1) + set(LIBDW_INCLUDE_DIR "") + set(LIBDW_LIBRARY "") + endif (LIBDW_FOUND) + + mark_as_advanced(LIBDW_INCLUDE_DIR LIBDW_LIBRARY) +else() + message("-- libdw (libdw-dev) disabled, simple backtrace only") + add_definitions(-DBACKWARD_HAS_DW=0 -DBACKWARD_HAS_BACKTRACE_SYMBOL=1) + set(LIBDW_INCLUDE_DIR "") + set(LIBDW_LIBRARY "") +endif (ENABLE_LIBDW_SUPPORT) + +# FinalCut if (ENABLE_TUI_SUPPORT AND NOT FC_INCLUDED) message("-- Cloning finalcut") include(FetchContent) @@ -342,22 +336,6 @@ if (NOT TARGET strip) COMMAND aarch64-linux-gnu-strip -s dvmbridge COMMAND aarch64-linux-gnu-strip -s dvmpatch) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) - 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 dvmbridge - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/_deps/rpitools-src/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch) - 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 dvmbridge - COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmpatch) - endif () else() if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) add_custom_target(strip diff --git a/Makefile b/Makefile index 0b859b8bc..f382d818d 100644 --- a/Makefile +++ b/Makefile @@ -32,14 +32,6 @@ aarch64: && make -j $(nproc) @echo 'Successfully compiled for AARCH64' -armhf: - @echo 'Cross-Compiling for ARMHF' - mkdir -p "build/$@" && cd "build/$@" \ - && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-s" -DCMAKE_CXX_FLAGS="-s" \ - -DCROSS_COMPILE_RPI_ARM=1 ../.. \ - && make -j $(nproc) - @echo 'Successfully compiled for ARMHF' - clean: @echo 'Removing all temporary files' git clean -ffxd diff --git a/README.md b/README.md index 5e2024750..ff05af0f5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This project suite generates a few executables: ### Core Applications - `dvmhost` host software that connects to the DVM modems (both air interface for repeater and hotspot or P25 DFSI for commerical P25 hardware) and is the primary data processing application for digital modes. [See configuration](#dvmhost-configuration) to configure and calibrate. -- `dvmfne` a network "core", this provides a central server for `dvmhost` instances to connect to and be networked with, allowing relay of traffic and other data between `dvmhost` instances and other `dvmfne` instances. [See configuration](#dvmfne-configuration) to configure. +- `dvmfne` a network core, this provides a central server for `dvmhost` instances (and other instances like consoles or `dvmbridge`s) to connect to and be networked with other instances, allowing switching of traffic and other data between `dvmhost` instances, as well as other peered `dvmfne` instances. [See configuration](#dvmfne-configuration) to configure. - `dvmbridge` a analog/PCM audio bridge, this provides the capability for analog or PCM audio resources to be connected to a `dvmfne` instance, allowing realtime vocoding of traffic. [See configuration](#dvmbridge-configuration) to configure. - `dvmpatch` a talkgroup patching utility, this provides the capability to manually patch talkgroups of the same digital mode together. [See configuration](#dvmpatch-configuration) to configure. - `dvmcmd` a simple command-line utility to send remote control commands to a `dvmhost` or `dvmfne` instance with REST API configured. @@ -43,11 +43,14 @@ The DVM Host software requires the library dependancies below. Generally, the so Alternatively, if you download the ASIO library from the ASIO website and extract it to a location, you can specify the path to the ASIO library using: `-DWITH_ASIO=/path/to/asio`. This method is required when cross-compiling for old Raspberry Pi ARM 32 bit. +If you want detailed stacktrace output on a crash, for compilation ensure `libdw-dev` is also installed. (`apt-get install libdw-dev`). For runtime you will need the `elfutils` package to be installed. (`apt-get install elfutils`). + If cross-compiling ensure you install the appropriate libraries, for example for AARCH64/ARM64: ``` sudo dpkg --add-architecture arm64 sudo apt-get update sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64 +sudo apt-get install libdw-dev:arm64 ``` ### Build Instructions @@ -89,9 +92,8 @@ sudo apt-get install libasio-dev:arm64 libncurses-dev:arm64 libssl-dev:arm64 If cross-compiling is required (for either ARM 32bit, 64bit or old Raspberry Pi ARM 32bit), the CMake build system has some options: -- `-DCROSS_COMPILE_ARM=1` - This will cross-compile dvmhost for generic ARM 32bit. (RPi4 running 32-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) -- `-DCROSS_COMPILE_AARCH64=1` - This will cross-compile dvmhost for generic ARM 64bit. (RPi4 running 64-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) -- `-DCROSS_COMPILE_RPI_ARM=1` - This will cross-compile for old Raspberry Pi ARM 32 bit. (typically this will be the RPi1, 2 and 3 platforms; see build notes, linked below) +- `-DCROSS_COMPILE_ARM=1` - This will cross-compile dvmhost for generic ARM 32bit. (RPi4+ running 32-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) +- `-DCROSS_COMPILE_AARCH64=1` - This will cross-compile dvmhost for generic ARM 64bit. (RPi4+ running 64-bit distro's can fall into this category [on Debian/Rasbpian anything bullseye or newer]) Please note cross-compliation requires you to have the appropriate development packages installed for your system. For ARM 32-bit, on Debian/Ubuntu OS install the "arm-linux-gnueabihf-gcc" and "arm-linux-gnueabihf-g++" packages. For ARM 64-bit, on Debian/Ubuntu OS install the "aarch64-linux-gnu-gcc" and "aarch64-linux-gnu-g++" packages. @@ -99,7 +101,7 @@ Please note cross-compliation requires you to have the appropriate development p ### Setup TUI (Text-based User Interface) -Since, DVM Host 3.5, the old calibration and setup modes have been deprecated in favor of a ncurses-based TUI. This TUI is optional, and DVM Host can still be compiled without it for systems or devices that cannot utilize it. +The DVM TUI applications are optional, and dvmhost can still be compiled without it for systems or devices that cannot utilize it. - `-DENABLE_SETUP_TUI=0` - This will disable the setup/calibration TUI interface. - `-DENABLE_TUI_SUPPORT=0` - This will disable TUI support project wide. Any projects that require TUI support will not compile, or will have any TUI components disabled. @@ -203,6 +205,19 @@ for step 4 to observe frequency error.) This source repository contains configuration example files within the configs folder, please review `fne-config.example.yml` for the `dvmfne` for details on various configurable options. When first setting up a FNE instance, it is important to properly configure a `talkgroup_rules.example.yml` file, this file defines all the various rules for valid talkgroups and other settings. +The other configurables on a `dvmfne` instance are within the `fne-config.example.yml`. Some of these are Radio ID (RID) access control listings, Peer ID (PID) access control listings, adjacent site maps for systems with trunked `dvmhost` instances connected, annd many other parameters. + +Here is a listing of files in the configs folder in this repo that pertain to FNE configuration: +- `adj_site_map.example.yml` - This is an example configuration file configuring adjacent site mappings for trunked `dvmhost` instances. +- `fne-config.example.yml` - This is the main/primary example configuration file for an FNE instance. +- `peer_list.example.dat` - This is a simple CSV-style file containing access control permissions for peers allowed to connect to the FNE (this includes both downstream peers (like `dvmhost` or `dvmbridge`) and other `dvmfne` instances connecting *to* the FNE instance). +- `rid_acl.example.dat` - This is a simple CSV-style file containing the access control permissions for radio ID (RID)s allowed to use a configured system/network. +- `talkgroup_rules.example.yml` - This is the second most important configuration file for an FNE, this file describes all the talkgroups and their related access control and configuration parameters. + +There is another file that is attributed to the FNE that an example is not provided for and that is the `key-container.ekc` file. This file provides cryptographic material needed for providing keyloading functionality across a configured system/network. + +Most parameters within the `fne-config.example.yml` should be set to reasonable defaults for simply just starting up a FNE, the only parameters in the configuration that *must* be reviewed before starting up an instance are proper file paths for the ACL and other files used by the FNE. + There is no other real configuration for a `dvmfne` instance other then setting the appropriate parameters within the configuration files. ## dvmbridge Configuration @@ -254,12 +269,14 @@ usage: ./dvmhost [-vhdf] [--syslog] [--setup] [--cal][--boot] [-c ] +usage: ./dvmfne [-vhf][-p][--syslog][-c ] -v show version information -h show this screen -f foreground mode + -p promiscuous hub mode + --syslog force logging to syslog -c specifies the configuration file to use @@ -343,8 +360,8 @@ to run on hardware below the minimal requirements, its is unlikely to provide a `dvmfne`'s requirements can change radically depending on network size. Larger, busier networks will require far more resources then smaller, less busy networks. (`dvmfne` has been tested with daily unique call counts of up to 100,000+ calls on a x86_64 Server with 8GB RAM and 8-core processor, and in this environment it runs comfortably.) -- Minimal Requirements (known "working"): x86_64 Server, 2MB RAM, Dual Core Processor. -- Requirements: x86_64 Server, 2GB RAM or better, Dual/Quad or better Core Processor. +- Minimal Requirements (known "working"): x86_64 Server, 2GB RAM, Quad Core Processor. +- Requirements: x86_64 Server, 4GB RAM or better, Quad or better Core Processor. ## Project Notes @@ -355,12 +372,13 @@ counts of up to 100,000+ calls on a x86_64 Server with 8GB RAM and 8-core proces - For maximize size reduction before performing a `make install`, `make old_install` or `make tarball` it is recommended to run `make strip` to strip any debug symbols or other unneeded information from the resultant binaries. -- By default when cross-compiling for old RPi 1 using the Debian/Ubuntu OS, the toolchain will attempt to fetch and clone the tools automatically. If you already have a copy of these tools, you can specify the location for them with the `-DWITH_RPI_ARM_TOOLS=/path/to/tools` -- For old RPi 1, 2 or 3 using Debian/Ubuntu OS install the standard ARM embedded toolchain (typically "arm-none-eabi-gcc" and "arm-none-eabi-g++"). The CMake build system will automatically attempt to clone down the compilation tools, if you already have the RPI_ARM compilation tools installed use the instructions the above bullet to point to them (this will prevent CMake from attempting to clone the compilation tools). -- The old RPi 1, 2 or 3 builds do not support the TUI when cross compiling. If you require the TUI on these platforms, you have to build the project directly on the target platform vs cross compiling. - - If you have old configuration files, missing comments or new parameters, there is a tool provided in the "tools" directory of the project called `config_annotator.py` this is a Python CLI tool designed to compare an existing configuration file against the example configuration file and recomment and add missing parameters (along with removing illegal/invalid parameters). It is recommended to backup your existing configuration file before running this tool on it. *This tool is only designed for the `dvmhost` configuration file, and no other configuration file!* +- By default Linux may restrict the maximum size of the receive and send buffers used by the kernel for network traffic. Please check the limits with `sudo sysctl net.core.rmem_max` and `sudo sysctl net.core.wmem_max`, these should be at least 512K (524288), while DVM will operate in lower +limits, you will see startup errors similar to: "Could not resize socket recv buffer, XXXXXX != 524288", or "Could not resize socket send buffer, XXXXXX != 524288". See the documentation for your specific distribution of Linux to adjust these parameters. + +- Offically support for cross-compiling for RPi 1/2/3 was removed in DVM R05A02. There is no support for cross-compiling on these platforms. It is recommended *not* to run DVM on these platforms and to use more modern RPi4+. + ## Security Warnings It is highly recommended that the REST API interface not be exposed directly to the internet. If such exposure is wanted/needed, it is highly recommended to proxy the dvmhost REST API through a modern web server (like nginx for example) rather then directly exposing dvmhost's REST API port. diff --git a/configs/bridge-config.example.yml b/configs/bridge-config.example.yml index 436b5e1bf..978f90505 100644 --- a/configs/bridge-config.example.yml +++ b/configs/bridge-config.example.yml @@ -10,11 +10,10 @@ daemon: true # # Logging Levels: # 1 - Debug -# 2 - Message -# 3 - Informational -# 4 - Warning -# 5 - Error -# 6 - Fatal +# 2 - Informational +# 3 - Warning +# 4 - Error +# 5 - Fatal # log: # Console display logging level (used when in foreground). @@ -80,19 +79,9 @@ network: # Flag indicating UDP audio should follow the USRP format. udpUsrp: false - # Delay in-between UDP audio frames (in ms). - # (Some applications will send RTP/PCM audio too fast, requiring a delay in-between packets to - # be added for appropriate IMBE audio pacing. For most cases a 20ms delay will properly pace - # audio frames. If set to 0, no frame pacing or jitter buffer will be applied and audio will be - # encoded as fast as it is received.) - udpInterFrameDelay: 0 - # Jitter Buffer Length (in ms). - # (This is only applied if utilizing inter frame delay, otherwise packet timing is assumed to be - # properly handled by the source.) - udpJitter: 200 - - # Flag indicating the UDP audio will be padded with silence during hang time before end of call. - udpHangSilence: true + # Flag indicating UDP audio frame timing will be performed at the bridge. + # (This allows the sending source to send audio as fast as it wants.) + udpFrameTiming: false # Traffic Encryption tek: @@ -190,3 +179,15 @@ system: rtsPttEnable: false # Serial port device for RTS PTT control (e.g., /dev/ttyUSB0). rtsPttPort: "/dev/ttyUSB0" + # Hold-off time (ms) before clearing RTS PTT after last audio output. + rtsPttHoldoffMs: 250 + + # CTS COR Configuration + # Flag indicating whether CTS-based COR detection is enabled. + ctsCorEnable: false + # Serial port device for CTS COR (e.g., /dev/ttyUSB0). Often same as RTS PTT. + ctsCorPort: "/dev/ttyUSB0" + # Flag indicating whether to invert COR logic (if true, COR LOW triggers instead of HIGH). + ctsCorInvert: false + # Hold-off time (ms) before ending call after CTS COR deasserts. + ctsCorHoldoffMs: 250 diff --git a/configs/config.example.yml b/configs/config.example.yml index 14359fc37..233511bc8 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -10,11 +10,10 @@ daemon: true # # Logging Levels: # 1 - Debug -# 2 - Message -# 3 - Informational -# 4 - Warning -# 5 - Error -# 6 - Fatal +# 2 - Informational +# 3 - Warning +# 4 - Error +# 5 - Fatal # log: # Flag indicating whether or not the NON-AUTHORITATIVE errors should be logged. @@ -640,10 +639,8 @@ system: # V.24 Modem Configuration # dfsi: - # RT/RT flag enabled (0x02) or disabled (0x04) + # RT/RT flag. rtrt: true - # Use the DIU source flag (0x00) instead of the quantar source flag (0x02) - diu: true # Jitter buffer length in ms jitter: 200 # Timer which will reset local/remote call flags if frames aren't received longer than this time in ms diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 3ef1f78f8..ea9d18527 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -9,11 +9,10 @@ daemon: true # Logging Configuration # Logging Levels: # 1 - Debug -# 2 - Message -# 3 - Informational -# 4 - Warning -# 5 - Error -# 6 - Fatal +# 2 - Informational +# 3 - Warning +# 4 - Error +# 5 - Fatal # log: # Console display logging level (used when in foreground). @@ -31,15 +30,18 @@ log: # # Master +# (This is the endpoint that downstream peers connect to for this FNE instance.) # master: # Network Peer ID + # NOTE: This ID is a uniquely identifying number. It *MUST* be a unique number across any networks this FNE + # instance connects to. Failure to use a unique number *WILL* cause network issues. peerId: 9000100 # Hostname/IP address to listen on (blank for all). address: 0.0.0.0 # Port number to listen on. - # NOTE: This port number includes itself for traffic, and master port + 1 for diagnostics and activity logging. (For - # example, a master port of 62031 will use 62032 for diagnostic and activity messages.) + # NOTE: This port number includes itself for traffic, and master port + 1 for diagnostics and activity logging. (For + # example, a master port of 62031 will use 62032 for diagnostic and activity messages.) port: 62031 # FNE access password. password: RPT1234 @@ -48,9 +50,24 @@ master: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # + # High Availability + # + ha: + # Flag indicating high availability advertisements are enabled. + enable: false + # WAN IP address of this FNE master. + # This IP address is advertised to the network as a globally WAN accessible IP. + advertisedWANAddress: 1.2.3.4 + # WAN port for this FNE master. + # This port is advertised to the network as a globally WAN accessible port. + advertisedWANPort: 62031 + # Flag indicating whether or not denied traffic will be logged. # (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.) logDenials: false + # Flag indicating whether or not calls start/end events from a upstream peer will be logged. + logUpstreamCallStartEnd: true # Maximum number of concurrent packet processing workers. workers: 16 @@ -58,6 +75,17 @@ master: # Maximum permitted connections (hard maximum is 250 peers). connectionLimit: 100 + # Flag indicating whether or not the peer spanning tree is enabled. + # NOTE: This should not be disabled. Disabling this can cause network loops + # and other issues in a multi-peer FNE network. + enableSpanningTree: true + # Flag indicating whether or not spanning tree changes will be logged. + logSpanningTreeChanges: false + # Flag indicating whether or not the spanning tree allows fast peer reconnects. + # (This is mainly useful for a peer announcing the same master to reconnect rapidly, inbetween + # spanning tree updates.) + spanningTreeFastReconnect: true + # Flag indicating whether or not peer pinging will be reported. reportPeerPing: true @@ -91,6 +119,16 @@ master: # Flag indicating whether or not a parrot TG call will only be sent to the originating peer. parrotOnlyToOrginiatingPeer: false + # Flag indicating whether or not P25 OTAR KMF services are enabled. + kmfServicesEnabled: false + # Port number to listen on for P25 OTAR KMF services. + kmfOtarPort: 64414 + # Flag indicating whether or not verbose debug logging for P25 OTAR KMF services is enabled. + kmfDebug: false + + # Amount of time in seconds for a call collision to last before switching over the source of a call. + callCollisionTimeout: 5 + # Flag indicating whether or not a grant responses will only be sent to TGs with affiliations, if the TG is configured for affiliation gating. restrictGrantToAffiliatedOnly: false # Flag indicating whether or not a private call will only be routed to the network peers the RID registers with. @@ -103,8 +141,10 @@ master: # Flag indicating whether or not a conventional site can override affiliation rules. allowConvSiteAffOverride: true # Flag indicating whether or not In-Call Control feedback is enabled. + disallowInCallCtrl: false + # Flag indicating whether or not RID ACL In-Call Control feedback is enabled. # (This will enforce RID ACLs network wide, regardless of local peer RID ACL setting.) - enableInCallCtrl: false + enableRIDInCallCtrl: false # Flag indicating whether or not unknown/undefined RIDs will be rejected by the FNE. # (This is a strict rejection, any unknown or undefined RID not in the RID ACL list will be hard rejected.) rejectUnknownRID: false @@ -147,7 +187,7 @@ master: crypto_container: # Flag indicating whether or not crypto services are enabled. enable: false - # Full path to the talkgroup rules file. + # Full path to the KFDtool crypto container file. file: key_container.ekc # Container password. password: "PASSWORD" @@ -173,10 +213,11 @@ master: time: 30 # -# External Peers +# Upstream FNE Neighbor Peering +# (This is the list of connections to upstream FNEs this FNE instance should be connected to.) # peers: - - name: EXAMPLEPEER + - name: MASTERFNE # Flag indicating whether or not the peer is enabled. enable: true # Hostname/IP address of the FNE master to connect to. @@ -185,26 +226,9 @@ peers: masterPort: 32090 # FNE access password. password: RPT1234 - # Textual identity of this peer. - identity: EXPEER - # Network Peer ID + # Network Peer ID of this peer on the upstream FNE master. peerId: 9000990 - # List of peer IDs to block traffic to for this peer. - # The purpose of the blockTrafficTo peer ID list is to prevent traffic sourced from a listed peer ID from - # being resent/repeated to this peer. This usually *should* not needed to be configured, and is usually used - # on complex system configurations where traffic loops are possible due to duplicated or redundant peer - # connections. - # - # For example: If we have FNEs: A, B and C, where both B and C are connected to A, and B is also connected to - # C. On FNE B we would have blockTrafficTo entries for each external peer block listing the peer block peer ID's - # for external peer Cs ID on external peer A's entry, and external peer As ID on external peer Cs entry. - # - # Additionally, depending on configured talkgroup rules and other criteria, it may be necessary to also have - # FNE Bs peer ID on FNE Cs peer block entry for FNE A. - # - blockTrafficTo: [] - # Flag indicating whether or not peer endpoint networking is encrypted. encrypted: false # AES-256 32-byte Preshared Key @@ -212,10 +236,6 @@ peers: # 0 - 9, A - F.) presharedKey: "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" - # - rxFrequency: 0 - # - txFrequency: 0 # Latitude. latitude: 0.0 # Longitude. @@ -230,6 +250,9 @@ peers: # System Configuration # system: + # Textual identity of this FNE (this is used when peering with upstream FNEs). + identity: MASTERFNE + # Time in seconds between pings to peers. pingTime: 5 # Maximum number of missable pings before a peer is considered disconnected. diff --git a/configs/fne-sysview.example.yml b/configs/fne-sysview.example.yml index 8ccd14d5d..376059217 100644 --- a/configs/fne-sysview.example.yml +++ b/configs/fne-sysview.example.yml @@ -7,11 +7,10 @@ # # Logging Levels: # 1 - Debug -# 2 - Message -# 3 - Informational -# 4 - Warning -# 5 - Error -# 6 - Fatal +# 2 - Informational +# 3 - Warning +# 4 - Error +# 5 - Fatal # log: # Console display logging level (used when in foreground). diff --git a/configs/patch-config.example.yml b/configs/patch-config.example.yml index 39954a1ff..63c1952b4 100644 --- a/configs/patch-config.example.yml +++ b/configs/patch-config.example.yml @@ -10,11 +10,10 @@ daemon: true # # Logging Levels: # 1 - Debug -# 2 - Message -# 3 - Informational -# 4 - Warning -# 5 - Error -# 6 - Fatal +# 2 - Informational +# 3 - Warning +# 4 - Error +# 5 - Fatal # log: # Console display logging level (used when in foreground). diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index 60cf3299b..922ec695a 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -1,9 +1,32 @@ # -# This file sets the valid peer IDs allowed on a FNE. -# -# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled)" -#1234,,0,,1,0, -#5678,MYSECUREPASSWORD,0,,0,0, -#9876,MYSECUREPASSWORD,1,,0,0, -#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0, -#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0, +# Digital Voice Modem - Peer ID Access Control List +# +# This file sets the valid peer IDs allowed on a FNE. This file should always end with an empty line! +# +# * PEER ID [REQUIRED] - Unique ID for a peer. +# Peer IDs are valid numbers between 1 and 999999999. +# * PEER PASSWORD [REQUIRED] - Unique password for this peer to use when authenticating. +# * PEER REPLICATION [OPTIONAL] - Flag indicating whether or not the peer connection is another FNE and will receive +# full configuration from this FNE. When peer replication is set, and the connection is +# another FNE, that FNE will receive all the talkgroups, radio ID lists, and +# peer lists from this FNE, it will also receive all system traffic. +# * PEER ALIAS [OPTIONAL] - Textual name alias for the peer. +# * CAN REQUEST KEYS [OPTIONAL] - Flag indicating the peer connection is allowed to request encryption keys. +# If this flag is disabled (0), and the connected peer requests and encryption key +# the encryption key request will be dropped and ignored. +# * CAN ISSUE INHIBIT [OPTIONAL] - Flag indicating the peer connection is capable of transmitting inhibit packets. +# If this flag is disabled (0), and the connected peer issues an inhibit to the network +# this FNE will drop the packet and ignore it. +# * HAS CALL PRIORITY [OPTIONAL] - Flag indicating the peer connection has call priority. +# If this flag is disabled (0), and the connected peer tries to transmit over an on going +# call, normal call collision rules are applied to the traffic being transmitted. +# If this flag is enabled (1), and the connected peer tries to transmit over an on going +# call, call collision rules are ignored, and the peer is given priority. +# +# Entry Format: "Peer ID,Peer Password,Peer Replication (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled),Has Call Priority (1 = Enabled / 0 = Disabled)" +# Examples: +#1234,,0,,1,0,0, +#5678,MYSECUREPASSWORD,0,,0,0,0, +#9876,MYSECUREPASSWORD,1,,0,0,0, +#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,0, +#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,0, diff --git a/configs/rid_acl.example.dat b/configs/rid_acl.example.dat index 97198cda7..d4294133d 100644 --- a/configs/rid_acl.example.dat +++ b/configs/rid_acl.example.dat @@ -1,6 +1,13 @@ # -# This file sets the valid Radio IDs allowed on a repeater. +# Digital Voice Modem - Radio ID Access Control List # -# Entry Format: "RID,Enabled (1 = Enabled / 0 = Disabled),Optional Alias,Optional IP Address," +# This file sets the valid Radio IDs allowed on a repeater. This file should always end with an empty line! +# +# * RID [REQUIRED] - Unique Radio ID. +# * ENABLED [REQUIRED] - Flag indicating whether or not this radio ID entry is enabled and valid. +# * ALIAS [OPTIONAL] - Textual string representing an alias for this radio ID entry. +# * IP ADDRESS [OPTIONAL] - IP Address assigned to this radio ID. # +# Entry Format: "RID,Enabled (1 = Enabled / 0 = Disabled),Optional Alias,Optional IP Address," +# Example: #1234,1,RID Alias,IP Address, diff --git a/contrib/openssl_cross_patch.RPI_ARM.tar.gz b/contrib/openssl_cross_patch.RPI_ARM.tar.gz deleted file mode 100644 index 29c5aac51..000000000 Binary files a/contrib/openssl_cross_patch.RPI_ARM.tar.gz and /dev/null differ diff --git a/docs/TN.1000 - FNE Network.adoc b/docs/TN.1000 - FNE Network.adoc index f6552601e..106af41f6 100644 --- a/docs/TN.1000 - FNE Network.adoc +++ b/docs/TN.1000 - FNE Network.adoc @@ -31,6 +31,8 @@ This document describes, in high-level, the general concepts and procedures for * DMR: Digital Mobile Radio. (ETSI TS-102) * P25: Project 25. (TIA-102) * NXDN: Next Generation Digital Narrowband. +* ANALOG: Analog Audio. +* STP: Spanning Tree Protocol. === 2.2 General Concept The DVM FNE Network Protocol defines a common and standard communications protocol between the, CFNE server application, and DVM end-point applications. @@ -166,9 +168,13 @@ The function parameter of the extension header defines the major operation opcod |$91 |This function is used to announce status related to Group Affiliation, Unit Registration and Voice Channel registration. -|Peer-Link +|Peer Replication |$92 |This function is used from both Master -> Peer and Peer -> Master for linked CFNEs operating as a single network. + +|Network Tree +|$93 +|This function is used from both Master -> Peer and Peer -> Master for linked CFNEs operating as a single network to transfer spanning tree data. |=== ===== 2.3.1.2.2 Sub-Function @@ -193,6 +199,11 @@ The sub-function parameter of the extension header defines the minor operation o |Protocol |This sub-function is used for NXDN traffic. +|Analog +|$0F +|Protocol +|This sub-function is used for analog audio traffic. + |Whitelist RIDs |$00 |Master @@ -258,25 +269,40 @@ The sub-function parameter of the extension header defines the minor operation o |Announce |This sub-function is used for a peer to announce its list of registered voice channels to the master. -|Peer-Link Talkgroup Transfer +|Peer Replication Talkgroup Transfer |$00 -|Peer-Link -|This sub-function is used for a CFNE master to transfer the entire certified talkgroup rules configuration to a CFNE linked peer. +|Peer Replication +|This sub-function is used for a CFNE master to transfer the entire certified talkgroup rules configuration to a CFNE peer replica. -|Peer-Link Radio ID Transfer +|Peer Replication Radio ID Transfer |$01 -|Peer-Link -|This sub-function is used for a CFNE master to transfer the entire certified radio ID lookup configuration to a CFNE linked peer. +|Peer Replication +|This sub-function is used for a CFNE master to transfer the entire certified radio ID lookup configuration to a CFNE peer replica. -|Peer-Link Peer ID Transfer +|Peer Replication Peer ID Transfer |$02 -|Peer-Link -|This sub-function is used for a CFNE master to transfer the entire certified peer ID lookup configuration to a CFNE linked peer. +|Peer Replication +|This sub-function is used for a CFNE master to transfer the entire certified peer ID lookup configuration to a CFNE peer replica. -|Peer-Link Active Peer List Transfer +|Peer Replication Active Peer List Transfer |$A2 -|Peer-Link -|This sub-function is used for a CFNE linked peer to transfer the internal list of active peers to the CFNE master. +|Peer Replication +|This sub-function is used for a CFNE peer replica to transfer the internal list of active peers to the CFNE master. + +|Peer Replication HA Parameters +|$A3 +|Peer Replication +|This sub-function is used for a CFNE peer replica to transfer the configured HA parameters to the CFNE master. + +|Network Tree List +|$00 +|Spanning Tree +|This sub-function is used for a CFNE peer to transfer the network tree list to/from the CFNE master. + +|Network Tree Disconnect +|$01 +|Spanning Tree +|This sub-function is used for a CFNE master to command a disconnect of a duplicated CFNE connection. |=== === 2.3 NACK Types @@ -321,6 +347,10 @@ This is the basic description of the various packet NACKs that may occur. |FNE Max Connections |8 |General failure of the CFNE having reached its maximum allowable connected peers. + +|FNE Duplicate Connection +|9 +|Fatal failure where a downstream CFNE peer is already connected to the network. |=== === 2.4 TAG Types @@ -342,6 +372,10 @@ Some protocol commands (documented in the procedures below) utilize textual mark |NXDD |Marker for NXDN data packets. +|Analog Audio Data +|ANOD +|Marker for analog audio packets. + |Repeater/Peer Login |RPTL | @@ -358,10 +392,18 @@ Some protocol commands (documented in the procedures below) utilize textual mark |RPTP | -|Ping Keep-Alive Response +|Repeater Grant Request |RPTG | +|Repeater Key Request +|RKEY +| + +|In-Call Control Request +|ICC +| + |Transfer Message |TRNS | @@ -382,6 +424,10 @@ Some protocol commands (documented in the procedures below) utilize textual mark |ANNC | +|Replication +|REPL +| + |=== == 3. Procedures diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3e486a4b..2e7485724 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,9 +17,9 @@ add_library(common STATIC ${common_SRC} ${common_INCLUDE}) if (COMPILE_WIN32) target_link_libraries(common PRIVATE asio::asio Threads::Threads) else () - target_link_libraries(common PRIVATE asio::asio Threads::Threads util) + target_link_libraries(common PRIVATE ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads util) endif (COMPILE_WIN32) -target_include_directories(common PRIVATE src src/common) +target_include_directories(common PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/common) # ## vocoder @@ -39,16 +39,16 @@ endif (ENABLE_SETUP_TUI) add_executable(dvmhost ${common_INCLUDE} ${dvmhost_SRC}) if (ENABLE_SETUP_TUI) - target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads util) + target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads util) else () if (COMPILE_WIN32) target_sources(dvmhost PRIVATE ${dvmhost_RC}) - target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) + target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads) else () - target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads util) + target_link_libraries(dvmhost PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads util) endif (COMPILE_WIN32) endif (ENABLE_SETUP_TUI) -target_include_directories(dvmhost PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host) +target_include_directories(dvmhost PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host) set(CPACK_SET_DESTDIR true) set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local") @@ -61,7 +61,7 @@ set(CPACK_PACKAGE_VENDOR "DVMProject") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "The DVM Host software provides the host computer implementation of a mixed-mode DMR, P25 and/or NXDN or dedicated-mode DMR, P25 or NXDN repeater system that talks to the actual modem hardware. The host software; is the portion of a complete Over-The-Air modem implementation that performs the data processing, decision making and FEC correction for a digital repeater.") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DVMProject Authors") -set(CPACK_DEBIAN_PACKAGE_VERSION "R04Jxx") +set(CPACK_DEBIAN_PACKAGE_VERSION "R05A") set(CPACK_DEBIAN_PACKAGE_RELEASE "0") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dvmproject") @@ -80,8 +80,8 @@ add_executable(dvmfne ${common_INCLUDE} ${dvmfne_SRC}) if (COMPILE_WIN32) target_sources(dvmfne PRIVATE ${dvmfne_RC}) endif (COMPILE_WIN32) -target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) -target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) +target_link_libraries(dvmfne PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads) +target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/fne) # ## dvmmon @@ -89,8 +89,8 @@ target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/fne) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) include(src/monitor/CMakeLists.txt) add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC}) - target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) - target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/monitor) + target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads) + target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host src/monitor) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) # @@ -99,11 +99,11 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) include(src/sysview/CMakeLists.txt) add_executable(sysview ${common_INCLUDE} ${sysView_SRC}) - target_link_libraries(sysview PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) + target_link_libraries(sysview PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads) if (NOT DISABLE_WEBSOCKETS) - target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${websocketpp_SOURCE_DIR} src src/host src/sysview) + target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} ${websocketpp_SOURCE_DIR} src src/host src/sysview) else () - target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/sysview) + target_include_directories(sysview PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/host src/sysview) endif (NOT DISABLE_WEBSOCKETS) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) @@ -113,8 +113,8 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) include(src/tged/CMakeLists.txt) add_executable(tged ${common_INCLUDE} ${tged_SRC}) - target_link_libraries(tged PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) - target_include_directories(tged PRIVATE ${OPENSSL_INCLUDE_DIR} websocketpp src src/host src/tged) + target_link_libraries(tged PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads) + target_include_directories(tged PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} websocketpp src src/host src/tged) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) # @@ -123,8 +123,8 @@ endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) include(src/peered/CMakeLists.txt) add_executable(peered ${common_INCLUDE} ${peered_SRC}) - target_link_libraries(peered PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) - target_include_directories(peered PRIVATE ${OPENSSL_INCLUDE_DIR} websocketpp src src/host src/peered) + target_link_libraries(peered PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio finalcut Threads::Threads) + target_include_directories(peered PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} websocketpp src src/host src/peered) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS)) # @@ -135,8 +135,8 @@ add_executable(dvmcmd ${common_INCLUDE} ${dvmcmd_SRC}) if (COMPILE_WIN32) target_sources(dvmcmd PRIVATE ${dvmcmd_RC}) endif (COMPILE_WIN32) -target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) -target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} src src/remote) +target_link_libraries(dvmcmd PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads) +target_include_directories(dvmcmd PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/remote) # ## dvmbridge @@ -145,15 +145,15 @@ include(src/bridge/CMakeLists.txt) add_executable(dvmbridge ${common_INCLUDE} ${bridge_SRC}) if (COMPILE_WIN32) target_sources(dvmbridge PRIVATE ${bridge_RC}) - target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) + target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads) else () if (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") - target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} dl atomic asio::asio Threads::Threads) + target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl atomic asio::asio Threads::Threads) else () - target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) + target_link_libraries(dvmbridge PRIVATE common vocoder ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl asio::asio Threads::Threads) endif (ARCH STREQUAL "arm64" OR ARCH STREQUAL "armhf") endif (COMPILE_WIN32) -target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} src src/bridge) +target_include_directories(dvmbridge PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/bridge) # ## dvmpatch @@ -162,8 +162,8 @@ include(src/patch/CMakeLists.txt) add_executable(dvmpatch ${common_INCLUDE} ${patch_SRC}) if (COMPILE_WIN32) target_sources(dvmpatch PRIVATE ${patch_RC}) - target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} asio::asio Threads::Threads) + target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} asio::asio Threads::Threads) else () - target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} dl asio::asio Threads::Threads) + target_link_libraries(dvmpatch PRIVATE common ${OPENSSL_LIBRARIES} ${LIBDW_LIBRARY} dl asio::asio Threads::Threads) endif (COMPILE_WIN32) -target_include_directories(dvmpatch PRIVATE ${OPENSSL_INCLUDE_DIR} src src/patch) +target_include_directories(dvmpatch PRIVATE ${OPENSSL_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR} src src/patch) diff --git a/src/CompilerOptions.cmake b/src/CompilerOptions.cmake index 0c8eb7c41..20f9b60ef 100644 --- a/src/CompilerOptions.cmake +++ b/src/CompilerOptions.cmake @@ -48,6 +48,7 @@ option(DEBUG_RINGBUFFER "" off) option(DEBUG_HTTP_PAYLOAD "" off) option(DEBUG_TRELLIS "" off) option(DEBUG_COMPRESS "" off) +option(DEBUG_RTP_MUX "" off) if (DEBUG_DMR_PDU_DATA) message(CHECK_START "DMR PDU Data Debug") @@ -149,6 +150,10 @@ if (DEBUG_COMPRESS) message(CHECK_START "zlib Compression Debug") add_definitions(-DDEBUG_COMPRESS) endif (DEBUG_COMPRESS) +if (DEBUG_RTP_MUX) + message(CHECK_START "RTP Mux Debug") + add_definitions(-DDEBUG_RTP_MUX) +endif (DEBUG_RTP_MUX) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") find_package(Threads REQUIRED) @@ -179,6 +184,7 @@ endif (HAVE_SENDMMSG) # are we enabling SSL support? if (NOT COMPILE_WIN32) + message(STATUS "OpenSSL root dir: ${OPENSSL_ROOT_DIR}") find_package(OpenSSL REQUIRED) if (OpenSSL_FOUND) message(STATUS "OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") diff --git a/src/bridge/ActivityLog.cpp b/src/bridge/ActivityLog.cpp index 8e4e5715e..12c88b127 100644 --- a/src/bridge/ActivityLog.cpp +++ b/src/bridge/ActivityLog.cpp @@ -4,19 +4,13 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "ActivityLog.h" #include "common/network/BaseNetwork.h" #include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork() -#if defined(_WIN32) -#include "common/Clock.h" -#else -#include -#endif // defined(_WIN32) - #if defined(CATCH2_TEST_COMPILATION) #include #endif @@ -39,12 +33,12 @@ const uint32_t ACT_LOG_BUFFER_LEN = 501U; // Global Variables // --------------------------------------------------------------------------- -static std::string m_actFilePath; -static std::string m_actFileRoot; +static std::string g_actFilePath; +static std::string g_actFileRoot; -static FILE* m_actFpLog = nullptr; +static FILE* g_actFpLog = nullptr; -static struct tm m_actTm; +static struct tm g_actTm; // --------------------------------------------------------------------------- // Global Functions @@ -62,22 +56,22 @@ static bool ActivityLogOpen() 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) + if (tm->tm_mday == g_actTm.tm_mday && tm->tm_mon == g_actTm.tm_mon && tm->tm_year == g_actTm.tm_year) { + if (g_actFpLog != nullptr) return true; } else { - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_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; + g_actFpLog = ::fopen(filename, "a+t"); + g_actTm = *tm; - return m_actFpLog != nullptr; + return g_actFpLog != nullptr; } /* Initializes the activity log. */ @@ -87,8 +81,8 @@ bool ActivityLogInitialise(const std::string& filePath, const std::string& fileR #if defined(CATCH2_TEST_COMPILATION) return true; #endif - m_actFilePath = filePath; - m_actFileRoot = fileRoot; + g_actFilePath = filePath; + g_actFileRoot = fileRoot; return ::ActivityLogOpen(); } @@ -100,56 +94,34 @@ void ActivityLogFinalise() #if defined(CATCH2_TEST_COMPILATION) return; #endif - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } /* Writes a new entry to the activity log. */ -void ActivityLog(const char* msg, ...) +void log_internal::ActivityLogInternal(const std::string& log) { #if defined(CATCH2_TEST_COMPILATION) return; #endif - assert(msg != nullptr); - - char buffer[ACT_LOG_BUFFER_LEN]; - time_t now; - ::time(&now); - struct tm* tm = ::localtime(&now); - - struct timeval nowMillis; - ::gettimeofday(&nowMillis, NULL); - - ::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, nowMillis.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 (LogGetNetwork() != nullptr) { + network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork(); + network->writeActLog(log.c_str()); + } + + ::fprintf(g_actFpLog, "%s\n", log.c_str()); + ::fflush(g_actFpLog); if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { - ::fprintf(stdout, "%s" EOL, buffer); + ::fprintf(stdout, "%s" EOL, log.c_str()); ::fflush(stdout); } } diff --git a/src/bridge/ActivityLog.h b/src/bridge/ActivityLog.h index 736d14d13..9b341fa1f 100644 --- a/src/bridge/ActivityLog.h +++ b/src/bridge/ActivityLog.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -18,12 +18,28 @@ #include "Defines.h" +#if defined(_WIN32) +#include "common/Clock.h" +#else +#include +#endif // defined(_WIN32) + #include // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- +namespace log_internal +{ + /** + * @brief Writes a new entry to the diagnostics log. + * @param level Log level for entry. + * @param log Fully formatted log message. + */ + extern HOST_SW_API void ActivityLogInternal(const std::string& log); +} // namespace log_internal + /** * @brief Initializes the activity log. * @param filePath File path for the log file. @@ -34,12 +50,45 @@ extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const * @brief Finalizes the activity log. */ extern HOST_SW_API void ActivityLogFinalise(); + /** - * @brief Writes a new entry to the activity log. - * @param msg String format. + * @brief Writes a new entry to the diagnostics log. + * @param fmt String format. * - * This is a variable argument function. + * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. */ -extern HOST_SW_API void ActivityLog(const char* msg, ...); +template +HOST_SW_API void ActivityLog(const std::string& fmt, Args... args) +{ + using namespace log_internal; + + int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + + int prefixLen = 0; + char prefixBuf[256]; + + time_t now; + ::time(&now); + struct tm* tm = ::localtime(&now); + + struct timeval nowMillis; + ::gettimeofday(&nowMillis, NULL); + + prefixLen = ::sprintf(prefixBuf, "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, nowMillis.tv_usec / 1000U); + + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + + std::snprintf(buf.get(), size, fmt.c_str(), args ...); + + std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen); + std::string msg = std::string(buf.get(), buf.get() + size - 1); + + ActivityLogInternal(std::string(prefix + msg)); +} #endif // __ACTIVITY_LOG_H__ diff --git a/src/bridge/BridgeMain.cpp b/src/bridge/BridgeMain.cpp index 1bfdb2e9e..125357b7d 100644 --- a/src/bridge/BridgeMain.cpp +++ b/src/bridge/BridgeMain.cpp @@ -118,7 +118,7 @@ void usage(const char* message, const char* arg) " -o output audio device\n" #ifdef _WIN32 "\n" - " -wasapi use WASAPI on Windows\n" + " -winmm use WinMM audio on Windows\n" #endif "\n" " -c specifies the configuration file to use\n" @@ -204,10 +204,10 @@ int checkArgs(int argc, char* argv[]) p += 2; } #ifdef _WIN32 - else if (IS("-wasapi")) { + else if (IS("-winmm")) { // Windows - g_backends[0] = ma_backend_wasapi; - g_backends[1] = ma_backend_winmm; + g_backends[0] = ma_backend_winmm; + g_backends[1] = ma_backend_wasapi; g_backends[2] = ma_backend_null; } #endif @@ -246,8 +246,8 @@ int main(int argc, char** argv) #ifdef _WIN32 // Windows - g_backends[0] = ma_backend_winmm; - g_backends[1] = ma_backend_wasapi; + g_backends[0] = ma_backend_wasapi; + g_backends[1] = ma_backend_winmm; g_backends[2] = ma_backend_null; #else // Linux @@ -281,6 +281,8 @@ int main(int argc, char** argv) } } + log_stacktrace::SignalHandling sh(g_foreground); + ::signal(SIGINT, sigHandler); ::signal(SIGTERM, sigHandler); #if !defined(_WIN32) diff --git a/src/bridge/CtsCorController.cpp b/src/bridge/CtsCorController.cpp new file mode 100644 index 000000000..c00096a9c --- /dev/null +++ b/src/bridge/CtsCorController.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + */ +/** + * @file CtsCorController.cpp + * @ingroup bridge + */ + +#include "Defines.h" +#include "CtsCorController.h" + +#if !defined(_WIN32) +#include +#endif + +CtsCorController::CtsCorController(const std::string& port) + : m_port(port), m_isOpen(false), m_ownsFd(true) +#if defined(_WIN32) + , m_fd(INVALID_HANDLE_VALUE) +#else + , m_fd(-1) +#endif // defined(_WIN32) +{ +} + +CtsCorController::~CtsCorController() +{ + close(); +} + +bool CtsCorController::open(int reuseFd) +{ + if (m_isOpen) + return true; + +#if defined(_WIN32) + std::string deviceName = m_port; + if (deviceName.find("\\\\.\\") == std::string::npos) { + deviceName = "\\\\." + m_port; + } + + m_fd = ::CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (m_fd == INVALID_HANDLE_VALUE) { + ::LogError(LOG_HOST, "Cannot open CTS COR device - %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } + + DCB dcb; + if (::GetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + + dcb.BaudRate = 9600; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + dcb.StopBits = ONESTOPBIT; + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDsrSensitivity = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + + if (::SetCommState(m_fd, &dcb) == 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + return false; + } + +#else + // If reusing an existing file descriptor from RTS PTT, don't open a new one + if (reuseFd >= 0) { + m_fd = reuseFd; + m_ownsFd = false; // Only COR can close file descriptor + ::LogInfo(LOG_HOST, "CTS COR Controller reusing file descriptor from RTS PTT on %s", m_port.c_str()); + m_isOpen = true; + return true; + } + + m_ownsFd = true; // COR owns the file descriptor + + // Open port if not available + m_fd = ::open(m_port.c_str(), O_RDONLY | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + // Try rw if ro fails + m_fd = ::open(m_port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + ::LogError(LOG_HOST, "Cannot open CTS COR device - %s", m_port.c_str()); + return false; + } + } + + if (::isatty(m_fd) == 0) { + ::LogError(LOG_HOST, "%s is not a TTY device", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + + // Save current RTS state before configuring termios + int savedModemState = 0; + if (::ioctl(m_fd, TIOCMGET, &savedModemState) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + bool savedRtsState = (savedModemState & TIOCM_RTS) != 0; + + if (!setTermios()) { + ::close(m_fd); + m_fd = -1; + return false; + } + + // Restore RTS to its original state + int currentModemState = 0; + if (::ioctl(m_fd, TIOCMGET, ¤tModemState) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s after termios", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + bool currentRtsState = (currentModemState & TIOCM_RTS) != 0; + if (currentRtsState != savedRtsState) { + // Restore RTS to original state + if (savedRtsState) { + currentModemState |= TIOCM_RTS; + } else { + currentModemState &= ~TIOCM_RTS; + } + if (::ioctl(m_fd, TIOCMSET, ¤tModemState) < 0) { + ::LogError(LOG_HOST, "Cannot restore RTS state for %s", m_port.c_str()); + ::close(m_fd); + m_fd = -1; + return false; + } + ::LogDebug(LOG_HOST, "CTS COR: Restored RTS to %s on %s", savedRtsState ? "HIGH" : "LOW", m_port.c_str()); + } +#endif // defined(_WIN32) + + ::LogInfo(LOG_HOST, "CTS COR Controller opened on %s (RTS preserved)", m_port.c_str()); + m_isOpen = true; + return true; +} + +void CtsCorController::close() +{ + if (!m_isOpen) + return; + +#if defined(_WIN32) + if (m_fd != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_fd); + m_fd = INVALID_HANDLE_VALUE; + } +#else + // Only close the file descriptor if we opened it ourselves + // If we're reusing a descriptor from RTS PTT, don't close it + if (m_fd != -1 && m_ownsFd) { + ::close(m_fd); + m_fd = -1; + } else if (m_fd != -1 && !m_ownsFd) { + m_fd = -1; + } +#endif // defined(_WIN32) + + m_isOpen = false; + ::LogInfo(LOG_HOST, "CTS COR Controller closed"); +} + +bool CtsCorController::isCtsAsserted() +{ + if (!m_isOpen) + return false; + +#if defined(_WIN32) + DWORD modemStat = 0; + if (::GetCommModemStatus(m_fd, &modemStat) == 0) { + ::LogError(LOG_HOST, "Cannot read modem status for %s, err=%04lx", m_port.c_str(), ::GetLastError()); + return false; + } + return (modemStat & MS_CTS_ON) != 0; +#else + int modemState = 0; + if (::ioctl(m_fd, TIOCMGET, &modemState) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_port.c_str()); + return false; + } + return (modemState & TIOCM_CTS) != 0; +#endif // defined(_WIN32) +} + +bool CtsCorController::setTermios() +{ +#if !defined(_WIN32) + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_port.c_str()); + return false; + } + + termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); + termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); + termios.c_iflag &= ~(IXON | IXOFF | IXANY); + termios.c_oflag &= ~(OPOST); + // Important: Disable hardware flow control (CRTSCTS) to avoid affecting RTS + // We only want to read CTS, not control RTS + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= (CS8 | CLOCAL | CREAD); + termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); + termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_port.c_str()); + return false; + } +#endif // !defined(_WIN32) + + return true; +} + + diff --git a/src/bridge/CtsCorController.h b/src/bridge/CtsCorController.h new file mode 100644 index 000000000..58f53806d --- /dev/null +++ b/src/bridge/CtsCorController.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Bridge + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Lorenzo L. Romero, K2LLR + */ +/** + * @file CtsCorController.h + * @ingroup bridge + */ +#if !defined(__CTS_COR_CONTROLLER_H__) +#define __CTS_COR_CONTROLLER_H__ + +#include "Defines.h" +#include "common/Log.h" + +#include + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#include +#include +#endif // defined(_WIN32) + +/** + * @brief This class implements CTS-based COR detection for the bridge. + * @ingroup bridge + */ +class HOST_SW_API CtsCorController { +public: + /** + * @brief Initializes a new instance of the CtsCorController class. + * @param port Serial port device (e.g., /dev/ttyUSB0). + */ + CtsCorController(const std::string& port); + /** + * @brief Finalizes a instance of the CtsCorController class. + */ + ~CtsCorController(); + + /** + * @brief Opens the serial port for CTS readback. + * @param reuseFd Optional file descriptor to reuse (when sharing port with RTS PTT). + * @returns bool True, if port was opened successfully, otherwise false. + */ + bool open(int reuseFd = -1); + /** + * @brief Closes the serial port. + */ + void close(); + + /** + * @brief Reads the current CTS signal state. + * @returns bool True if CTS is asserted (active), otherwise false. + */ + bool isCtsAsserted(); + +private: + std::string m_port; + bool m_isOpen; + bool m_ownsFd; // true if we opened the fd, false if reusing from RTS PTT +#if defined(_WIN32) + HANDLE m_fd; +#else + int m_fd; +#endif // defined(_WIN32) + + /** + * @brief Sets the termios settings on the serial port. + * @returns bool True, if settings are set, otherwise false. + */ + bool setTermios(); +}; + +#endif // __CTS_COR_CONTROLLER_H__ + + diff --git a/src/bridge/Defines.h b/src/bridge/Defines.h index c7c383e1f..0785ffc88 100644 --- a/src/bridge/Defines.h +++ b/src/bridge/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/bridge/HostBridge.cpp b/src/bridge/HostBridge.cpp index 75b04c3fc..7c03e2c29 100644 --- a/src/bridge/HostBridge.cpp +++ b/src/bridge/HostBridge.cpp @@ -6,6 +6,7 @@ * * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2025 Caleb, K4PHP + * Copyright (C) 2025 Lorenzo L Romero, K2LLR * */ #include "Defines.h" @@ -63,6 +64,7 @@ const int NUMBER_OF_BUFFERS = 32; #define LOCAL_CALL "Local Traffic" #define UDP_CALL "UDP Traffic" +#define TEK_DES "des" #define TEK_AES "aes" #define TEK_ARC4 "arc4" @@ -70,8 +72,8 @@ const int NUMBER_OF_BUFFERS = 32; // Static Class Members // --------------------------------------------------------------------------- -std::mutex HostBridge::m_audioMutex; -std::mutex HostBridge::m_networkMutex; +std::mutex HostBridge::s_audioMutex; +std::mutex HostBridge::s_networkMutex; // --------------------------------------------------------------------------- // Global Functions @@ -89,7 +91,7 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 // capture input audio if (frameCount > 0U) { - std::lock_guard lock(HostBridge::m_audioMutex); + std::lock_guard lock(HostBridge::s_audioMutex); int smpIdx = 0; short samples[AUDIO_SAMPLES_LENGTH]; @@ -114,12 +116,9 @@ void audioCallback(ma_device* device, void* output, const void* input, ma_uint32 pcmIdx += 2; } - // Assert RTS PTT when audio is being sent to output + // Assert RTS PTT when audio is being sent to output and record last output time bridge->assertRtsPtt(); - } - else { - // Deassert RTS PTT when no audio is being sent to output - bridge->deassertRtsPtt(); + bridge->m_lastAudioOut = system_clock::hrc::now(); } } @@ -133,7 +132,7 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit return; if (op == OP_PTT_ID && bridge->m_overrideSrcIdFromMDC) { - ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, unitId = $%04X", unitID); + ::LogInfoEx(LOG_HOST, "Local Traffic, MDC Detect, unitId = $%04X", unitID); // HACK: nasty bullshit to convert MDC unitID to decimal char* pCharRes = new char[16]; // enough space for "0xFFFFFFFF" @@ -150,7 +149,7 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit delete[] pCharRes; bridge->m_srcIdOverride = res; - ::LogMessage(LOG_HOST, "Local Traffic, MDC Detect, converted srcId = %u", bridge->m_srcIdOverride); + ::LogInfoEx(LOG_HOST, "Local Traffic, MDC Detect, converted srcId = %u", bridge->m_srcIdOverride); } } @@ -175,10 +174,8 @@ HostBridge::HostBridge(const std::string& confFile) : m_udpUseULaw(false), m_udpRTPFrames(false), m_udpUsrp(false), - m_udpInterFrameDelay(0U), - m_udpJitter(200U), - m_udpSilenceDuringHang(true), - m_lastUdpFrameTime(0U), + m_udpFrameTiming(false), + m_udpFrameCnt(0U), m_tekAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekKeyId(0U), m_requestedTek(false), @@ -200,8 +197,6 @@ HostBridge::HostBridge(const std::string& confFile) : m_voxSampleLevel(30.0f), m_dropTimeMS(180U), m_localDropTime(1000U, 0U, 180U), - m_udpCallClock(1000U, 0U, 160U), - m_udpHangTime(1000U, 0U, 180U), m_udpDropTime(1000U, 0U, 180U), m_detectAnalogMDC1200(false), m_preambleLeaderTone(false), @@ -247,6 +242,7 @@ HostBridge::HostBridge(const std::string& confFile) : m_txStreamId(0U), m_detectedSampleCnt(0U), m_dumpSampleLevel(false), + m_mtNoSleep(false), m_running(false), m_trace(false), m_debug(false), @@ -254,6 +250,15 @@ HostBridge::HostBridge(const std::string& confFile) : m_rtsPttPort(), m_rtsPttController(nullptr), m_rtsPttActive(false), + m_lastAudioOut(), + m_rtsPttHoldoffMs(250U), + m_ctsCorEnable(false), + m_ctsCorPort(), + m_ctsCorController(nullptr), + m_ctsCorActive(false), + m_ctsCorInvert(false), + m_ctsPadTimeout(1000U, 0U, 22U), + m_ctsCorHoldoffMs(250U), m_rtpSeqNo(0U), m_rtpTimestamp(INVALID_TS), m_usrpSeqNo(0U) @@ -288,6 +293,10 @@ HostBridge::HostBridge(const std::string& confFile) : ::memset(m_netLDU2, 0x00U, 9U * 25U); m_p25Crypto = new p25::crypto::P25Crypto(); + + // initialize RTS PTT timing + m_lastAudioOut = system_clock::hrc::now(); + m_rtsPttHoldoffMs = 250U; } /* Finalizes a instance of the HostBridge class. */ @@ -412,6 +421,11 @@ int HostBridge::run() if (!ret) return EXIT_FAILURE; + // initialize CTS COR detection + ret = initializeCtsCor(); + if (!ret) + return EXIT_FAILURE; + ma_result result; if (m_localAudio) { // initialize audio devices @@ -515,15 +529,18 @@ int HostBridge::run() // set the In-Call Control function callback if (m_network != nullptr) { if (m_txMode == TX_MODE_DMR) { - m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) { processInCallCtrl(command, dstId, slotNo); }); + m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId, slotNo); }); } if (m_txMode == TX_MODE_P25) { - m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); + m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId, 0U); }); } if (m_txMode == TX_MODE_ANALOG) { - m_network->setAnalogICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId, 0U); }); + m_network->setAnalogICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId, 0U); }); } } @@ -539,7 +556,9 @@ int HostBridge::run() if (m_localAudio) { if (!Thread::runAsThread(this, threadAudioProcess)) return EXIT_FAILURE; + } + if (m_localAudio) { // start audio device result = ma_device_start(&m_maDevice); if (result != MA_SUCCESS) { @@ -592,14 +611,14 @@ int HostBridge::run() // ------------------------------------------------------ if (m_network != nullptr) { - std::lock_guard lock(HostBridge::m_networkMutex); + std::lock_guard lock(HostBridge::s_networkMutex); m_network->clock(ms); } if (m_udpAudio && m_udpAudioSocket != nullptr) processUDPAudio(); - if (ms < 2U) + if (ms < 2U && !m_mtNoSleep) Thread::sleep(1U); } @@ -910,16 +929,15 @@ bool HostBridge::readParams() yaml::Node networkConf = m_conf["network"]; m_udpAudio = networkConf["udpAudio"].as(false); - bool udpSilenceDuringHang = networkConf["udpHangSilence"].as(true); switch (m_txMode) { case TX_MODE_DMR: break; case TX_MODE_P25: { - if (m_udpAudio && udpSilenceDuringHang && m_dropTimeMS < 360U) { - ::LogWarning(LOG_HOST, "When using UDP silence during hang time, the minimum allowable drop time is 360ms."); - m_dropTimeMS = 360U; // drop time for UDP is minimum 360ms when using silence during hang time + if (m_udpAudio) { + ::LogWarning(LOG_HOST, "When using UDP audio, the drop time is fixed to 360ms. (1 P25 audio superframe.)"); + m_dropTimeMS = 360U; } } break; @@ -930,10 +948,6 @@ bool HostBridge::readParams() m_localDropTime = Timer(1000U, 0U, m_dropTimeMS); m_udpDropTime = Timer(1000U, 0U, m_dropTimeMS); - // bryanb: UDP drop timer cannot be less then 180ms - if (m_dropTimeMS > 180U) - m_udpDropTime = Timer(1000U, 0U, m_dropTimeMS); - m_detectAnalogMDC1200 = systemConf["detectAnalogMDC1200"].as(false); m_preambleLeaderTone = systemConf["preambleLeaderTone"].as(false); @@ -952,6 +966,21 @@ bool HostBridge::readParams() // RTS PTT Configuration m_rtsPttEnable = systemConf["rtsPttEnable"].as(false); m_rtsPttPort = systemConf["rtsPttPort"].as("/dev/ttyUSB0"); + m_rtsPttHoldoffMs = (uint32_t)systemConf["rtsPttHoldoffMs"].as(m_rtsPttHoldoffMs); + + // CTS COR Configuration + m_ctsCorEnable = systemConf["ctsCorEnable"].as(false); + m_ctsCorPort = systemConf["ctsCorPort"].as("/dev/ttyUSB0"); + m_ctsCorInvert = systemConf["ctsCorInvert"].as(false); + m_ctsCorHoldoffMs = (uint32_t)systemConf["ctsCorHoldoffMs"].as(m_ctsCorHoldoffMs); + + if (m_ctsCorEnable) { + LogInfo("CTS COR Configuration"); + LogInfo(" Enabled: yes"); + LogInfo(" Port: %s", m_ctsCorPort.c_str()); + LogInfo(" Invert: %s (%s triggers)", m_ctsCorInvert ? "yes" : "no", m_ctsCorInvert ? "LOW" : "HIGH"); + LogInfo(" Holdoff: %u ms", m_ctsCorHoldoffMs); + } std::string txModeStr = "DMR"; if (m_txMode == TX_MODE_P25) @@ -981,6 +1010,7 @@ bool HostBridge::readParams() LogInfo(" RTS PTT Enable: %s", m_rtsPttEnable ? "yes" : "no"); if (m_rtsPttEnable) { LogInfo(" RTS PTT Port: %s", m_rtsPttPort.c_str()); + LogInfo(" RTS PTT Hold-off: %ums", m_rtsPttHoldoffMs); } if (m_debug) { @@ -1012,9 +1042,7 @@ bool HostBridge::createNetwork() m_udpReceiveAddress = networkConf["udpReceiveAddress"].as(); m_udpUseULaw = networkConf["udpUseULaw"].as(false); m_udpUsrp = networkConf["udpUsrp"].as(false); - m_udpInterFrameDelay = (uint8_t)networkConf["udpInterFrameDelay"].as(0U); - m_udpJitter = (uint16_t)networkConf["udpJitter"].as(200U); - m_udpSilenceDuringHang = networkConf["udpHangSilence"].as(true); + m_udpFrameTiming = networkConf["udpFrameTiming"].as(false); if (m_udpUsrp) { m_udpMetadata = false; // USRP disables metadata due to USRP always having metadata @@ -1044,6 +1072,8 @@ bool HostBridge::createNetwork() m_tekAlgoId = P25DEF::ALGO_AES_256; else if (tekAlgo == TEK_ARC4) m_tekAlgoId = P25DEF::ALGO_ARC4; + else if (tekAlgo == TEK_DES) + m_tekAlgoId = P25DEF::ALGO_DES; else { ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); m_tekAlgoId = P25DEF::ALGO_UNENCRYPT; @@ -1176,9 +1206,7 @@ bool HostBridge::createNetwork() LogInfo(" UDP Audio RTP Framed: %s", m_udpRTPFrames ? "yes" : "no"); } LogInfo(" UDP Audio USRP: %s", m_udpUsrp ? "yes" : "no"); - LogInfo(" UDP Inter Audio Frame Delay: %ums", m_udpInterFrameDelay); - LogInfo(" UDP Jitter: %ums", m_udpJitter); - LogInfo(" UDP Silence During Hangtime: %s", m_udpSilenceDuringHang ? "yes" : "no"); + LogInfo(" UDP Frame Timing: %s", m_udpFrameTiming ? "yes" : "no"); } LogInfo(" Traffic Encrypted: %s", tekEnable ? "yes" : "no"); @@ -1240,6 +1268,13 @@ bool HostBridge::createNetwork() if (m_udpAudio) { m_udpAudioSocket = new Socket(m_udpReceiveAddress, m_udpReceivePort); m_udpAudioSocket->open(); + + /* + ** bryanb: resize the system UDP socket buffer used for receiving audio frames to 2M, this should hold + * ~6300 raw audio frames before filling + */ + if (!m_udpAudioSocket->recvBufSize(2097152U)) // 2M recv buffer + LogWarning(LOG_HOST, "failed to resize UDP audio socket buffer size to 2M"); } return true; @@ -1266,6 +1301,8 @@ void HostBridge::processUDPAudio() } if (length > 0) { + m_mtNoSleep = true; // make main thread run as fast as possible + if (m_trace) Utils::dump(1U, "HostBridge()::processUDPAudio(), Audio Network Packet", buffer, length); @@ -1321,40 +1358,19 @@ void HostBridge::processUDPAudio() req->pcmLength = pcmLength; - req->srcId = m_srcId; - req->dstId = m_dstId; - - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - if (m_udpInterFrameDelay > 0U) { - uint64_t pktTime = 0U; - - // if this is our first message, timestamp is just now + the jitter buffer offset in ms - if (m_lastUdpFrameTime == 0U) { - pktTime = now + m_udpJitter; - } - else { - // if the last message occurred longer than our jitter buffer delay, we restart the sequence and calculate the same as above - if ((int64_t)(now - m_lastUdpFrameTime) > m_udpJitter) { - pktTime = now + m_udpJitter; - } - // otherwise, we time out messages as required by the message type - else { - pktTime = m_lastUdpFrameTime + 20U; - } - } - - req->pktRxTime = pktTime; - m_lastUdpFrameTime = pktTime; - } else { - req->pktRxTime = now; - } - if (m_udpMetadata) { req->srcId = GET_UINT32(buffer, pcmLength + 8U); } + else { + req->srcId = m_srcId; + } + req->dstId = m_dstId; m_udpPackets.push_back(req); } + else { + m_mtNoSleep = false; // restore main thread sleeping if no pending packets + } } /* Helper to process an In-Call Control message. */ @@ -1471,7 +1487,7 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; - LogMessage(LOG_HOST, "DMR, call start, srcId = %u, dstId = %u, slot = %u", srcId, dstId, slotNo); + LogInfoEx(LOG_HOST, "DMR, call start, srcId = %u, dstId = %u, slot = %u", srcId, dstId, slotNo); if (m_preambleLeaderTone) generatePreambleTone(); @@ -1512,7 +1528,7 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; - LogMessage(LOG_HOST, "DMR, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "DMR, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_rxDMRLC = lc::LC(); @@ -1542,7 +1558,7 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) if (m_udpUsrp) sendUsrpEot(); - LogMessage(LOG_HOST, "P25, call end (T), srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "P25, call end (T), srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_ignoreCall = true; @@ -1556,7 +1572,7 @@ void HostBridge::processDMRNetwork(uint8_t* buffer, uint32_t length) ambe[13] |= (uint8_t)(data[19] & 0x0F); ::memcpy(ambe + 14U, data.get() + 20U, 13U); - LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u", slotNo, srcId, dstId, n); + LogInfoEx(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u", slotNo, srcId, dstId, n); decodeDMRAudioFrame(ambe, srcId, dstId, n); } @@ -1591,7 +1607,7 @@ void HostBridge::decodeDMRAudioFrame(uint8_t* ambe, uint32_t srcId, uint32_t dst #endif // defined(_WIN32) if (m_debug) - LogMessage(LOG_HOST, DMR_DT_VOICE ", Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", dmrN, n, srcId, dstId, errs); + LogInfoEx(LOG_HOST, DMR_DT_VOICE ", Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", dmrN, n, srcId, dstId, errs); // post-process: apply gain to decoded audio frames AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain); @@ -1764,7 +1780,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ dmrData.setData(data); - LogMessage(LOG_HOST, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X", m_slot, + LogInfoEx(LOG_HOST, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X", m_slot, dmrLC.getSrcId(), dmrLC.getDstId(), dmrData.getFLCO()); m_network->writeDMR(dmrData, false); @@ -1797,7 +1813,7 @@ void HostBridge::encodeDMRAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ emb.encode(data); } - LogMessage(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); + LogInfoEx(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); // generate DMR network frame NetData dmrData; @@ -1975,7 +1991,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; - LogMessage(LOG_HOST, "P25, call start, srcId = %u, dstId = %u, callAlgoId = $%02X, callKID = $%04X", srcId, dstId, m_callAlgoId, callKID); + LogInfoEx(LOG_HOST, "P25, call start, srcId = %u, dstId = %u, callAlgoId = $%02X, callKID = $%04X", srcId, dstId, m_callAlgoId, callKID); if (m_preambleLeaderTone) generatePreambleTone(); } @@ -1994,7 +2010,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) sendUsrpEot(); } - LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_rxP25LC = lc::LC(); @@ -2037,7 +2053,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; - LogMessage(LOG_HOST, "P25, call end (T), srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "P25, call end (T), srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_ignoreCall = true; @@ -2092,7 +2108,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) dfsiLC.decodeLDU1(data.get() + count, m_netLDU1 + 204U); count += DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; - LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); // decode 9 IMBE codewords into PCM samples decodeP25AudioFrame(m_netLDU1, srcId, dstId, 1U); @@ -2143,7 +2159,7 @@ void HostBridge::processP25Network(uint8_t* buffer, uint32_t length) dfsiLC.decodeLDU2(data.get() + count, m_netLDU2 + 204U); count += DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; - LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); + LogInfoEx(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); // decode 9 IMBE codewords into PCM samples decodeP25AudioFrame(m_netLDU2, srcId, dstId, 2U); @@ -2228,6 +2244,9 @@ void HostBridge::decodeP25AudioFrame(uint8_t* ldu, uint32_t srcId, uint32_t dstI case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25Crypto->cryptDES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", m_tekAlgoId); break; @@ -2413,6 +2432,9 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ case P25DEF::ALGO_ARC4: m_p25Crypto->cryptARC4_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25Crypto->cryptDES_IMBE(imbe, (m_p25N < 9U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "unsupported TEK algorithm, tekAlgoId = $%02X", m_tekAlgoId); break; @@ -2523,19 +2545,23 @@ void HostBridge::encodeP25AudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_ // send P25 LDU1 if (m_p25N == 8U) { - LogMessage(LOG_HOST, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_HOST, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::HDU_VALID, controlByte); m_txStreamId = m_network->getP25StreamId(); } // send P25 LDU2 if (m_p25N == 17U) { - LogMessage(LOG_HOST, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_tekAlgoId, m_tekKeyId); + LogInfoEx(LOG_HOST, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_tekAlgoId, m_tekKeyId); m_network->writeP25LDU2(lc, lsd, m_netLDU2, controlByte); } m_p25SeqNo++; m_p25N++; + + // if N is >17 reset sequence + if (m_p25N > 17) + m_p25N = 0; } /* Helper to process analog network traffic. */ @@ -2590,7 +2616,7 @@ void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; - LogMessage(LOG_HOST, "Analog, call start, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_HOST, "Analog, call start, srcId = %u, dstId = %u", srcId, dstId); if (m_preambleLeaderTone) generatePreambleTone(); } @@ -2604,7 +2630,7 @@ void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; - LogMessage(LOG_HOST, "Analog, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "Analog, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_rxStartTime = 0U; @@ -2622,7 +2648,7 @@ void HostBridge::processAnalogNetwork(uint8_t* buffer, uint32_t length) return; if (frameType == AudioFrameType::VOICE_START || frameType == AudioFrameType::VOICE) { - LogMessage(LOG_NET, ANO_VOICE ", audio, srcId = %u, dstId = %u, seqNo = %u", srcId, dstId, analogData.getSeqNo()); + LogInfoEx(LOG_NET, ANO_VOICE ", audio, srcId = %u, dstId = %u, seqNo = %u", srcId, dstId, analogData.getSeqNo()); short samples[AUDIO_SAMPLES_LENGTH]; int smpIdx = 0; @@ -2824,7 +2850,7 @@ void HostBridge::sendUsrpEot() void HostBridge::generatePreambleTone() { - std::lock_guard lock(m_audioMutex); + std::lock_guard lock(s_audioMutex); uint64_t frameCount = AnalogAudio::toSamples(SAMPLE_RATE, 1, m_preambleLength); if (frameCount > m_outputAudio.freeSpace()) { @@ -2903,18 +2929,16 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) return; } - LogMessage(LOG_HOST, "%s, call end, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId); - m_audioDetect = false; m_localDropTime.stop(); m_udpDropTime.stop(); - m_udpHangTime.stop(); - m_udpCallClock.stop(); if (!m_callInProgress) { switch (m_txMode) { case TX_MODE_DMR: { + padSilenceAudio(srcId, dstId); + DMRDEF::DataType::E dataType = DMRDEF::DataType::VOICE_SYNC; if (m_dmrN == 0) dataType = DMRDEF::DataType::VOICE_SYNC; @@ -2933,7 +2957,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) data.setBER(0U); data.setRSSI(0U); - LogMessage(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); + LogInfoEx(LOG_HOST, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot, dstId); m_network->writeDMRTerminator(data, &m_dmrSeqNo, &m_dmrN, m_dmrEmbeddedData); m_network->resetDMR(data.getSlotNo()); @@ -2941,6 +2965,8 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) break; case TX_MODE_P25: { + padSilenceAudio(srcId, dstId); + p25::lc::LC lc = p25::lc::LC(); lc.setLCO(P25DEF::LCO::GROUP); lc.setDstId(dstId); @@ -2948,7 +2974,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, P25_TDU_STR); + LogInfoEx(LOG_HOST, P25_TDU_STR); uint8_t controlByte = 0x00U; m_network->writeP25TDU(lc, lsd, controlByte); @@ -2957,7 +2983,7 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) break; case TX_MODE_ANALOG: { - LogMessage(LOG_HOST, ANO_TERMINATOR); + LogInfoEx(LOG_HOST, ANO_TERMINATOR); uint8_t controlByte = 0x00U; @@ -2978,13 +3004,20 @@ void HostBridge::callEnd(uint32_t srcId, uint32_t dstId) } } + LogInfoEx(LOG_HOST, "%s, call end, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId); + m_srcIdOverride = 0; m_txStreamId = 0; m_udpSrcId = 0; m_udpDstId = 0; - m_lastUdpFrameTime = 0U; m_trafficFromUDP = false; + m_udpFrameCnt = 0U; + + // ensure PTT is dropped at call end + if (m_rtsPttEnable) { + deassertRtsPtt(); + } m_dmrSeqNo = 0U; m_dmrN = 0U; @@ -3007,7 +3040,7 @@ void HostBridge::processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_ return; if (algId == m_tekAlgoId && ki->kId() == m_tekKeyId) { - LogMessage(LOG_HOST, "TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + LogInfoEx(LOG_HOST, "TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); UInt8Array tek = std::make_unique(keyLength); ki->getKey(tek.get()); @@ -3046,7 +3079,7 @@ void* HostBridge::threadAudioProcess(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -3059,9 +3092,25 @@ void* HostBridge::threadAudioProcess(void* arg) // scope is intentional { - std::lock_guard lock(m_audioMutex); + std::lock_guard lock(s_audioMutex); - if (bridge->m_inputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH) { + // When COR is active, we need to send frames continuously when audio data is available + // The audio callback should be continuously feeding data, so we should always have data available + bool hasAudioData = bridge->m_inputAudio.dataSize() >= AUDIO_SAMPLES_LENGTH; + + // Process if we have audio data + // When COR is active: process whenever we have data (which should be continuous) + // When COR is not active: only process when VOX detects audio (normal mode) + bool shouldProcess = false; + if (bridge->m_ctsCorActive && bridge->m_audioDetect) { + // COR is active: process whenever we have audio data (continuous transmission) + shouldProcess = hasAudioData; + } else if (!bridge->m_ctsCorActive && bridge->m_audioDetect) { + // Normal VOX mode: process when we have audio data + shouldProcess = hasAudioData; + } + + if (shouldProcess && hasAudioData) { short samples[AUDIO_SAMPLES_LENGTH]; bridge->m_inputAudio.get(samples, AUDIO_SAMPLES_LENGTH); @@ -3100,78 +3149,239 @@ void* HostBridge::threadAudioProcess(void* arg) bridge->m_detectedSampleCnt++; } - // handle Rx triggered by internal VOX - if (maxSample > sampleLevel) { - bridge->m_audioDetect = true; - if (bridge->m_txStreamId == 0U) { - bridge->m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this... - LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId); - - if (bridge->m_grantDemand) { - switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(P25DEF::LCO::GROUP); - lc.setDstId(dstId); - lc.setSrcId(srcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; - if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) - controlByte |= network::NET_CTRL_GRANT_ENCRYPT; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); + // handle Rx triggered by internal VOX (unless COR is active, which takes precedence) + if (!bridge->m_ctsCorActive) { + if (maxSample > sampleLevel) { + bridge->m_audioDetect = true; + if (bridge->m_txStreamId == 0U) { + bridge->m_txStreamId = 1U; + LogInfoEx(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", trafficType.c_str(), srcId, dstId); + + if (bridge->m_grantDemand) { + switch (bridge->m_txMode) { + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; } - break; } } - } - bridge->m_localDropTime.stop(); - } else { - // if we've exceeded the audio drop timeout, then really drop the audio - if (bridge->m_localDropTime.isRunning() && bridge->m_localDropTime.hasExpired()) { - if (bridge->m_audioDetect) { - bridge->callEnd(srcId, dstId); + bridge->m_localDropTime.stop(); + } else { + // if we've exceeded the audio drop timeout, then really drop the audio + if (bridge->m_localDropTime.isRunning() && bridge->m_localDropTime.hasExpired()) { + if (bridge->m_audioDetect) { + bridge->callEnd(srcId, dstId); + } } - } - if (!bridge->m_localDropTime.isRunning()) - bridge->m_localDropTime.start(); + if (!bridge->m_localDropTime.isRunning()) + bridge->m_localDropTime.start(); + } } + // Send audio frames: either from actual audio input OR silence frames when COR is active if (bridge->m_audioDetect && !bridge->m_callInProgress) { - ma_uint32 pcmBytes = AUDIO_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); - DECLARE_UINT8_ARRAY(pcm, pcmBytes); + // If COR is active, always send the actual audio from the buffer (even if quiet) + // If COR is not active, only send when VOX detects audio + if (bridge->m_ctsCorActive) { + // COR is active: always encode actual audio from buffer + // The buffer should always have data from audio callback, even if it's quiet + ma_uint32 pcmBytes = AUDIO_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); + DECLARE_UINT8_ARRAY(pcm, pcmBytes); + + // Always encode the actual samples, even if they're quiet + // This ensures real audio is passed through, not just silence + int pcmIdx = 0; + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } - int pcmIdx = 0; - for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { - pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); - pcmIdx += 2; - } + switch (bridge->m_txMode) + { + case TX_MODE_DMR: + bridge->encodeDMRAudioFrame(pcm); + break; + case TX_MODE_P25: + bridge->encodeP25AudioFrame(pcm); + break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(pcm); + break; + } + } else { + // COR is not active: normal VOX-based audio processing + if (maxSample > sampleLevel) { + ma_uint32 pcmBytes = AUDIO_SAMPLES_LENGTH * ma_get_bytes_per_frame(bridge->m_maDevice.capture.format, bridge->m_maDevice.capture.channels); + DECLARE_UINT8_ARRAY(pcm, pcmBytes); + + int pcmIdx = 0; + for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcmIdx += 2; + } - switch (bridge->m_txMode) - { - case TX_MODE_DMR: - bridge->encodeDMRAudioFrame(pcm); - break; - case TX_MODE_P25: - bridge->encodeP25AudioFrame(pcm); - break; - case TX_MODE_ANALOG: - bridge->encodeAnalogAudioFrame(pcm); - break; + switch (bridge->m_txMode) + { + case TX_MODE_DMR: + bridge->encodeDMRAudioFrame(pcm); + break; + case TX_MODE_P25: + bridge->encodeP25AudioFrame(pcm); + break; + case TX_MODE_ANALOG: + bridge->encodeAnalogAudioFrame(pcm); + break; + } + } } } } } + // When COR is active, we need to process frames continuously + // The audio callback should be feeding data, but if buffer is empty and COR is active, + // we'll send silence frames. Keep minimal sleep to ensure responsive processing. Thread::sleep(1U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + +/* Entry point to CTS COR monitor thread. */ + +void* HostBridge::threadCtsCorMonitor(void* arg) +{ + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("bridge:cts-cor-monitor"); + HostBridge* bridge = static_cast(th->obj); + if (bridge == nullptr) { + g_killed = true; + LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); + } + + if (g_killed) { + delete th; + return nullptr; + } + + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + // Initialize lastCts to current state to avoid false trigger on startup + bool lastCts = false; + if (bridge->m_ctsCorEnable && bridge->m_ctsCorController != nullptr) { + bool ctsRawInit = bridge->m_ctsCorController->isCtsAsserted(); + lastCts = bridge->m_ctsCorInvert ? !ctsRawInit : ctsRawInit; + bridge->m_ctsCorActive = lastCts; + LogInfoEx(LOG_HOST, "CTS COR monitor initialized: initial state = %s (raw: %s)", + lastCts ? "TRIGGER" : "IDLE", ctsRawInit ? "HIGH" : "LOW"); + } + uint32_t pollCount = 0U; + + while (!g_killed) { + if (!bridge->m_running) { + Thread::sleep(10U); + continue; + } + + if (!bridge->m_ctsCorEnable) { + LogDebug(LOG_HOST, "CTS COR is disabled, waiting..."); + Thread::sleep(1000U); + continue; + } + + if (bridge->m_ctsCorController == nullptr) { + LogError(LOG_HOST, "CTS COR Controller is null!"); + Thread::sleep(1000U); + continue; + } + + bool ctsRaw = bridge->m_ctsCorController->isCtsAsserted(); + // Apply inversion: if invert is true, LOW triggers (so we invert the raw signal) + bool cts = bridge->m_ctsCorInvert ? !ctsRaw : ctsRaw; + pollCount++; + + if (cts != lastCts) { + LogInfoEx(LOG_HOST, "CTS COR state changed: %s -> %s (raw: %s)", + lastCts ? "TRIGGER" : "IDLE", cts ? "TRIGGER" : "IDLE", ctsRaw ? "HIGH" : "LOW"); + lastCts = cts; + bridge->m_ctsCorActive = cts; + if (cts) { + // Rising edge: force call start, immediately send silence frame, and start padding timer + uint32_t srcId = bridge->m_srcId; + uint32_t dstId = bridge->m_dstId; + if (!bridge->m_audioDetect) { + bridge->m_audioDetect = true; + if (bridge->m_txStreamId == 0U) { + bridge->m_txStreamId = 1U; + LogInfoEx(LOG_HOST, "%s, call start (CTS COR), srcId = %u, dstId = %u", LOCAL_CALL, srcId, dstId); + if (bridge->m_grantDemand) { + switch (bridge->m_txMode) { + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(dstId); + lc.setSrcId(srcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; + } + } + } + } + // Stop drop timer while COR is activem audio is processing + bridge->m_localDropTime.stop(); + // Don't start padding timer + } + else { + // Falling edge: start hold-off timer before allowing call to end + bridge->m_ctsPadTimeout.stop(); + // Start drop timer with hold-off delay + bridge->m_localDropTime = Timer(1000U, 0U, bridge->m_ctsCorHoldoffMs); + bridge->m_localDropTime.start(); + } + } + + Thread::sleep(5U); + } + + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -3202,43 +3412,74 @@ void* HostBridge::threadUDPAudioProcess(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE + StopWatch stopWatch; + stopWatch.start(); + + ulong64_t lastFrameTime = 0U; + Timer frameTimeout = Timer(1000U, 0U, 22U); + while (!g_killed) { if (!bridge->m_running) { Thread::sleep(1U); continue; } + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + frameTimeout.clock(ms); + if (frameTimeout.isRunning() && frameTimeout.hasExpired()) { + frameTimeout.stop(); + bridge->padSilenceAudio(bridge->m_udpSrcId, bridge->m_udpDstId); + } + if (bridge->m_udpPackets.empty()) Thread::sleep(1U); else { NetPacketRequest* req = bridge->m_udpPackets[0]; - if (req != nullptr) { - if (bridge->m_udpInterFrameDelay > 0U) { - uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // are we timing UDP audio frame release? + if (bridge->m_udpFrameTiming) { + if (lastFrameTime == 0U) + lastFrameTime = now; + else { + // IMBEs must go out at 20ms intervals + if (lastFrameTime + 20U > now) + continue; - if (req->pktRxTime > now) { - Thread::sleep(1U); - continue; + lastFrameTime = now; } } - bridge->m_udpPackets.pop_front(); + if (bridge->m_debug) + LogDebugEx(LOG_HOST, "HostBridge::threadUDPAudioProcess()", "now = %llu, lastUdpFrameTime = %llu, audioDetect = %u, callInProgress = %u, p25N = %u, dmrN = %u, analogN = %u, frameCnt = %u", + now, lastFrameTime, bridge->m_audioDetect, bridge->m_callInProgress, bridge->m_p25N, bridge->m_dmrN, bridge->m_analogN, bridge->m_udpFrameCnt); - bridge->m_udpDstId = bridge->m_dstId; + bridge->m_udpPackets.pop_front(); + bridge->m_udpDropTime.start(); + frameTimeout.start(); + bool forceCallStart = false; + uint32_t txStreamId = bridge->m_txStreamId; if (bridge->m_udpMetadata) { if (bridge->m_overrideSrcIdFromUDP) { - if (req->srcId != 0U) { + if (req->srcId != 0U && bridge->m_udpSrcId != 0U) { // if the UDP source ID now doesn't match the current call ID, reset call states if (bridge->m_resetCallForSourceIdChange && (req->srcId != bridge->m_udpSrcId)) { + LogInfoEx(LOG_HOST, "%s, call switch over, old srcId = %u, new srcId = %u", UDP_CALL, bridge->m_udpSrcId, req->srcId); bridge->callEnd(bridge->m_udpSrcId, bridge->m_dstId); - bridge->m_udpDstId = bridge->m_dstId; + + if (bridge->m_udpDropTime.isRunning()) + bridge->m_udpDropTime.start(); + + forceCallStart = true; } bridge->m_udpSrcId = req->srcId; @@ -3247,6 +3488,10 @@ void* HostBridge::threadUDPAudioProcess(void* arg) if (bridge->m_udpSrcId == 0U) { bridge->m_udpSrcId = req->srcId; } + + if (bridge->m_udpSrcId == 0U) { + bridge->m_udpSrcId = bridge->m_srcId; + } } } else { @@ -3257,106 +3502,101 @@ void* HostBridge::threadUDPAudioProcess(void* arg) bridge->m_udpSrcId = bridge->m_srcId; } - std::lock_guard lock(m_audioMutex); + bridge->m_udpDstId = bridge->m_dstId; + + // force start a call if one isn't already in progress + if ((!bridge->m_audioDetect && !bridge->m_callInProgress) || forceCallStart) { + bridge->m_audioDetect = true; + if (bridge->m_txStreamId == 0U) { + bridge->m_txStreamId = 1U; + if (forceCallStart) + bridge->m_txStreamId = txStreamId; + + LogInfoEx(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, bridge->m_udpSrcId, bridge->m_udpDstId); + if (bridge->m_grantDemand) { + switch (bridge->m_txMode) { + case TX_MODE_P25: + { + p25::lc::LC lc = p25::lc::LC(); + lc.setLCO(P25DEF::LCO::GROUP); + lc.setDstId(bridge->m_udpDstId); + lc.setSrcId(bridge->m_udpSrcId); + + p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); + + uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; + if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) + controlByte |= network::NET_CTRL_GRANT_ENCRYPT; + controlByte |= network::NET_CTRL_SWITCH_OVER; + bridge->m_network->writeP25TDU(lc, lsd, controlByte); + } + break; + } + } + } + + bridge->m_udpDropTime.stop(); + if (!bridge->m_udpDropTime.isRunning()) + bridge->m_udpDropTime.start(); + } + + std::lock_guard lock(s_audioMutex); + uint8_t pcm[AUDIO_SAMPLES_LENGTH_BYTES]; + ::memset(pcm, 0x00U, AUDIO_SAMPLES_LENGTH_BYTES); + ::memcpy(pcm, req->pcm, AUDIO_SAMPLES_LENGTH_BYTES); - int smpIdx = 0; - short samples[AUDIO_SAMPLES_LENGTH]; if (bridge->m_udpUseULaw) { if (bridge->m_trace) - Utils::dump(1U, "HostBridge()::threadUDPAudioProcess(), uLaw Audio", req->pcm, AUDIO_SAMPLES_LENGTH * 2U); + Utils::dump(1U, "HostBridge()::threadUDPAudioProcess(), uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH * 2U); + int smpIdx = 0; + short samples[AUDIO_SAMPLES_LENGTH]; for (uint32_t pcmIdx = 0; pcmIdx < AUDIO_SAMPLES_LENGTH; pcmIdx++) { - samples[smpIdx] = AnalogAudio::decodeMuLaw(req->pcm[pcmIdx]); + samples[smpIdx] = AnalogAudio::decodeMuLaw(pcm[pcmIdx]); smpIdx++; } int pcmIdx = 0; for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) { - req->pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); - req->pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); + pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF); + pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF); pcmIdx += 2; } } - else { - for (int pcmIdx = 0; pcmIdx < req->pcmLength; pcmIdx += 2) { - samples[smpIdx] = (short)((req->pcm[pcmIdx + 1] << 8) + req->pcm[pcmIdx + 0]); - smpIdx++; - } - } bridge->m_trafficFromUDP = true; - // force start a call if one isn't already in progress - if (!bridge->m_audioDetect && !bridge->m_callInProgress) { - bridge->m_audioDetect = true; - if (bridge->m_txStreamId == 0U) { - bridge->m_txStreamId = 1U; // prevent further false starts -- this isn't the right way to handle this... - LogMessage(LOG_HOST, "%s, call start, srcId = %u, dstId = %u", UDP_CALL, bridge->m_udpSrcId, bridge->m_udpDstId); - if (bridge->m_grantDemand) { - switch (bridge->m_txMode) { - case TX_MODE_P25: - { - p25::lc::LC lc = p25::lc::LC(); - lc.setLCO(P25DEF::LCO::GROUP); - lc.setDstId(bridge->m_udpDstId); - lc.setSrcId(bridge->m_udpSrcId); - - p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - - uint8_t controlByte = network::NET_CTRL_GRANT_DEMAND; - if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT) - controlByte |= network::NET_CTRL_GRANT_ENCRYPT; - controlByte |= network::NET_CTRL_SWITCH_OVER; - bridge->m_network->writeP25TDU(lc, lsd, controlByte); - } - break; - } - } - } - - bridge->m_udpCallClock.stop(); - bridge->m_udpDropTime.stop(); - - if (!bridge->m_udpDropTime.isRunning()) - bridge->m_udpDropTime.start(); - bridge->m_udpCallClock.start(); - } - - // If audio detection is active and no call is in progress, encode and transmit the audio + // if audio detection is active and no call is in progress, encode and transmit the audio if (bridge->m_audioDetect && !bridge->m_callInProgress) { bridge->m_udpDropTime.start(); - bridge->m_udpCallClock.start(); switch (bridge->m_txMode) { case TX_MODE_DMR: - bridge->encodeDMRAudioFrame(req->pcm, bridge->m_udpSrcId); + bridge->encodeDMRAudioFrame(pcm, bridge->m_udpSrcId); break; case TX_MODE_P25: - bridge->encodeP25AudioFrame(req->pcm, bridge->m_udpSrcId); + bridge->encodeP25AudioFrame(pcm, bridge->m_udpSrcId); break; case TX_MODE_ANALOG: - bridge->encodeAnalogAudioFrame(req->pcm, bridge->m_udpSrcId); + bridge->encodeAnalogAudioFrame(pcm, bridge->m_udpSrcId); break; } } + bridge->m_udpFrameCnt++; + delete[] req->pcm; delete req; - - if (bridge->m_udpInterFrameDelay > 0U) { - Thread::sleep(bridge->m_udpInterFrameDelay); - } - else { - Thread::sleep(1U); - } } else { bridge->m_udpPackets.pop_front(); - Thread::sleep(1U); } + + if (!bridge->m_callInProgress) + Thread::sleep(1U); } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -3387,7 +3627,7 @@ void* HostBridge::threadNetworkProcess(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -3402,7 +3642,7 @@ void* HostBridge::threadNetworkProcess(void* arg) if (bridge->m_tekAlgoId != P25DEF::ALGO_UNENCRYPT && bridge->m_tekKeyId > 0U) { if (bridge->m_p25Crypto->getTEKLength() == 0U && !bridge->m_requestedTek) { bridge->m_requestedTek = true; - LogMessage(LOG_HOST, "Bridge encryption enabled, requesting TEK from network."); + LogInfoEx(LOG_HOST, "Bridge encryption enabled, requesting TEK from network."); bridge->m_network->writeKeyReq(bridge->m_tekKeyId, bridge->m_tekAlgoId); } } @@ -3411,7 +3651,7 @@ void* HostBridge::threadNetworkProcess(void* arg) uint32_t length = 0U; bool netReadRet = false; if (bridge->m_txMode == TX_MODE_DMR) { - std::lock_guard lock(HostBridge::m_networkMutex); + std::lock_guard lock(HostBridge::s_networkMutex); UInt8Array dmrBuffer = bridge->m_network->readDMR(netReadRet, length); if (netReadRet) { bridge->processDMRNetwork(dmrBuffer.get(), length); @@ -3419,7 +3659,7 @@ void* HostBridge::threadNetworkProcess(void* arg) } if (bridge->m_txMode == TX_MODE_P25) { - std::lock_guard lock(HostBridge::m_networkMutex); + std::lock_guard lock(HostBridge::s_networkMutex); UInt8Array p25Buffer = bridge->m_network->readP25(netReadRet, length); if (netReadRet) { bridge->processP25Network(p25Buffer.get(), length); @@ -3427,7 +3667,7 @@ void* HostBridge::threadNetworkProcess(void* arg) } if (bridge->m_txMode == TX_MODE_ANALOG) { - std::lock_guard lock(HostBridge::m_networkMutex); + std::lock_guard lock(HostBridge::s_networkMutex); UInt8Array analogBuffer = bridge->m_network->readAnalog(netReadRet, length); if (netReadRet) { bridge->processAnalogNetwork(analogBuffer.get(), length); @@ -3437,7 +3677,7 @@ void* HostBridge::threadNetworkProcess(void* arg) Thread::sleep(1U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -3485,7 +3725,7 @@ void HostBridge::resetWithNullAudio(uint8_t* data, bool encrypted) /* */ -void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) +void HostBridge::padSilenceAudio(uint32_t srcId, uint32_t dstId) { switch (m_txMode) { case TX_MODE_DMR: @@ -3528,7 +3768,7 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) emb.encode(data); } - LogMessage(LOG_HOST, DMR_DT_VOICE ", silence srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); + LogInfoEx(LOG_HOST, DMR_DT_VOICE ", audio (silence), srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_slot, m_dmrN); // generate DMR network frame NetData dmrData; @@ -3557,6 +3797,99 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) using namespace p25::data; // fill the LDU buffers appropriately + if (m_p25N > 0U) { + // LDU1 + if (m_p25N >= 0U && m_p25N < 9U) { + LogWarning(LOG_HOST, "incomplete audio frame, padding %u audio sequences with silence", 8U - m_p25N); + + for (uint8_t n = m_p25N; n < 9U; n++) { + switch (n) { + case 0: + ::memcpy(m_netLDU1 + 10U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 1: + ::memcpy(m_netLDU1 + 26U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 2: + ::memcpy(m_netLDU1 + 55U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 3: + ::memcpy(m_netLDU1 + 80U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 4: + ::memcpy(m_netLDU1 + 105U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 5: + ::memcpy(m_netLDU1 + 130U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 6: + ::memcpy(m_netLDU1 + 155U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 7: + ::memcpy(m_netLDU1 + 180U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 8: + ::memcpy(m_netLDU1 + 204U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + } + } + + m_p25N = 8U; + } + + // LDU2 + if (m_p25N >= 9U && m_p25N < 17U) { + LogWarning(LOG_HOST, "incomplete audio frame, padding %u audio sequences with silence", 17U - m_p25N); + + for (uint8_t n = m_p25N; n < 18U; n++) { + switch (n) { + case 9: + ::memcpy(m_netLDU2 + 10U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 10: + ::memcpy(m_netLDU2 + 26U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 11: + ::memcpy(m_netLDU2 + 55U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 12: + ::memcpy(m_netLDU2 + 80U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 13: + ::memcpy(m_netLDU2 + 105U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 14: + ::memcpy(m_netLDU2 + 130U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 15: + ::memcpy(m_netLDU2 + 155U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 16: + ::memcpy(m_netLDU2 + 180U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + case 17: + ::memcpy(m_netLDU2 + 204U, P25DEF::NULL_IMBE, RAW_IMBE_LENGTH_BYTES); + break; + } + } + + m_p25N = 17U; + } + } + else { + // LDU1 + if (m_p25N >= 0U && m_p25N < 9U) { + resetWithNullAudio(m_netLDU1, false); + m_p25N = 8U; + } + + // LDU2 + if (m_p25N >= 9U && m_p25N < 17U) { + resetWithNullAudio(m_netLDU2, false); + m_p25N = 17U; + } + } + switch (m_p25N) { // LDU1 case 0: @@ -3582,16 +3915,16 @@ void HostBridge::callEndSilence(uint32_t srcId, uint32_t dstId) LowSpeedData lsd = LowSpeedData(); // send P25 LDU1 - if (m_p25N == 0U) { - LogMessage(LOG_HOST, P25_LDU1_STR " silence audio, srcId = %u, dstId = %u", srcId, dstId); + if (m_p25N == 8U) { + LogInfoEx(LOG_HOST, P25_LDU1_STR " audio (silence padded), srcId = %u, dstId = %u", srcId, dstId); m_network->writeP25LDU1(lc, lsd, m_netLDU1, FrameType::DATA_UNIT); - m_p25N++; + m_p25N = 9U; break; } // send P25 LDU2 - if (m_p25N == 1U) { - LogMessage(LOG_HOST, P25_LDU2_STR " silence audio, algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); + if (m_p25N == 17U) { + LogInfoEx(LOG_HOST, P25_LDU2_STR " audio (silence padded), algo = $%02X, kid = $%04X", ALGO_UNENCRYPT, 0U); m_network->writeP25LDU2(lc, lsd, m_netLDU2); m_p25N = 0U; break; @@ -3625,7 +3958,7 @@ void* HostBridge::threadCallWatchdog(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -3651,6 +3984,17 @@ void* HostBridge::threadCallWatchdog(void* arg) bridge->m_udpDropTime.clock(ms); } + // Debounce RTS PTT clear using hold-off after last audio output + if (bridge->m_rtsPttEnable && bridge->m_rtsPttActive) { + uint64_t sinceLastOut = system_clock::hrc::diffNow(bridge->m_lastAudioOut); + if (sinceLastOut >= bridge->m_rtsPttHoldoffMs) { + bridge->deassertRtsPtt(); + } + } + + // When CTS COR is active, the audio processing thread handles frame transmission + // We don't use the watchdog thread for padding to avoid conflicts with actual audio frames + std::string trafficType = LOCAL_CALL; if (bridge->m_trafficFromUDP) trafficType = UDP_CALL; @@ -3668,57 +4012,25 @@ void* HostBridge::threadCallWatchdog(void* arg) srcId = bridge->m_udpSrcId; dstId = bridge->m_udpDstId; - if (bridge->m_udpSilenceDuringHang) { - if (bridge->m_udpCallClock.isRunning()) - bridge->m_udpCallClock.clock(ms); - - if (bridge->m_udpCallClock.isRunning() && bridge->m_udpCallClock.hasExpired()) { - bridge->m_udpCallClock.stop(); - bridge->m_udpHangTime.start(); - - bridge->m_dmrN = 0U; - bridge->m_p25N = 0U; - bridge->m_analogN = 0U; - } - - if (bridge->m_udpHangTime.isRunning()) - bridge->m_udpHangTime.clock(ms); - - if (bridge->m_udpHangTime.isRunning() && bridge->m_udpHangTime.hasExpired()) { - bridge->callEndSilence(srcId, dstId); - bridge->m_udpHangTime.start(); - } - } - if (bridge->m_udpDropTime.isRunning() && bridge->m_udpDropTime.hasExpired()) { - if (bridge->m_udpSilenceDuringHang) { - bridge->m_udpHangTime.stop(); - switch (bridge->m_txMode) { - case TX_MODE_DMR: - // TODO: send DMR silence - break; - case TX_MODE_P25: - if (bridge->m_p25N > 0U) - bridge->callEndSilence(srcId, dstId); - break; - } - } - bridge->callEnd(srcId, dstId); } } else { - // if we've exceeded the drop timeout, then really drop the audio - if (bridge->m_localDropTime.isRunning() && (bridge->m_localDropTime.getTimer() >= dropTimeout)) { - LogMessage(LOG_HOST, "%s, terminating stuck call", trafficType.c_str()); - bridge->callEnd(srcId, dstId); + // Don't end call due to drop timeout if COR is still active + if (!bridge->m_ctsCorActive) { + // if we've exceeded the drop timeout, then really drop the audio + if (bridge->m_localDropTime.isRunning() && (bridge->m_localDropTime.getTimer() >= dropTimeout)) { + LogInfoEx(LOG_HOST, "%s, terminating stuck call", trafficType.c_str()); + bridge->callEnd(srcId, dstId); + } } } Thread::sleep(5U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -3749,6 +4061,56 @@ bool HostBridge::initializeRtsPtt() return true; } +/* Helper to initialize CTS COR detection. */ + +bool HostBridge::initializeCtsCor() +{ + if (!m_ctsCorEnable) + return true; + + if (m_ctsCorPort.empty()) { + ::LogError(LOG_HOST, "CTS COR port is not specified"); + return false; + } + + m_ctsCorController = new CtsCorController(m_ctsCorPort); + + // If RTS PTT and CTS COR are on the same port, reuse the file descriptor + // to avoid opening the port twice (which would affect RTS) + int reuseFd = -1; + if (m_rtsPttEnable && m_rtsPttController != nullptr && + m_rtsPttPort == m_ctsCorPort && m_rtsPttController->getFd() >= 0) { + reuseFd = m_rtsPttController->getFd(); + ::LogInfo(LOG_HOST, "CTS COR reusing RTS PTT file descriptor for %s (same port)", m_ctsCorPort.c_str()); + } + + if (!m_ctsCorController->open(reuseFd)) { + ::LogError(LOG_HOST, "Failed to open CTS COR port %s", m_ctsCorPort.c_str()); + delete m_ctsCorController; + m_ctsCorController = nullptr; + return false; + } + + // Start monitor thread + thread_t* th = new thread_t(); + th->obj = this; + if (!Thread::runAsThread(this, &HostBridge::threadCtsCorMonitor, th)) { + ::LogError(LOG_HOST, "Failed to start CTS COR monitor thread"); + return false; + } + + ::LogInfo(LOG_HOST, "CTS COR initialized on %s", m_ctsCorPort.c_str()); + + // Test read CTS state to verify it's working + bool ctsRaw = m_ctsCorController->isCtsAsserted(); + bool ctsEffective = m_ctsCorInvert ? !ctsRaw : ctsRaw; + ::LogInfo(LOG_HOST, "CTS COR initial state: raw=%s, effective=%s (%s)", + ctsRaw ? "HIGH" : "LOW", ctsEffective ? "TRIGGER" : "IDLE", + m_ctsCorInvert ? "inverted" : "normal"); + + return true; +} + /* Helper to assert RTS PTT (start transmission). */ void HostBridge::assertRtsPtt() diff --git a/src/bridge/HostBridge.h b/src/bridge/HostBridge.h index dbd6485fc..7449aadcf 100644 --- a/src/bridge/HostBridge.h +++ b/src/bridge/HostBridge.h @@ -26,6 +26,7 @@ #include "common/yaml/Yaml.h" #include "common/RingBuffer.h" #include "common/Timer.h" +#include "common/Clock.h" #include "vocoder/MBEDecoder.h" #include "vocoder/MBEEncoder.h" #define MINIAUDIO_IMPLEMENTATION @@ -33,6 +34,7 @@ #include "mdc/mdc_decode.h" #include "network/PeerNetwork.h" #include "RtsPttController.h" +#include "CtsCorController.h" #include #include @@ -103,13 +105,11 @@ void mdcPacketDetected(int frameCount, mdc_u8_t op, mdc_u8_t arg, mdc_u16_t unit * @ingroup bridge */ struct NetPacketRequest { - uint32_t srcId; - uint32_t dstId; + uint32_t srcId; //!< Source Address + uint32_t dstId; //!< Destination Address - int pcmLength = 0U; //! Length of PCM data buffer - uint8_t* pcm = nullptr; //! Raw PCM buffer - - uint64_t pktRxTime; //! Packet receive time + int pcmLength = 0U; //!< Length of PCM data buffer + uint8_t* pcm = nullptr; //!< Raw PCM buffer }; // --------------------------------------------------------------------------- @@ -159,10 +159,8 @@ class HOST_SW_API HostBridge { bool m_udpUseULaw; bool m_udpRTPFrames; bool m_udpUsrp; - uint8_t m_udpInterFrameDelay; - uint16_t m_udpJitter; - bool m_udpSilenceDuringHang; - uint64_t m_lastUdpFrameTime; + bool m_udpFrameTiming; + uint32_t m_udpFrameCnt; uint8_t m_tekAlgoId; uint16_t m_tekKeyId; @@ -190,8 +188,6 @@ class HOST_SW_API HostBridge { float m_voxSampleLevel; uint16_t m_dropTimeMS; Timer m_localDropTime; - Timer m_udpCallClock; - Timer m_udpHangTime; Timer m_udpDropTime; bool m_detectAnalogMDC1200; @@ -255,6 +251,8 @@ class HOST_SW_API HostBridge { uint8_t m_detectedSampleCnt; bool m_dumpSampleLevel; + bool m_mtNoSleep; + bool m_running; bool m_trace; bool m_debug; @@ -264,14 +262,26 @@ class HOST_SW_API HostBridge { std::string m_rtsPttPort; RtsPttController* m_rtsPttController; bool m_rtsPttActive; + // Timestamp of last audio written to output and hold-off before clearing PTT + system_clock::hrc::hrc_t m_lastAudioOut; + uint32_t m_rtsPttHoldoffMs; + + // CTS COR Detection + bool m_ctsCorEnable; + std::string m_ctsCorPort; + CtsCorController* m_ctsCorController; + bool m_ctsCorActive; + bool m_ctsCorInvert; // if true, COR LOW triggers (instead of HIGH) + Timer m_ctsPadTimeout; // drives silence padding while CTS is active + uint32_t m_ctsCorHoldoffMs; // hold-off time before clearing COR after it deasserts uint16_t m_rtpSeqNo; uint32_t m_rtpTimestamp; uint32_t m_usrpSeqNo; - static std::mutex m_audioMutex; - static std::mutex m_networkMutex; + static std::mutex s_audioMutex; + static std::mutex s_networkMutex; #if defined(_WIN32) void* m_decoderState; @@ -523,6 +533,11 @@ class HOST_SW_API HostBridge { * @returns bool True, if RTS PTT was initialized successfully, otherwise false. */ bool initializeRtsPtt(); + /** + * @brief Helper to initialize CTS COR detection. + * @returns bool True, if CTS COR was initialized successfully, otherwise false. + */ + bool initializeCtsCor(); /** * @brief Helper to assert RTS PTT (start transmission). */ @@ -565,7 +580,7 @@ class HOST_SW_API HostBridge { * @param srcId * @param dstId */ - void callEndSilence(uint32_t srcId, uint32_t dstId); + void padSilenceAudio(uint32_t srcId, uint32_t dstId); /** * @brief Entry point to call watchdog handler thread. @@ -573,6 +588,12 @@ class HOST_SW_API HostBridge { * @returns void* (Ignore) */ static void* threadCallWatchdog(void* arg); + /** + * @brief Entry point to CTS COR monitor thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadCtsCorMonitor(void* arg); }; #endif // __HOST_BRIDGE_H__ diff --git a/src/bridge/RtsPttController.cpp b/src/bridge/RtsPttController.cpp index e82be9c99..cb0c29d68 100644 --- a/src/bridge/RtsPttController.cpp +++ b/src/bridge/RtsPttController.cpp @@ -211,6 +211,17 @@ bool RtsPttController::clearPTT() return true; } +/* Gets the file descriptor for sharing with CTS COR controller. */ + +int RtsPttController::getFd() const +{ +#if defined(_WIN32) + return (int)(intptr_t)m_fd; +#else + return m_fd; +#endif // defined(_WIN32) +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/bridge/RtsPttController.h b/src/bridge/RtsPttController.h index 5cc8860c4..f607c40a2 100644 --- a/src/bridge/RtsPttController.h +++ b/src/bridge/RtsPttController.h @@ -72,6 +72,12 @@ class HOST_SW_API RtsPttController { */ bool clearPTT(); + /** + * @brief Gets the file descriptor for sharing with CTS COR controller. + * @returns int File descriptor, or -1 if not open. + */ + int getFd() const; + private: std::string m_port; bool m_isOpen; diff --git a/src/bridge/network/PeerNetwork.cpp b/src/bridge/network/PeerNetwork.cpp index 449d68b8c..31e75d0cb 100644 --- a/src/bridge/network/PeerNetwork.cpp +++ b/src/bridge/network/PeerNetwork.cpp @@ -11,9 +11,9 @@ #include "common/dmr/data/EMB.h" #include "common/dmr/lc/FullLC.h" #include "common/dmr/SlotType.h" -#include "common/network/json/json.h" #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" +#include "common/json/json.h" #include "common/Utils.h" #include "network/PeerNetwork.h" diff --git a/src/bridge/win32/resource.rc b/src/bridge/win32/resource.rc index 964be84fb..86e3e5186 100644 Binary files a/src/bridge/win32/resource.rc and b/src/bridge/win32/resource.rc differ diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index ea4983dbb..47d9ea131 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -47,12 +47,12 @@ file(GLOB common_SRC "src/common/edac/*.cpp" "src/common/lookups/*.cpp" "src/common/network/*.cpp" - "src/common/network/rest/*.cpp" - "src/common/network/rest/http/*.cpp" "src/common/network/sip/*.cpp" "src/common/network/tcp/*.cpp" "src/common/network/udp/*.cpp" "src/common/network/viface/*.cpp" + "src/common/restapi/*.cpp" + "src/common/restapi/http/*.cpp" "src/common/yaml/*.cpp" "src/common/zlib/*.cpp" "src/common/zlib/*.c" @@ -99,15 +99,17 @@ file(GLOB common_INCLUDE "src/common/concurrent/*.h" "src/common/edac/*.h" "src/common/edac/rs/*.h" + "src/common/json/*.h" "src/common/lookups/*.h" "src/common/network/*.h" "src/common/network/json/*.h" - "src/common/network/rest/*.h" - "src/common/network/rest/http/*.h" "src/common/network/sip/*.h" "src/common/network/tcp/*.h" "src/common/network/udp/*.h" "src/common/network/viface/*.h" + "src/common/restapi/*.h" + "src/common/restapi/http/*.h" + "src/common/backtrace/*.h" "src/common/yaml/*.h" "src/common/zlib/*.h" "src/common/*.h" diff --git a/src/common/Clock.h b/src/common/Clock.h index 52ef36084..46a1a1e44 100644 --- a/src/common/Clock.h +++ b/src/common/Clock.h @@ -24,8 +24,21 @@ #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include #include + +/* +** bryanb: because we've included Windows.h in a header -- take the nuclear option +** make sure Windows.h and any of its includes *DO NOT* define min/max macros +*/ +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif + #else #include #endif // defined(_WIN32) diff --git a/src/common/DESCrypto.cpp b/src/common/DESCrypto.cpp new file mode 100644 index 000000000..7871ca889 --- /dev/null +++ b/src/common/DESCrypto.cpp @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT +/* + * Digital Voice Modem - Common Library + * MIT Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "DESCrypto.h" +#include "Log.h" +#include "Utils.h" + +using namespace crypto; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define LB32_MASK 0x00000001 +#define LB64_MASK 0x0000000000000001 +#define L64_MASK 0x00000000ffffffff +#define H64_MASK 0xffffffff00000000 + +// Permuted Choice 1 Table [7*8] +static const char PC1_TABLE[] = { + 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4 +}; + +// Permuted Choice 2 Table [6*8] +static const char PC2_TABLE[] = { + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 +}; + +// Iteration Shift Array +static const char ITERATION_SHIFT[] = { +// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 +}; + +// Initial Permutation Table [8*8] +static const char IP[] = { + 58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7 +}; + +// Inverse Initial Permutation Table [8*8] +static const char FP[] = { + 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25 +}; + +// Expansion Table [6*8] +static const char EXPANSION[] = { + 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1 +}; + +// Post S-Box Permutation [4*8] +static const char PBOX[] = { + 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25 +}; + +// The S-Box Tables [8*16*4] +static const char SBOX[8][64] = { + { // S1 + 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, + 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, + 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, + 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, + { // S2 + 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, + 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, + 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, + 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, + { // S3 + 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, + 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, + 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, + 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, + { // S4 + 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, + 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, + 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, + 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, + { // S5 + 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, + 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, + 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, + 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, + { // S6 + 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, + 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, + 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, + 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, + { // S7 + 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, + 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, + 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, + 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, + { // S8 + 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, + 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, + 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, + 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } +}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the DES class. */ + +DES::DES() = default; + +/* Encrypt input block with given key. */ + +uint8_t* DES::encryptBlock(const uint8_t block[], const uint8_t key[]) +{ + ulong64_t keyValue = toValue(key); + ulong64_t blockValue = toValue(block); + + generateSubkeys(keyValue); + ulong64_t out = des(blockValue, false); + + return fromValue(out); +} + +/* Decrypt input block with given key. */ + +uint8_t* DES::decryptBlock(const uint8_t block[], const uint8_t key[]) +{ + ulong64_t keyValue = toValue(key); + ulong64_t blockValue = toValue(block); + + generateSubkeys(keyValue); + ulong64_t out = des(blockValue, true); + + return fromValue(out); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to convert payload bytes to a 64-bit long value. */ + +ulong64_t DES::toValue(const uint8_t* payload) +{ + assert(payload != nullptr); + + ulong64_t value = 0U; + + // combine bytes into ulong64_t (8 byte) value + value = payload[0U]; + value = (value << 8) + payload[1U]; + value = (value << 8) + payload[2U]; + value = (value << 8) + payload[3U]; + value = (value << 8) + payload[4U]; + value = (value << 8) + payload[5U]; + value = (value << 8) + payload[6U]; + value = (value << 8) + payload[7U]; + + return value; +} + +/* Internal helper to convert a 64-bit long value to payload bytes. */ + +uint8_t* DES::fromValue(const ulong64_t value) +{ + uint8_t* payload = new uint8_t[8U]; + ::memset(payload, 0x00U, 8U); + + // split ulong64_t (8 byte) value into bytes + payload[0U] = (uint8_t)((value >> 56) & 0xFFU); + payload[1U] = (uint8_t)((value >> 48) & 0xFFU); + payload[2U] = (uint8_t)((value >> 40) & 0xFFU); + payload[3U] = (uint8_t)((value >> 32) & 0xFFU); + payload[4U] = (uint8_t)((value >> 24) & 0xFFU); + payload[5U] = (uint8_t)((value >> 16) & 0xFFU); + payload[6U] = (uint8_t)((value >> 8) & 0xFFU); + payload[7U] = (uint8_t)((value >> 0) & 0xFFU); + + return payload; +} + +/* */ + +void DES::generateSubkeys(uint64_t key) +{ + // initial key schedule calculation + uint64_t PC1 = 0; // 56 bits + for (uint8_t i = 0; i < 56; i++) { + PC1 <<= 1; + PC1 |= (key >> (64 - PC1_TABLE[i])) & LB64_MASK; + } + + // 28 bits + uint32_t C = (uint32_t)((PC1 >> 28) & 0x000000000fffffff); + uint32_t D = (uint32_t)(PC1 & 0x000000000fffffff); + + // calculation of the 16 keys + for (uint8_t i = 0; i < 16; i++) { + // key schedule, shifting Ci and Di + for (uint8_t j = 0; j < ITERATION_SHIFT[i]; j++) { + C = (0x0fffffff & (C << 1)) | (0x00000001 & (C >> 27)); + D = (0x0fffffff & (D << 1)) | (0x00000001 & (D >> 27)); + } + + uint64_t PC2 = (((uint64_t)C) << 28) | (uint64_t)D; + + sub_key[i] = 0; // 48 bits (2*24) + for (uint8_t j = 0; j < 48; j++) { + sub_key[i] <<= 1; + sub_key[i] |= (PC2 >> (56 - PC2_TABLE[j])) & LB64_MASK; + } + } +} + +/* */ + +ulong64_t DES::des(ulong64_t block, bool decrypt) +{ + // applying initial permutation + block = intialPermutation(block); + + // dividing T' into two 32-bit parts + uint32_t L = (uint32_t)(block >> 32) & L64_MASK; + uint32_t R = (uint32_t)(block & L64_MASK); + + // 16 rounds + for (uint8_t i = 0; i < 16; i++) { + uint32_t F = decrypt ? f(R, sub_key[15 - i]) : f(R, sub_key[i]); + feistel(L, R, F); + } + + // swapping the two parts + block = (((uint64_t)R) << 32) | (uint64_t)L; + + // applying final permutation + return finalPermutation(block); +} + +/* */ + +ulong64_t DES::intialPermutation(ulong64_t block) +{ + // initial permutation + uint64_t result = 0; + for (uint8_t i = 0; i < 64; i++) { + result <<= 1; + result |= (block >> (64 - IP[i])) & LB64_MASK; + } + + return result; +} + +/* */ + +ulong64_t DES::finalPermutation(ulong64_t block) +{ + // inverse initial permutation + uint64_t result = 0; + for (uint8_t i = 0; i < 64; i++) { + result <<= 1; + result |= (block >> (64 - FP[i])) & LB64_MASK; + } + + return result; +} + +/* */ + +void DES::feistel(uint32_t& L, uint32_t& R, uint32_t F) +{ + uint32_t temp = R; + R = L ^ F; + L = temp; +} + +/* */ + +uint32_t DES::f(uint32_t R, ulong64_t k) +{ + // applying expansion permutation and returning 48-bit data + ulong64_t input = 0; + for (uint8_t i = 0; i < 48; i++) { + input <<= 1; + input |= (ulong64_t)((R >> (32 - EXPANSION[i])) & LB32_MASK); + } + + // XORing expanded Ri with Ki, the round key + input = input ^ k; + + // applying S-Boxes function and returning 32-bit data + uint32_t output = 0; + for (uint8_t i = 0; i < 8; i++) { + // Outer bits + char row = (char)((input & (0x0000840000000000 >> 6 * i)) >> (42 - 6 * i)); + row = (row >> 4) | (row & 0x01); + + // Middle 4 bits of input + char column = (char)((input & (0x0000780000000000 >> 6 * i)) >> (43 - 6 * i)); + + output <<= 4; + output |= (uint32_t)(SBOX[i][16 * row + column] & 0x0f); + } + + // applying the round permutation + uint32_t result = 0; + for (uint8_t i = 0; i < 32; i++) { + result <<= 1; + result |= (output >> (32 - PBOX[i])) & LB32_MASK; + } + + return result; +} diff --git a/src/common/DESCrypto.h b/src/common/DESCrypto.h new file mode 100644 index 000000000..d437d6144 --- /dev/null +++ b/src/common/DESCrypto.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +/* + * Digital Voice Modem - Common Library + * MIT Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file DESCrypto.h + * @ingroup crypto + * @file DESCrypto.cpp + * @ingroup crypto + */ +#if !defined(__DES_CRYPTO_H__) +#define __DES_CRYPTO_H__ + +#include "common/Defines.h" + +namespace crypto +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Data Encryption Standard Algorithm. + * @ingroup crypto + */ + class HOST_SW_API DES { + public: + /** + * @brief Initializes a new instance of the DES class. + */ + explicit DES(); + + /** + * @brief Encrypt input block with given key. + * @param in Input buffer with block to encrypt. + * @param key Encryption key. + * @returns uint8_t* Encrypted input buffer. + */ + uint8_t* encryptBlock(const uint8_t block[], const uint8_t key[]); + /** + * @brief Decrypt input block with given key. + * @param block Input buffer with block to encrypt. + * @param key Encryption key. + * @returns uint8_t* Encrypted input buffer. + */ + uint8_t* decryptBlock(const uint8_t block[], const uint8_t key[]); + + private: + uint64_t sub_key[16]; // 48 bits each + + static ulong64_t toValue(const uint8_t* payload); + static uint8_t* fromValue(const ulong64_t value); + + void generateSubkeys(uint64_t key); + + ulong64_t des(ulong64_t block, bool decrypt); + + ulong64_t intialPermutation(ulong64_t block); + ulong64_t finalPermutation(ulong64_t block); + + void feistel(uint32_t& L, uint32_t& R, uint32_t F); + uint32_t f(uint32_t R, ulong64_t k); + }; +} // namespace crypto + +#endif // __DES_CRYPTO_H__ diff --git a/src/common/Defines.h b/src/common/Defines.h index bb9db1e26..4b167ceaf 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -106,22 +106,15 @@ typedef unsigned long long ulong64_t; // Constants // --------------------------------------------------------------------------- -#ifndef __GIT_VER__ -#define __GIT_VER__ "00000000" -#endif -#ifndef __GIT_VER_HASH__ -#define __GIT_VER_HASH__ "00000000" -#endif - #define __PROG_NAME__ "" #define __EXE_NAME__ "" -#define VERSION_MAJOR "04" -#define VERSION_MINOR "32" -#define VERSION_REV "J" +#define VERSION_MAJOR "05" +#define VERSION_MINOR "02" +#define VERSION_REV "A" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR -#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" +#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR ")" #define __BUILD__ __DATE__ " " __TIME__ #if !defined(NDEBUG) @@ -168,32 +161,32 @@ const uint32_t RPC_DEFAULT_PORT = 9890; * @brief Operational Host States */ enum HOST_STATE { - FNE_STATE = 240U, //! FNE (only used by dvmfne) + FNE_STATE = 240U, //!< FNE (only used by dvmfne) - HOST_STATE_LOCKOUT = 250U, //! Lockout (dvmhost traffic lockout state) - HOST_STATE_ERROR = 254U, //! Error (dvmhost error state) - HOST_STATE_QUIT = 255U, //! Quit (dvmhost quit state) + HOST_STATE_LOCKOUT = 250U, //!< Lockout (dvmhost traffic lockout state) + HOST_STATE_ERROR = 254U, //!< Error (dvmhost error state) + HOST_STATE_QUIT = 255U, //!< Quit (dvmhost quit state) }; /** * @brief Operational RF States */ enum RPT_RF_STATE { - RS_RF_LISTENING, //! Modem Listening - RS_RF_LATE_ENTRY, //! Traffic Late Entry - RS_RF_AUDIO, //! Audio - RS_RF_DATA, //! Data - RS_RF_REJECTED, //! Traffic Rejected - RS_RF_INVALID //! Traffic Invalid + RS_RF_LISTENING, //!< Modem Listening + RS_RF_LATE_ENTRY, //!< Traffic Late Entry + RS_RF_AUDIO, //!< Audio + RS_RF_DATA, //!< Data + RS_RF_REJECTED, //!< Traffic Rejected + RS_RF_INVALID //!< Traffic Invalid }; /** * @brief Operational Network States */ enum RPT_NET_STATE { - RS_NET_IDLE, //! Idle - RS_NET_AUDIO, //! Audio - RS_NET_DATA //! Data + RS_NET_IDLE, //!< Idle + RS_NET_AUDIO, //!< Audio + RS_NET_DATA //!< Data }; const uint8_t UDP_COMPRESS_NONE = 0x00U; diff --git a/src/common/GitHash.h b/src/common/GitHash.h new file mode 100644 index 000000000..a10771267 --- /dev/null +++ b/src/common/GitHash.h @@ -0,0 +1,36 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file GitHash.h + * @ingroup common + */ +#pragma once +#if !defined(__GIT_HASH_H__) +#define __GIT_HASH_H__ + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#ifndef __GIT_VER__ +#define __GIT_VER__ "00000000" +#endif +#ifndef __GIT_VER_HASH__ +#define __GIT_VER_HASH__ "00000000" +#endif + +#ifdef __VER__ +#undef __VER__ +#define __VER__ VERSION_MAJOR "." VERSION_MINOR VERSION_REV " (R" VERSION_MAJOR VERSION_REV VERSION_MINOR " " __GIT_VER__ ")" +#endif + +/** @} */ + +#endif // __GIT_HASH_H__ diff --git a/src/common/Log.cpp b/src/common/Log.cpp index 10a835a1f..143b639ef 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -11,21 +11,17 @@ #include "Log.h" #include "network/BaseNetwork.h" -#if defined(_WIN32) -#include "Clock.h" -#else -#include -#include -#endif // defined(_WIN32) - #if defined(CATCH2_TEST_COMPILATION) #include #endif +#if !defined(_WIN32) +#include +#endif // defined(_WIN32) + #include #include #include -#include #include #include #include @@ -42,13 +38,13 @@ const uint32_t LOG_BUFFER_LEN = 4096U; // Global Variables // --------------------------------------------------------------------------- -static uint32_t m_fileLevel = 0U; -static std::string m_filePath; -static std::string m_fileRoot; +static uint32_t g_fileLevel = 0U; +static std::string g_filePath; +static std::string g_fileRoot; -static network::BaseNetwork* m_network; +static network::BaseNetwork* g_network; -static FILE* m_fpLog = nullptr; +static FILE* g_fpLog = nullptr; uint32_t g_logDisplayLevel = 2U; bool g_disableTimeDisplay = false; @@ -56,11 +52,11 @@ bool g_disableTimeDisplay = false; bool g_useSyslog = false; bool g_disableNetworkLog = false; -static struct tm m_tm; +static struct tm g_tm; -static std::ostream m_outStream { std::cerr.rdbuf() }; +static std::ostream g_outStream { std::cerr.rdbuf() }; -static char LEVELS[] = " DMIWEF"; +bool log_stacktrace::SignalHandling::s_foreground = false; // --------------------------------------------------------------------------- // Global Functions @@ -68,15 +64,15 @@ static char LEVELS[] = " DMIWEF"; /* Helper to get the current log file level. */ -uint32_t CurrentLogFileLevel() { return m_fileLevel; } +uint32_t CurrentLogFileLevel() { return g_fileLevel; } /* Helper to get the current log file path. */ -std::string LogGetFilePath() { return m_filePath; } +std::string LogGetFilePath() { return g_filePath; } /* Helper to get the current log file root. */ -std::string LogGetFileRoot() { return m_fileRoot; } +std::string LogGetFileRoot() { return g_fileRoot; } /* Helper to open the detailed log file, file handle. */ @@ -85,7 +81,7 @@ static bool LogOpen() #if defined(CATCH2_TEST_COMPILATION) return true; #endif - if (m_fileLevel == 0U) + if (g_fileLevel == 0U) return true; if (!g_useSyslog) { @@ -94,26 +90,26 @@ static bool LogOpen() struct tm* tm = ::localtime(&now); - if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) { - if (m_fpLog != nullptr) + if (tm->tm_mday == g_tm.tm_mday && tm->tm_mon == g_tm.tm_mon && tm->tm_year == g_tm.tm_year) { + if (g_fpLog != nullptr) return true; } else { - if (m_fpLog != nullptr) - ::fclose(m_fpLog); + if (g_fpLog != nullptr) + ::fclose(g_fpLog); } char filename[200U]; - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", g_filePath.c_str(), g_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - m_fpLog = ::fopen(filename, "a+t"); - m_tm = *tm; + g_fpLog = ::fopen(filename, "a+t"); + g_tm = *tm; - return m_fpLog != nullptr; + return g_fpLog != nullptr; } else { #if !defined(_WIN32) - switch (m_fileLevel) { + switch (g_fileLevel) { case 1U: setlogmask(LOG_UPTO(LOG_DEBUG)); break; @@ -132,7 +128,7 @@ static bool LogOpen() break; } - openlog(m_fileRoot.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON); + openlog(g_fileRoot.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON); return true; #else return false; @@ -140,19 +136,12 @@ static bool LogOpen() } } -/* Internal helper to set an output stream to direct logging to. */ - -void __InternalOutputStream(std::ostream& stream) -{ - m_outStream.rdbuf(stream.rdbuf()); -} - /* Gets the instance of the Network class to transfer the activity log with. */ void* LogGetNetwork() { // NO GOOD, VERY BAD, TERRIBLE HACK - return (void*)m_network; + return (void*)g_network; } /* Sets the instance of the Network class to transfer the activity log with. */ @@ -164,16 +153,16 @@ void LogSetNetwork(void* network) #endif // note: The Network class is passed here as a void so we can avoid including the Network.h // header in Log.h. This is dirty and probably terrible... - m_network = (network::BaseNetwork*)network; + g_network = (network::BaseNetwork*)network; } /* Initializes the diagnostics log. */ bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay, bool useSyslog) { - m_filePath = filePath; - m_fileRoot = fileRoot; - m_fileLevel = fileLevel; + g_filePath = filePath; + g_fileRoot = fileRoot; + g_fileLevel = fileLevel; g_logDisplayLevel = displayLevel; g_disableTimeDisplay = disableTimeDisplay; #if defined(_WIN32) @@ -192,9 +181,9 @@ void LogFinalise() #if defined(CATCH2_TEST_COMPILATION) return; #endif - if (m_fpLog != nullptr) { - ::fclose(m_fpLog); - m_fpLog = nullptr; + if (g_fpLog != nullptr) { + ::fclose(g_fpLog); + g_fpLog = nullptr; } #if !defined(_WIN32) if (g_useSyslog) @@ -202,140 +191,42 @@ void LogFinalise() #endif // !defined(_WIN32) } -/* Writes a new entry to the diagnostics log. */ +/* Internal helper to set an output stream to direct logging to. */ -void Log(uint32_t level, const char *module, const char* file, const int lineNo, const char* func, const char* fmt, ...) +void log_internal::SetInternalOutputStream(std::ostream& stream) { - assert(fmt != nullptr); -#if defined(CATCH2_TEST_COMPILATION) - g_disableTimeDisplay = true; -#endif - char buffer[LOG_BUFFER_LEN]; - if (!g_disableTimeDisplay && !g_useSyslog) { - time_t now; - ::time(&now); - struct tm* tm = ::localtime(&now); - - struct timeval nowMillis; - ::gettimeofday(&nowMillis, NULL); - - if (module != nullptr) { - // level 1 is DEBUG - if (level == 1U) { - // if we have a file and line number -- add that to the log entry - if (file != nullptr && lineNo > 0) { - // if we have a function name add that to the log entry - if (func != nullptr) { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u][%s] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module, file, lineNo, func); - } - else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module, file, lineNo); - } - } else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module); - } - } else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, module); - } - } - else { - // level 1 is DEBUG - if (level == 1U) { - // if we have a file and line number -- add that to the log entry - if (file != nullptr && lineNo > 0) { - // if we have a function name add that to the log entry - if (func != nullptr) { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u][%s] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, file, lineNo, func); - } - else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u] ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, file, lineNo); - } - } else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U); - } - } else { - ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U); - } - } - } - else { - if (module != nullptr) { - // level 1 is DEBUG - if (level == 1U) { - // if we have a file and line number -- add that to the log entry - if (file != nullptr && lineNo > 0) { - // if we have a function name add that to the log entry - if (func != nullptr) { - ::sprintf(buffer, "%c: (%s)[%s:%u][%s] ", LEVELS[level], module, file, lineNo, func); - } - else { - ::sprintf(buffer, "%c: (%s)[%s:%u] ", LEVELS[level], module, file, lineNo); - } - } - else { - ::sprintf(buffer, "%c: (%s) ", LEVELS[level], module); - } - } else { - ::sprintf(buffer, "%c: (%s) ", LEVELS[level], module); - } - } - else { - if (level >= 9999U) { - ::sprintf(buffer, "U: "); - } - else { - // if we have a file and line number -- add that to the log entry - if (file != nullptr && lineNo > 0) { - // if we have a function name add that to the log entry - if (func != nullptr) { - ::sprintf(buffer, "%c: [%s:%u][%s] ", LEVELS[level], file, lineNo, func); - } - else { - ::sprintf(buffer, "%c: [%s:%u] ", LEVELS[level], file, lineNo); - } - } - else { - ::sprintf(buffer, "%c: ", LEVELS[level]); - } - } - } - } - - va_list vl, vl_len; - va_start(vl, fmt); - va_copy(vl_len, vl); - - size_t len = ::vsnprintf(nullptr, 0U, fmt, vl_len); - ::vsnprintf(buffer + ::strlen(buffer), len + 1U, fmt, vl); + g_outStream.rdbuf(stream.rdbuf()); +} - va_end(vl_len); - va_end(vl); +/* Writes a new entry to the diagnostics log. */ - if (m_outStream && g_logDisplayLevel == 0U) { - m_outStream << buffer << std::endl; +void log_internal::LogInternal(uint32_t level, const std::string& log) +{ + if (g_outStream && g_logDisplayLevel == 0U) { + g_outStream << log << std::endl; } - if (m_network != nullptr && !g_disableNetworkLog) { + if (g_network != nullptr && !g_disableNetworkLog) { // don't transfer debug data... if (level > 1U) { - m_network->writeDiagLog(buffer); + g_network->writeDiagLog(log.c_str()); } } #if defined(CATCH2_TEST_COMPILATION) - UNSCOPED_INFO(buffer); + UNSCOPED_INFO(log.c_str()); return; #endif - if (level >= m_fileLevel && m_fileLevel != 0U) { + if (level >= g_fileLevel && g_fileLevel != 0U) { if (!g_useSyslog) { bool ret = ::LogOpen(); if (!ret) return; - if (m_fpLog != nullptr) { - ::fprintf(m_fpLog, "%s\n", buffer); - ::fflush(m_fpLog); + if (g_fpLog != nullptr) { + ::fprintf(g_fpLog, "%s\n", log.c_str()); + ::fflush(g_fpLog); } } else { #if !defined(_WIN32) @@ -346,16 +237,13 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo, syslogLevel = LOG_DEBUG; break; case 2U: - syslogLevel = LOG_NOTICE; - break; - case 3U: case 9999U: // in-band U: messages should also be info level syslogLevel = LOG_INFO; break; - case 4U: + case 3U: syslogLevel = LOG_WARNING; break; - case 5U: + case 4U: syslogLevel = LOG_ERR; break; default: @@ -363,20 +251,20 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo, break; } - syslog(syslogLevel, "%s", buffer); + syslog(syslogLevel, "%s", log.c_str()); #endif // !defined(_WIN32) } } if (!g_useSyslog && level >= g_logDisplayLevel && g_logDisplayLevel != 0U) { - ::fprintf(stdout, "%s" EOL, buffer); + ::fprintf(stdout, "%s" EOL, log.c_str()); ::fflush(stdout); } // fatal error (specially allow any log levels above 9999) - if (level >= 6U && level < 9999U) { - if (m_fpLog != nullptr) - ::fclose(m_fpLog); + if (level >= 5U && level < 9999U) { + if (g_fpLog != nullptr) + ::fclose(g_fpLog); #if !defined(_WIN32) if (g_useSyslog) ::closelog(); @@ -384,3 +272,24 @@ void Log(uint32_t level, const char *module, const char* file, const int lineNo, exit(1); } } + +/* Internal helper to get the log file path. */ + +std::string log_internal::GetLogFilePath() +{ + return g_filePath; +} + +/* Internal helper to get the log file root name. */ + +std::string log_internal::GetLogFileRoot() +{ + return g_fileRoot; +} + +/* Internal helper to get the log file handle pointer. */ + +FILE* log_internal::GetLogFile() +{ + return g_fpLog; +} diff --git a/src/common/Log.h b/src/common/Log.h index 2ee85a2f2..fc2c419bc 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -22,7 +22,17 @@ #define __LOG_H__ #include "common/Defines.h" +#if defined(_WIN32) +#include "common/Clock.h" +#else +#include +#endif // defined(_WIN32) +#if !defined(CATCH2_TEST_COMPILATION) +#include "common/backtrace/backward.h" +#endif + +#include #include /** @@ -38,13 +48,13 @@ #define LOG_HOST "HOST" #define LOG_REST "RESTAPI" -#define LOG_SIP "SIP" #define LOG_MODEM "MODEM" #define LOG_RF "RF" #define LOG_NET "NET" #define LOG_P25 "P25" #define LOG_NXDN "NXDN" #define LOG_DMR "DMR" +#define LOG_ANALOG "ANALOG" #define LOG_CAL "CAL" #define LOG_SETUP "SETUP" #define LOG_SERIAL "SERIAL" @@ -63,7 +73,7 @@ * * This is a variable argument function. */ -#define LogDebug(_module, fmt, ...) Log(1U, _module, __FILE__, __LINE__, nullptr, fmt, ##__VA_ARGS__) +#define LogDebug(_module, fmt, ...) Log(1U, {_module, __FILE__, __LINE__, nullptr}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a debug log entry. * @param _module Name of module generating log entry. @@ -72,15 +82,7 @@ * * This is a variable argument function. */ -#define LogDebugEx(_module, _func, fmt, ...) Log(1U, _module, __FILE__, __LINE__, _func, fmt, ##__VA_ARGS__) -/** - * @brief Macro helper to create a message log entry. - * @param _module Name of module generating log entry. - * @param fmt String format. - * - * This is a variable argument function. - */ -#define LogMessage(_module, fmt, ...) Log(2U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogDebugEx(_module, _func, fmt, ...) Log(1U, {_module, __FILE__, __LINE__, _func}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a informational log entry. * @param _module Name of module generating log entry. @@ -89,7 +91,7 @@ * This is a variable argument function. LogInfo() does not use a module * name when creating a log entry. */ -#define LogInfo(fmt, ...) Log(3U, nullptr, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogInfo(fmt, ...) Log(2U, {nullptr, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a informational log entry with module name. * @param _module Name of module generating log entry. @@ -97,7 +99,7 @@ * * This is a variable argument function. */ -#define LogInfoEx(_module, fmt, ...) Log(3U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogInfoEx(_module, fmt, ...) Log(2U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a warning log entry. * @param _module Name of module generating log entry. @@ -105,7 +107,7 @@ * * This is a variable argument function. */ -#define LogWarning(_module, fmt, ...) Log(4U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogWarning(_module, fmt, ...) Log(3U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a error log entry. * @param _module Name of module generating log entry. @@ -113,7 +115,7 @@ * * This is a variable argument function. */ -#define LogError(_module, fmt, ...) Log(5U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogError(_module, fmt, ...) Log(4U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__) /** * @brief Macro helper to create a fatal log entry. * @param _module Name of module generating log entry. @@ -121,7 +123,7 @@ * * This is a variable argument function. */ -#define LogFatal(_module, fmt, ...) Log(6U, _module, nullptr, 0, nullptr, fmt, ##__VA_ARGS__) +#define LogFatal(_module, fmt, ...) Log(5U, {_module, nullptr, 0, nullptr}, fmt, ##__VA_ARGS__) // --------------------------------------------------------------------------- // Externs @@ -144,14 +146,611 @@ extern bool g_useSyslog; */ extern bool g_disableNetworkLog; +namespace log_internal +{ + constexpr static char LOG_LEVELS[] = " DIWEF"; + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents a source code location. + * @ingroup logger + */ + struct SourceLocation { + public: + /** + * @brief Initializes a new instance of the SourceLocation class. + */ + constexpr SourceLocation() = default; + /** + * @brief Initializes a new instance of the SourceLocation class. + * @param module Application module. + * @param filename Source code filename. + * @param line Line number in source code file. + * @param func Function name within source code. + */ + constexpr SourceLocation(const char* module, const char* filename, int line, const char* func) : + module(module), + filename(filename), + line(line), + funcname(func) + { + /* stub */ + } + + public: + const char* module = nullptr; + const char* filename = nullptr; + int line = 0; + const char* funcname = nullptr; + }; + + /** + * @brief Internal helper to set an output stream to direct logging to. + * @param stream + */ + extern HOST_SW_API void SetInternalOutputStream(std::ostream& stream); + /** + * @brief Writes a new entry to the diagnostics log. + * @param level Log level for entry. + * @param log Fully formatted log message. + */ + extern HOST_SW_API void LogInternal(uint32_t level, const std::string& log); + + /** + * @brief Internal helper to get the log file path. + * @returns std::string Configured log file path. + */ + extern HOST_SW_API std::string GetLogFilePath(); + /** + * @brief Internal helper to get the log file root name. + * @returns std::string Configured log file root name. + */ + extern HOST_SW_API std::string GetLogFileRoot(); + /** + * @brief Internal helper to get the log file handle pointer. + * @returns FILE* Pointer to the open log file. + */ + extern HOST_SW_API FILE* GetLogFile(); +} // namespace log_internal + +namespace log_stacktrace +{ +#if !defined(CATCH2_TEST_COMPILATION) +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Backward backtrace signal handling class. + * @ingroup logger + */ + class HOST_SW_API SignalHandling { + public: + /** + * @brief Helper to generate a default list of POSIX signals to handle. + * @return std::vector List of POSIX signals. + */ + static std::vector makeDefaultSignals() { + const int posixSignals[] = { + // Signals for which the default action is "Core". + SIGABRT, // Abort signal from abort(3) + SIGBUS, // Bus error (bad memory access) + SIGFPE, // Floating point exception + SIGILL, // Illegal Instruction + SIGIOT, // IOT trap. A synonym for SIGABRT + SIGQUIT, // Quit from keyboard + SIGSEGV, // Invalid memory reference + SIGSYS, // Bad argument to routine (SVr4) + SIGTRAP, // Trace/breakpoint trap + SIGXCPU, // CPU time limit exceeded (4.2BSD) + SIGXFSZ, // File size limit exceeded (4.2BSD) + #if defined(BACKWARD_SYSTEM_DARWIN) + SIGEMT, // emulation instruction executed + #endif + }; + + return std::vector(posixSignals, posixSignals + sizeof posixSignals / sizeof posixSignals[0]); + } + + /** + * @brief Initializes a new instance of the SignalHandling class + * @param foreground Log stacktrace to stderr. + * @param posixSignals List of signals to handle. + */ + SignalHandling(bool foreground, const std::vector& posixSignals = makeDefaultSignals()) : + m_loaded(false) + { + bool success = true; + + s_foreground = foreground; + + const size_t stackSize = 1024 * 1024 * 8; + m_stackContent.reset(static_cast(malloc(stackSize))); + if (m_stackContent) { + stack_t ss; + ss.ss_sp = m_stackContent.get(); + ss.ss_size = stackSize; + ss.ss_flags = 0; + + if (sigaltstack(&ss, nullptr) < 0) { + success = false; + } + } else { + success = false; + } + + for (size_t i = 0; i < posixSignals.size(); ++i) { + struct sigaction action; + memset(&action, 0, sizeof action); + action.sa_flags = static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); + sigfillset(&action.sa_mask); + sigdelset(&action.sa_mask, posixSignals[i]); + #if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" + #endif + action.sa_sigaction = &sig_handler; + #if defined(__clang__) + #pragma clang diagnostic pop + #endif + int r = sigaction(posixSignals[i], &action, nullptr); + if (r < 0) + success = false; + } + + m_loaded = success; + } + + /** + * @brief Helper to return whether or not the SignalHandling class is loaded. + * @return bool True if signal handler is loaded, otherwise false. + */ + bool loaded() const { return m_loaded; } + + /** + * @brief Helper to handle a signal. + * @param signo Signal number. + * @param info Signal informational structure. + * @param _ctx Signal user context data. + */ + static void handleSignal(int signo, siginfo_t* info, void* _ctx) + { + ucontext_t *uctx = static_cast(_ctx); + + backward::StackTrace st; + void* errorAddr = nullptr; + #ifdef REG_RIP // x86_64 + errorAddr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); + #elif defined(REG_EIP) // x86_32 + errorAddr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); + #elif defined(__arm__) + errorAddr = reinterpret_cast(uctx->uc_mcontext.arm_pc); + #elif defined(__aarch64__) + #if defined(__APPLE__) + errorAddr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); + #else + errorAddr = reinterpret_cast(uctx->uc_mcontext.pc); + #endif + #elif defined(__mips__) + errorAddr = reinterpret_cast(reinterpret_cast(&uctx->uc_mcontext)->sc_pc); + #elif defined(__APPLE__) && defined(__POWERPC__) + errorAddr = reinterpret_cast(uctx->uc_mcontext->__ss.__srr0); + #elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ + defined(__POWERPC__) + errorAddr = reinterpret_cast(uctx->uc_mcontext.regs->nip); + #elif defined(__riscv) + errorAddr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); + #elif defined(__s390x__) + errorAddr = reinterpret_cast(uctx->uc_mcontext.psw.addr); + #elif defined(__APPLE__) && defined(__x86_64__) + errorAddr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); + #elif defined(__APPLE__) + errorAddr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); + #elif defined(__loongarch__) + errorAddr = reinterpret_cast(uctx->uc_mcontext.__pc); + #else + #warning ":/ sorry, ain't know no nothing none not of your architecture!" + #endif + + if (errorAddr) { + st.load_from(errorAddr, 32, reinterpret_cast(uctx), info->si_addr); + } else { + st.load_here(32, reinterpret_cast(uctx), info->si_addr); + } + + backward::Printer p; + p.address = true; + p.snippet = false; + p.color_mode = backward::ColorMode::never; + + log_internal::LogInternal(2U, "UNRECOVERABLE FATAL ERROR!"); + if (s_foreground > 0) { + p.print(st, stderr); + } + + std::string filePath = log_internal::GetLogFilePath(); + std::string fileRoot = log_internal::GetLogFileRoot(); + + time_t now; + ::time(&now); + + struct tm* tm = ::localtime(&now); + + char filename[200U]; + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.stacktrace.log", filePath.c_str(), fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + + // log stack trace to a file if we're using syslog + if (g_useSyslog) { + FILE* stacktraceFp = ::fopen(filename, "a+t"); + ::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n"); + p.print(st, stacktraceFp); + ::fflush(stacktraceFp); + ::fclose(stacktraceFp); + } else { + FILE *stacktraceFp = log_internal::GetLogFile(); + if (stacktraceFp == nullptr) { + stacktraceFp = ::fopen(filename, "a+t"); + ::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n"); + } + p.print(st, stacktraceFp); + ::fflush(stacktraceFp); + ::fclose(stacktraceFp); + } + + (void)info; + } + + private: + backward::details::handle m_stackContent; + bool m_loaded; + static bool s_foreground; + + /** + * @brief Internal helper to handle a signal. + * @param signo Signal number. + * @param info Signal informational structure. + * @param _ctx Signal user context data. + */ + #ifdef __GNUC__ + __attribute__((noreturn)) + #endif + static void sig_handler(int signo, siginfo_t* info, void* _ctx) + { + handleSignal(signo, info, _ctx); + + // try to forward the signal. + raise(info->si_signo); + + // terminate the process immediately. + puts("Abnormal termination."); + _exit(EXIT_FAILURE); + } + }; +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN +#ifdef BACKWARD_SYSTEM_WINDOWS + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Backward backtrace signal handling class. + * @ingroup logger + */ + class HOST_SW_API SignalHandling { + public: + /** + * @brief Initializes a new instance of the SignalHandling class + * @param foreground Log stacktrace to stderr. (Windows always logs to the foreground, this is ignored.) + * @param posixSignals List of signals to handle. + */ + SignalHandling(bool foreground, const std::vector& = std::vector()) : + m_reporterThread([]() { + /* We handle crashes in a utility thread: + ** backward structures and some Windows functions called here + ** need stack space, which we do not have when we encounter a + ** stack overflow. + ** To support reporting stack traces during a stack overflow, + ** we create a utility thread at startup, which waits until a + ** crash happens or the program exits normally. + */ + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != CRASH_STATUS::RUNNING; }); + } + + if (crashed() == CRASH_STATUS::CRASHED) { + handleStackTrace(skipRecs()); + } + + { + std::unique_lock lk(mtx()); + crashed() = CRASH_STATUS::ENDING; + } + cv().notify_one(); + }) + { + SetUnhandledExceptionFilter(crashHandler); + + signal(SIGABRT, signalHandler); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + std::set_terminate(&terminator); + #ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); + #endif + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalidParameterHandler); + } + /** + * @brief Finalizes a instance of the SignalHandling class + */ + ~SignalHandling() + { + { + std::unique_lock lk(mtx()); + crashed() = CRASH_STATUS::NORMAL_EXIT; + } + + cv().notify_one(); + + m_reporterThread.join(); + } + + /** + * @brief Helper to return whether or not the SignalHandling class is loaded. + * @return bool True if signal handler is loaded, otherwise false. + */ + bool loaded() const { return true; } + + private: + enum class CRASH_STATUS { + RUNNING, + CRASHED, + NORMAL_EXIT, + ENDING + }; + + /** + * @brief + * @return CONTEXT* + */ + static CONTEXT* ctx() + { + static CONTEXT data; + return &data; + } + + /** + * @brief + * @return crash_status& + */ + static CRASH_STATUS& crashed() + { + static CRASH_STATUS data; + return data; + } + + /** + * @brief + * @return std::mutex& + */ + static std::mutex& mtx() + { + static std::mutex data; + return data; + } + + /** + * @brief + * @return std::condition_variable& + */ + static std::condition_variable& cv() + { + static std::condition_variable data; + return data; + } + + /** + * @brief + * @return HANDLE& + */ + static HANDLE& threadHandle() + { + static HANDLE handle; + return handle; + } + + std::thread m_reporterThread; + static bool s_foreground; + + // TODO: how not to hardcode these? + static const constexpr int signalSkipRecs = + #ifdef __clang__ + // With clang, RtlCaptureContext also captures the stack frame of the + // current function Below that, there are 3 internal Windows functions + 4 + #else + // With MSVC cl, RtlCaptureContext misses the stack frame of the current + // function The first entries during StackWalk are the 3 internal Windows + // functions + 3 + #endif + ; + + /** + * @brief + * @return int& + */ + static int& skipRecs() + { + static int data; + return data; + } + + /** + * @brief + */ + static inline void terminator() + { + crashHandler(signalSkipRecs); + abort(); + } + + /** + * @brief + */ + static inline void signalHandler(int) + { + crashHandler(signalSkipRecs); + abort(); + } + + /** + * @brief + * @param int + */ + static inline void __cdecl invalidParameterHandler(const wchar_t *, const wchar_t *, const wchar_t *, + unsigned int, uintptr_t) + { + crashHandler(signalSkipRecs); + abort(); + } + + /** + * @brief + * @param info + * @return NOINLINE + */ + NOINLINE static LONG WINAPI crashHandler(EXCEPTION_POINTERS* info) + { + // the exception info supplies a trace from exactly where the issue was, + // no need to skip records + crashHandler(0, info->ContextRecord); + return EXCEPTION_CONTINUE_SEARCH; + } + + /** + * @brief + * @param skip + * @param ct + * @return NOINLINE + */ + NOINLINE static void crashHandler(int skip, CONTEXT* ct = nullptr) + { + if (ct == nullptr) { + RtlCaptureContext(ctx()); + } else { + memcpy(ctx(), ct, sizeof(CONTEXT)); + } + + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &threadHandle(), 0, FALSE, DUPLICATE_SAME_ACCESS); + + skipRecs() = skip; + + { + std::unique_lock lk(mtx()); + crashed() = CRASH_STATUS::CRASHED; + } + + cv().notify_one(); + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != CRASH_STATUS::CRASHED; }); + } + } + + /** + * @brief + * @param skipFrames + */ + static void handleStackTrace(int skipFrames = 0) + { + // printer creates the TraceResolver, which can supply us a machine type + // for stack walking. Without this, StackTrace can only guess using some + // macros. + // StackTrace also requires that the PDBs are already loaded, which is done + // in the constructor of TraceResolver + backward::Printer p; + + backward::StackTrace st; + st.set_machine_type(p.resolver().machine_type()); + st.set_thread_handle(threadHandle()); + st.load_here(32 + skipFrames, ctx()); + st.skip_n_firsts(skipFrames); + + p.address = true; + p.snippet = false; + p.color_mode = backward::ColorMode::never; + + log_internal::LogInternal(2U, "UNRECOVERABLE FATAL ERROR!"); + p.print(st, std::cerr); + + std::string filePath = log_internal::GetLogFilePath(); + std::string fileRoot = log_internal::GetLogFileRoot(); + + time_t now; + ::time(&now); + + struct tm* tm = ::localtime(&now); + + char filename[200U]; + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.stacktrace.log", filePath.c_str(), fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + + // log stack trace to a file if we're using syslog + FILE *stacktraceFp = log_internal::GetLogFile(); + if (stacktraceFp == nullptr) { + stacktraceFp = ::fopen(filename, "a+t"); + ::fprintf(stacktraceFp, "UNRECOVERABLE FATAL ERROR!\r\n"); + } + + p.print(st, stacktraceFp); + ::fflush(stacktraceFp); + ::fclose(stacktraceFp); + } + }; +#endif // BACKWARD_SYSTEM_WINDOWS +#ifdef BACKWARD_SYSTEM_UNKNOWN + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Backward backtrace signal handling class. + * @ingroup logger + */ + class HOST_SW_API SignalHandling { + public: + /** + * @brief Initializes a new instance of the SignalHandling class + * @param foreground Log stacktrace to stderr. + */ + SignalHandling(bool forground, const std::vector& = std::vector()) + { + /* stub */ + } + + /** + * @brief Helper to return whether or not the SignalHandling class is loaded. + * @return bool True if signal handler is loaded, otherwise false. + */ + bool loaded() { return false; } + + private: + static bool s_foreground; + }; +#endif // BACKWARD_SYSTEM_UNKNOWN +#endif // !defined(CATCH2_TEST_COMPILATION) +} + // --------------------------------------------------------------------------- -// Global Functions +// Global Function Externs // --------------------------------------------------------------------------- -/** - * @brief Internal helper to set an output stream to direct logging to. - * @param stream - */ -extern HOST_SW_API void __InternalOutputStream(std::ostream& stream); /** * @brief Helper to get the current log file level. @@ -191,23 +790,166 @@ extern HOST_SW_API void LogSetNetwork(void* network); * @param syslog Flag indicating whether or not logs will be sent to syslog. * @returns */ -extern HOST_SW_API bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay = false, bool useSyslog = false); +extern HOST_SW_API bool LogInitialise(const std::string& filePath, const std::string& fileRoot, + uint32_t fileLevel, uint32_t displayLevel, bool disableTimeDisplay = false, bool useSyslog = false); /** * @brief Finalizes the diagnostics log. */ extern HOST_SW_API void LogFinalise(); + /** * @brief Writes a new entry to the diagnostics log. * @param level Log level for entry. - * @param module Name of module generating log entry. - * @param file Name of source code file generating log entry. - * @param line Line number in source code file generating log entry. - * @param func Name of function generating log entry. + * @param sourceLog Source code location information. * @param fmt String format. * * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. */ -extern HOST_SW_API void Log(uint32_t level, const char* module, const char* file, const int lineNo, const char* func, const char* fmt, ...); +template +HOST_SW_API void Log(uint32_t level, log_internal::SourceLocation sourceLoc, const std::string& fmt, Args... args) +{ + using namespace log_internal; + + int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + +#if defined(CATCH2_TEST_COMPILATION) + g_disableTimeDisplay = true; +#endif + int prefixLen = 0; + char prefixBuf[256]; + + if (!g_disableTimeDisplay && !g_useSyslog) { + time_t now; + ::time(&now); + struct tm* tm = ::localtime(&now); + + struct timeval nowMillis; + ::gettimeofday(&nowMillis, NULL); + + if (level > 6U) + level = 2U; // default this sort of log message to INFO + + if (sourceLoc.module != nullptr) { + // level 1 is DEBUG + if (level == 1U) { + // if we have a file and line number -- add that to the log entry + if (sourceLoc.filename != nullptr && sourceLoc.line > 0) { + // if we have a function name add that to the log entry + if (sourceLoc.funcname != nullptr) { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u][%s] ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.module, sourceLoc.filename, sourceLoc.line, sourceLoc.funcname); + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s)[%s:%u] ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.module, sourceLoc.filename, sourceLoc.line); + } + } else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.module); + } + } else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu (%s) ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.module); + } + } + else { + // level 1 is DEBUG + if (level == 1U) { + // if we have a file and line number -- add that to the log entry + if (sourceLoc.filename != nullptr && sourceLoc.line > 0) { + // if we have a function name add that to the log entry + if (sourceLoc.funcname != nullptr) { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u][%s] ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.filename, sourceLoc.line, sourceLoc.funcname); + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu [%s:%u] ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, + sourceLoc.filename, sourceLoc.line); + } + } else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LOG_LEVELS[level], tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U); + } + } else { + prefixLen = ::sprintf(prefixBuf, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LOG_LEVELS[level], + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U); + } + } + } + else { + if (sourceLoc.module != nullptr) { + if (level > 6U) + level = 2U; // default this sort of log message to INFO + + // level 1 is DEBUG + if (level == 1U) { + // if we have a file and line number -- add that to the log entry + if (sourceLoc.filename != nullptr && sourceLoc.line > 0) { + // if we have a function name add that to the log entry + if (sourceLoc.funcname != nullptr) { + prefixLen = ::sprintf(prefixBuf, "%c: (%s)[%s:%u][%s] ", LOG_LEVELS[level], + sourceLoc.module, sourceLoc.filename, sourceLoc.line, sourceLoc.funcname); + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: (%s)[%s:%u] ", LOG_LEVELS[level], + sourceLoc.module, sourceLoc.filename, sourceLoc.line); + } + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: (%s) ", LOG_LEVELS[level], + sourceLoc.module); + } + } else { + prefixLen = ::sprintf(prefixBuf, "%c: (%s) ", LOG_LEVELS[level], + sourceLoc.module); + } + } + else { + if (level >= 9999U) { + prefixLen = ::sprintf(prefixBuf, "U: "); + } + else { + if (level > 6U) + level = 2U; // default this sort of log message to INFO + + // if we have a file and line number -- add that to the log entry + if (sourceLoc.filename != nullptr && sourceLoc.line > 0) { + // if we have a function name add that to the log entry + if (sourceLoc.funcname != nullptr) { + prefixLen = ::sprintf(prefixBuf, "%c: [%s:%u][%s] ", LOG_LEVELS[level], + sourceLoc.filename, sourceLoc.line, sourceLoc.funcname); + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: [%s:%u] ", LOG_LEVELS[level], + sourceLoc.filename, sourceLoc.line); + } + } + else { + prefixLen = ::sprintf(prefixBuf, "%c: ", LOG_LEVELS[level]); + } + } + } + } + + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + + std::snprintf(buf.get(), size, fmt.c_str(), args ...); + + std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen); + std::string msg = std::string(buf.get(), buf.get() + size - 1); + + LogInternal(level, std::string(prefix + msg)); +} /** @} */ #endif // __LOG_H__ diff --git a/src/common/Thread.h b/src/common/Thread.h index 7232e0125..b6d43726f 100644 --- a/src/common/Thread.h +++ b/src/common/Thread.h @@ -47,8 +47,8 @@ typedef HANDLE pthread_t; * @ingroup common */ struct thread_t { - void* obj; //! Object that created this thread. - pthread_t thread; //! Thread Handle. + void* obj; //!< Object that created this thread. + pthread_t thread; //!< Thread Handle. }; // --------------------------------------------------------------------------- diff --git a/src/common/Utils.cpp b/src/common/Utils.cpp index 12ac2e8d9..4fa66dcee 100644 --- a/src/common/Utils.cpp +++ b/src/common/Utils.cpp @@ -72,7 +72,7 @@ void Utils::dump(int level, const std::string& title, const uint8_t* data, uint3 { assert(data != nullptr); - ::Log(level, "DUMP", nullptr, 0, nullptr, "%s (len %u)", title.c_str(), length); + ::Log(level, {"DUMP", nullptr, 0, nullptr}, "%s (len %u)", title.c_str(), length); uint32_t offset = 0U; @@ -103,7 +103,7 @@ void Utils::dump(int level, const std::string& title, const uint8_t* data, uint3 output += '*'; #endif - ::Log(level, "DUMP", nullptr, 0, nullptr, "%04X: %s", offset, output.c_str()); + ::Log(level, {"DUMP", nullptr, 0, nullptr}, "%04X: %s", offset, output.c_str()); offset += 16U; @@ -143,7 +143,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng { assert(data != nullptr); - ::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "%s (len %u)", title.c_str(), length); + ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "%s (len %u)", title.c_str(), length); uint32_t offset = 0U; uint32_t count = 0U; @@ -158,7 +158,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng microslotHeader += temp; } - ::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "MCR: %s", microslotHeader.c_str()); + ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "MCR: %s", microslotHeader.c_str()); uint32_t bufLen = length; while (bufLen > 0U) { @@ -188,7 +188,7 @@ void Utils::symbols(const std::string& title, const uint8_t* data, uint32_t leng symOffset += 9; } - ::Log(2U, "SYMBOLS", nullptr, 0, nullptr, "%03u: %s", count, output.c_str()); + ::Log(2U, {"SYMBOLS", nullptr, 0, nullptr}, "%03u: %s", count, output.c_str()); offset += 18U; count += 2U; diff --git a/src/common/analog/AnalogDefines.h b/src/common/analog/AnalogDefines.h index ee604b16f..6fb6a7731 100644 --- a/src/common/analog/AnalogDefines.h +++ b/src/common/analog/AnalogDefines.h @@ -37,17 +37,17 @@ namespace analog * @{ */ - const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //! Sample size for 20ms of 16-bit audio at 8kHz. - const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //! Sample size for 20ms of 16-bit audio at 8kHz in bytes. + const uint32_t AUDIO_SAMPLES_LENGTH = 160U; //!< Sample size for 20ms of 16-bit audio at 8kHz. + const uint32_t AUDIO_SAMPLES_LENGTH_BYTES = 320U; //!< Sample size for 20ms of 16-bit audio at 8kHz in bytes. /** @} */ /** @brief Audio Frame Type(s) */ namespace AudioFrameType { /** @brief Audio Frame Type(s) */ enum E : uint8_t { - VOICE_START = 0x00U, //! Voice Start Frame - VOICE = 0x01U, //! Voice Continuation Frame - TERMINATOR = 0x02U, //! Voice End Frame / Call Terminator + VOICE_START = 0x00U, //!< Voice Start Frame + VOICE = 0x01U, //!< Voice Continuation Frame + TERMINATOR = 0x02U, //!< Voice End Frame / Call Terminator }; } diff --git a/src/common/backtrace/backward.h b/src/common/backtrace/backward.h new file mode 100644 index 000000000..6cfd737dc --- /dev/null +++ b/src/common/backtrace/backward.h @@ -0,0 +1,4550 @@ +// SPDX-License-Identifier: MIT +/* + * Digital Voice Modem - Common Library + * MIT Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2013 Google Inc. All Rights Reserved. + * + */ +/* + * backward.hpp + * Copyright 2013 Google Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef H_6B9572DA_A64B_49E6_B234_051480991C89 +#define H_6B9572DA_A64B_49E6_B234_051480991C89 + +#ifndef __cplusplus +#error "It's not going to compile without a C++ compiler..." +#endif + +#if defined(BACKWARD_CXX11) +#elif defined(BACKWARD_CXX98) +#else +#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800) +#define BACKWARD_CXX11 +#define BACKWARD_ATLEAST_CXX11 +#define BACKWARD_ATLEAST_CXX98 +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define BACKWARD_ATLEAST_CXX17 +#endif +#else +#define BACKWARD_CXX98 +#define BACKWARD_ATLEAST_CXX98 +#endif +#endif + +// You can define one of the following (or leave it to the auto-detection): +// +// #define BACKWARD_SYSTEM_LINUX +// - specialization for linux +// +// #define BACKWARD_SYSTEM_DARWIN +// - specialization for Mac OS X 10.5 and later. +// +// #define BACKWARD_SYSTEM_WINDOWS +// - specialization for Windows (Clang 9 and MSVC2017) +// +// #define BACKWARD_SYSTEM_UNKNOWN +// - placebo implementation, does nothing. +// +#if defined(BACKWARD_SYSTEM_LINUX) +#elif defined(BACKWARD_SYSTEM_DARWIN) +#elif defined(BACKWARD_SYSTEM_UNKNOWN) +#elif defined(BACKWARD_SYSTEM_WINDOWS) +#else +#if defined(__linux) || defined(__linux__) +#define BACKWARD_SYSTEM_LINUX +#elif defined(__APPLE__) +#define BACKWARD_SYSTEM_DARWIN +#elif defined(_WIN32) +#define BACKWARD_SYSTEM_WINDOWS +#else +#define BACKWARD_SYSTEM_UNKNOWN +#endif +#endif + +#define NOINLINE __attribute__((noinline)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BACKWARD_SYSTEM_LINUX) + +// On linux, backtrace can back-trace or "walk" the stack using the following +// libraries: +// +// #define BACKWARD_HAS_UNWIND 1 +// - unwind comes from libgcc, but I saw an equivalent inside clang itself. +// - with unwind, the stacktrace is as accurate as it can possibly be, since +// this is used by the C++ runtime in gcc/clang for stack unwinding on +// exception. +// - normally libgcc is already linked to your program by default. +// +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// +// #define BACKWARD_HAS_BACKTRACE == 1 +// - backtrace seems to be a little bit more portable than libunwind, but on +// linux, it uses unwind anyway, but abstract away a tiny information that is +// sadly really important in order to get perfectly accurate stack traces. +// - backtrace is part of the (e)glib library. +// +// The default is: +// #define BACKWARD_HAS_UNWIND == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_UNWIND == 1 +#elif BACKWARD_HAS_LIBUNWIND == 1 +#elif BACKWARD_HAS_BACKTRACE == 1 +#else +#undef BACKWARD_HAS_UNWIND +#define BACKWARD_HAS_UNWIND 1 +#undef BACKWARD_HAS_LIBUNWIND +#define BACKWARD_HAS_LIBUNWIND 0 +#undef BACKWARD_HAS_BACKTRACE +#define BACKWARD_HAS_BACKTRACE 0 +#endif + +// On linux, backward can extract detailed information about a stack trace +// using one of the following libraries: +// +// #define BACKWARD_HAS_DW 1 +// - libdw gives you the most juicy details out of your stack traces: +// - object filename +// - function name +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) +// - You need to link with the lib "dw": +// - apt-get install libdw-dev +// - g++/clang++ -ldw ... +// +// #define BACKWARD_HAS_BFD 1 +// - With libbfd, you get a fair amount of details: +// - object filename +// - function name +// - source filename +// - line numbers +// - source code snippet (assuming the file is accessible) +// - You need to link with the lib "bfd": +// - apt-get install binutils-dev +// - g++/clang++ -lbfd ... +// +// #define BACKWARD_HAS_DWARF 1 +// - libdwarf gives you the most juicy details out of your stack traces: +// - object filename +// - function name +// - source filename +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) +// - You need to link with the lib "dwarf": +// - apt-get install libdwarf-dev +// - g++/clang++ -ldwarf ... +// +// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +// - backtrace provides minimal details for a stack trace: +// - object filename +// - function name +// - backtrace is part of the (e)glib library. +// +// The default is: +// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_DW == 1 +#elif BACKWARD_HAS_BFD == 1 +#elif BACKWARD_HAS_DWARF == 1 +#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +#else +#undef BACKWARD_HAS_DW +#define BACKWARD_HAS_DW 0 +#undef BACKWARD_HAS_BFD +#define BACKWARD_HAS_BFD 0 +#undef BACKWARD_HAS_DWARF +#define BACKWARD_HAS_DWARF 0 +#undef BACKWARD_HAS_BACKTRACE_SYMBOL +#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +#endif + +#include +#include +#ifdef __ANDROID__ +// Old Android API levels define _Unwind_Ptr in both link.h and +// unwind.h Rename the one in link.h as we are not going to be using +// it +#define _Unwind_Ptr _Unwind_Ptr_Custom +#include +#undef _Unwind_Ptr +#else +#include +#endif +#if defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ + defined(__POWERPC__) +// Linux kernel header required for the struct pt_regs definition +// to access the NIP (Next Instruction Pointer) register value +#include +#endif +#include +#include +#include +#include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#include +#undef _GNU_SOURCE +#else +#include +#endif + +#if BACKWARD_HAS_BFD == 1 +// NOTE: defining PACKAGE{,_VERSION} is required before including +// bfd.h on some platforms, see also: +// https://sourceware.org/bugzilla/show_bug.cgi?id=14243 +#ifndef PACKAGE +#define PACKAGE +#endif +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION +#endif +#include +#endif + +#if BACKWARD_HAS_DW == 1 +#include +#include +#include +#endif + +#if BACKWARD_HAS_DWARF == 1 +#include +#include +#include +#include +#include +#endif + +#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +// then we shall rely on backtrace +#include +#endif + +#endif // defined(BACKWARD_SYSTEM_LINUX) + +#if defined(BACKWARD_SYSTEM_DARWIN) +// On Darwin, backtrace can back-trace or "walk" the stack using the following +// libraries: +// +// #define BACKWARD_HAS_UNWIND 1 +// - unwind comes from libgcc, but I saw an equivalent inside clang itself. +// - with unwind, the stacktrace is as accurate as it can possibly be, since +// this is used by the C++ runtime in gcc/clang for stack unwinding on +// exception. +// - normally libgcc is already linked to your program by default. +// +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind comes from clang, which implements an API compatible version. +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// +// #define BACKWARD_HAS_BACKTRACE == 1 +// - backtrace is available by default, though it does not produce as much +// information as another library might. +// +// The default is: +// #define BACKWARD_HAS_UNWIND == 1 +// +// Note that only one of the define should be set to 1 at a time. +// +#if BACKWARD_HAS_UNWIND == 1 +#elif BACKWARD_HAS_BACKTRACE == 1 +#elif BACKWARD_HAS_LIBUNWIND == 1 +#else +#undef BACKWARD_HAS_UNWIND +#define BACKWARD_HAS_UNWIND 1 +#undef BACKWARD_HAS_BACKTRACE +#define BACKWARD_HAS_BACKTRACE 0 +#undef BACKWARD_HAS_LIBUNWIND +#define BACKWARD_HAS_LIBUNWIND 0 +#endif + +// On Darwin, backward can extract detailed information about a stack trace +// using one of the following libraries: +// +// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +// - backtrace provides minimal details for a stack trace: +// - object filename +// - function name +// +// The default is: +// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +// +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +#else +#undef BACKWARD_HAS_BACKTRACE_SYMBOL +#define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +#endif + +#include +#include +#include +#include +#include +#include + +#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +#include +#endif +#endif // defined(BACKWARD_SYSTEM_DARWIN) + +#if defined(BACKWARD_SYSTEM_WINDOWS) + +#include +#include +#include + +#include + +#if defined(_WIN32) +#ifndef _SSIZE_T_DECLARED +typedef SSIZE_T ssize_t; +#define _SSIZE_T_DECLARED +#endif +#endif // defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include + +/* +** bryanb: because we've included Windows.h in a header -- take the nuclear option +** make sure Windows.h and any of its includes *DO NOT* define min/max macros +*/ +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif + +#include +#include + +#ifndef __clang__ +#undef NOINLINE +#define NOINLINE __declspec(noinline) +#endif + +#ifdef _MSC_VER +#pragma comment(lib, "psapi.lib") +#pragma comment(lib, "dbghelp.lib") +#endif + +// Comment / packing is from stackoverflow: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 +// Some versions of imagehlp.dll lack the proper packing directives themselves +// so we need to do it. +#pragma pack(push, before_imagehlp, 8) +#include +#pragma pack(pop, before_imagehlp) + +// TODO maybe these should be undefined somewhere else? +#undef BACKWARD_HAS_UNWIND +#undef BACKWARD_HAS_BACKTRACE +#if BACKWARD_HAS_PDB_SYMBOL == 1 +#else +#undef BACKWARD_HAS_PDB_SYMBOL +#define BACKWARD_HAS_PDB_SYMBOL 1 +#endif + +#endif + +#if BACKWARD_HAS_UNWIND == 1 + +#include +// while gcc's unwind.h defines something like that: +// extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *); +// extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *); +// +// clang's unwind.h defines something like this: +// uintptr_t _Unwind_GetIP(struct _Unwind_Context* __context); +// +// Even if the _Unwind_GetIPInfo can be linked to, it is not declared, worse we +// cannot just redeclare it because clang's unwind.h doesn't define _Unwind_Ptr +// anyway. +// +// Luckily we can play on the fact that the guard macros have a different name: +#ifdef __CLANG_UNWIND_H +// In fact, this function still comes from libgcc (on my different linux boxes, +// clang links against libgcc). +#include +extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context *, int *); +#endif + +#endif // BACKWARD_HAS_UNWIND == 1 + +#if BACKWARD_HAS_LIBUNWIND == 1 +#define UNW_LOCAL_ONLY +#include +#endif // BACKWARD_HAS_LIBUNWIND == 1 + +#ifdef BACKWARD_ATLEAST_CXX11 +#include +#include // for std::swap +namespace backward { +namespace details { +template struct hashtable { + typedef std::unordered_map type; +}; +using std::move; +} // namespace details +} // namespace backward +#else // NOT BACKWARD_ATLEAST_CXX11 +#define nullptr NULL +#define override +#include +namespace backward { +namespace details { +template struct hashtable { + typedef std::map type; +}; +template const T &move(const T &v) { return v; } +template T &move(T &v) { return v; } +} // namespace details +} // namespace backward +#endif // BACKWARD_ATLEAST_CXX11 + +namespace backward { +namespace details { +#if defined(BACKWARD_SYSTEM_WINDOWS) +const char kBackwardPathDelimiter[] = ";"; +#else +const char kBackwardPathDelimiter[] = ":"; +#endif +} // namespace details +} // namespace backward + +namespace backward { + +namespace system_tag { +struct linux_tag; // seems that I cannot call that "linux" because the name +// is already defined... so I am adding _tag everywhere. +struct darwin_tag; +struct windows_tag; +struct unknown_tag; + +#if defined(BACKWARD_SYSTEM_LINUX) +typedef linux_tag current_tag; +#elif defined(BACKWARD_SYSTEM_DARWIN) +typedef darwin_tag current_tag; +#elif defined(BACKWARD_SYSTEM_WINDOWS) +typedef windows_tag current_tag; +#elif defined(BACKWARD_SYSTEM_UNKNOWN) +typedef unknown_tag current_tag; +#else +#error "May I please get my system defines?" +#endif +} // namespace system_tag + +namespace trace_resolver_tag { +#if defined(BACKWARD_SYSTEM_LINUX) +struct libdw; +struct libbfd; +struct libdwarf; +struct backtrace_symbol; + +#if BACKWARD_HAS_DW == 1 +typedef libdw current; +#elif BACKWARD_HAS_BFD == 1 +typedef libbfd current; +#elif BACKWARD_HAS_DWARF == 1 +typedef libdwarf current; +#elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +typedef backtrace_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#elif defined(BACKWARD_SYSTEM_DARWIN) +struct backtrace_symbol; + +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +typedef backtrace_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#elif defined(BACKWARD_SYSTEM_WINDOWS) +struct pdb_symbol; +#if BACKWARD_HAS_PDB_SYMBOL == 1 +typedef pdb_symbol current; +#else +#error "You shall not pass, until you know what you want." +#endif +#endif +} // namespace trace_resolver_tag + +namespace details { + +template struct rm_ptr { typedef T type; }; + +template struct rm_ptr { typedef T type; }; + +template struct rm_ptr { typedef const T type; }; + +template struct deleter { + template void operator()(U &ptr) const { (*F)(ptr); } +}; + +template struct default_delete { + void operator()(T &ptr) const { delete ptr; } +}; + +template > +class handle { + struct dummy; + T _val; + bool _empty; + +#ifdef BACKWARD_ATLEAST_CXX11 + handle(const handle &) = delete; + handle &operator=(const handle &) = delete; +#endif + +public: + ~handle() { + if (!_empty) { + Deleter()(_val); + } + } + + explicit handle() : _val(), _empty(true) {} + explicit handle(T val) : _val(val), _empty(false) { + if (!_val) + _empty = true; + } + +#ifdef BACKWARD_ATLEAST_CXX11 + handle(handle &&from) : _empty(true) { swap(from); } + handle &operator=(handle &&from) { + swap(from); + return *this; + } +#else + explicit handle(const handle &from) : _empty(true) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + } + handle &operator=(const handle &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + return *this; + } +#endif + + void reset(T new_val) { + handle tmp(new_val); + swap(tmp); + } + + void update(T new_val) { + _val = new_val; + _empty = !static_cast(new_val); + } + + operator const dummy *() const { + if (_empty) { + return nullptr; + } + return reinterpret_cast(_val); + } + T get() { return _val; } + T release() { + _empty = true; + return _val; + } + void swap(handle &b) { + using std::swap; + swap(b._val, _val); // can throw, we are safe here. + swap(b._empty, _empty); // should not throw: if you cannot swap two + // bools without throwing... It's a lost cause anyway! + } + + T &operator->() { return _val; } + const T &operator->() const { return _val; } + + typedef typename rm_ptr::type &ref_t; + typedef const typename rm_ptr::type &const_ref_t; + ref_t operator*() { return *_val; } + const_ref_t operator*() const { return *_val; } + ref_t operator[](size_t idx) { return _val[idx]; } + + // Watch out, we've got a badass over here + T *operator&() { + _empty = false; + return &_val; + } +}; + +// Default demangler implementation (do nothing). +template struct demangler_impl { + static std::string demangle(const char *funcname) { return funcname; } +}; + +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + +template <> struct demangler_impl { + demangler_impl() : _demangle_buffer_length(0) {} + + std::string demangle(const char *funcname) { + using namespace details; + char *result = abi::__cxa_demangle(funcname, _demangle_buffer.get(), + &_demangle_buffer_length, nullptr); + if (result) { + _demangle_buffer.update(result); + return result; + } + return funcname; + } + +private: + details::handle _demangle_buffer; + size_t _demangle_buffer_length; +}; + +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN + +struct demangler : public demangler_impl {}; + +// Split a string on the platform's PATH delimiter. Example: if delimiter +// is ":" then: +// "" --> [] +// ":" --> ["",""] +// "::" --> ["","",""] +// "/a/b/c" --> ["/a/b/c"] +// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"] +// etc. +inline std::vector split_source_prefixes(const std::string &s) { + std::vector out; + size_t last = 0; + size_t next = 0; + size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1; + while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) { + out.push_back(s.substr(last, next - last)); + last = next + delimiter_size; + } + if (last <= s.length()) { + out.push_back(s.substr(last)); + } + return out; +} + +} // namespace details + +/*************** A TRACE ***************/ + +struct Trace { + void *addr; + size_t idx; + + Trace() : addr(nullptr), idx(0) {} + + explicit Trace(void *_addr, size_t _idx) : addr(_addr), idx(_idx) {} +}; + +struct ResolvedTrace : public Trace { + + struct SourceLoc { + std::string function; + std::string filename; + unsigned line; + unsigned col; + + SourceLoc() : line(0), col(0) {} + + bool operator==(const SourceLoc &b) const { + return function == b.function && filename == b.filename && + line == b.line && col == b.col; + } + + bool operator!=(const SourceLoc &b) const { return !(*this == b); } + }; + + // In which binary object this trace is located. + std::string object_filename; + + // The function in the object that contain the trace. This is not the same + // as source.function which can be an function inlined in object_function. + std::string object_function; + + // The source location of this trace. It is possible for filename to be + // empty and for line/col to be invalid (value 0) if this information + // couldn't be deduced, for example if there is no debug information in the + // binary object. + SourceLoc source; + + // An optionals list of "inliners". All the successive sources location + // from where the source location of the trace (the attribute right above) + // is inlined. It is especially useful when you compiled with optimization. + typedef std::vector source_locs_t; + source_locs_t inliners; + + ResolvedTrace() : Trace() {} + ResolvedTrace(const Trace &mini_trace) : Trace(mini_trace) {} +}; + +/*************** STACK TRACE ***************/ + +// default implemention. +template class StackTraceImpl { +public: + size_t size() const { return 0; } + Trace operator[](size_t) const { return Trace(); } + size_t load_here(size_t = 0) { return 0; } + size_t load_from(void *, size_t = 0, void * = nullptr, void * = nullptr) { + return 0; + } + size_t thread_id() const { return 0; } + void skip_n_firsts(size_t) {} + void *const *begin() const { return nullptr; } +}; + +class StackTraceImplBase { +public: + StackTraceImplBase() + : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {} + + size_t thread_id() const { return _thread_id; } + + void skip_n_firsts(size_t n) { _skip = n; } + +protected: + void load_thread_info() { +#ifdef BACKWARD_SYSTEM_LINUX +#ifndef __ANDROID__ + _thread_id = static_cast(syscall(SYS_gettid)); +#else + _thread_id = static_cast(gettid()); +#endif + if (_thread_id == static_cast(getpid())) { + // If the thread is the main one, let's hide that. + // I like to keep little secret sometimes. + _thread_id = 0; + } +#elif defined(BACKWARD_SYSTEM_DARWIN) + _thread_id = reinterpret_cast(pthread_self()); + if (pthread_main_np() == 1) { + // If the thread is the main one, let's hide that. + _thread_id = 0; + } +#endif + } + + void set_context(void *context) { _context = context; } + void *context() const { return _context; } + + void set_error_addr(void *error_addr) { _error_addr = error_addr; } + void *error_addr() const { return _error_addr; } + + size_t skip_n_firsts() const { return _skip; } + +private: + size_t _thread_id; + size_t _skip; + void *_context; + void *_error_addr; +}; + +class StackTraceImplHolder : public StackTraceImplBase { +public: + size_t size() const { + return (_stacktrace.size() >= skip_n_firsts()) + ? _stacktrace.size() - skip_n_firsts() + : 0; + } + Trace operator[](size_t idx) const { + if (idx >= size()) { + return Trace(); + } + return Trace(_stacktrace[idx + skip_n_firsts()], idx); + } + void *const *begin() const { + if (size()) { + return &_stacktrace[skip_n_firsts()]; + } + return nullptr; + } + +protected: + std::vector _stacktrace; +}; + +#if BACKWARD_HAS_UNWIND == 1 + +namespace details { + +template class Unwinder { +public: + size_t operator()(F &f, size_t depth) { + _f = &f; + _index = -1; + _depth = depth; + _Unwind_Backtrace(&this->backtrace_trampoline, this); + if (_index == -1) { + // _Unwind_Backtrace has failed to obtain any backtraces + return 0; + } else { + return static_cast(_index); + } + } + +private: + F *_f; + ssize_t _index; + size_t _depth; + + static _Unwind_Reason_Code backtrace_trampoline(_Unwind_Context *ctx, + void *self) { + return (static_cast(self))->backtrace(ctx); + } + + _Unwind_Reason_Code backtrace(_Unwind_Context *ctx) { + if (_index >= 0 && static_cast(_index) >= _depth) + return _URC_END_OF_STACK; + + int ip_before_instruction = 0; + uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction); + + if (!ip_before_instruction) { + // calculating 0-1 for unsigned, looks like a possible bug to sanitizers, + // so let's do it explicitly: + if (ip == 0) { + ip = std::numeric_limits::max(); // set it to 0xffff... (as + // from casting 0-1) + } else { + ip -= 1; // else just normally decrement it (no overflow/underflow will + // happen) + } + } + + if (_index >= 0) { // ignore first frame. + (*_f)(static_cast(_index), reinterpret_cast(ip)); + } + _index += 1; + return _URC_NO_REASON; + } +}; + +template size_t unwind(F f, size_t depth) { + Unwinder unwinder; + return unwinder(f, depth); +} + +} // namespace details + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_thread_info(); + set_context(context); + set_error_addr(error_addr); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth); + size_t trace_cnt = details::unwind(callback(*this), depth); + _stacktrace.resize(trace_cnt); + skip_n_firsts(0); + return size(); + } + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } + +private: + struct callback { + StackTraceImpl &self; + callback(StackTraceImpl &_self) : self(_self) {} + + void operator()(size_t idx, void *addr) { self._stacktrace[idx] = addr; } + }; +}; + +#elif BACKWARD_HAS_LIBUNWIND == 1 + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + __attribute__((noinline)) size_t load_here(size_t depth = 32, + void *_context = nullptr, + void *_error_addr = nullptr) { + set_context(_context); + set_error_addr(_error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + + int result = 0; + + unw_context_t ctx; + size_t index = 0; + + // Add the tail call. If the Instruction Pointer is the crash address it + // means we got a bad function pointer dereference, so we "unwind" the + // bad pointer manually by using the return address pointed to by the + // Stack Pointer as the Instruction Pointer and letting libunwind do + // the rest + + if (context()) { + ucontext_t *uctx = reinterpret_cast(context()); +#ifdef REG_RIP // x86_64 + if (uctx->uc_mcontext.gregs[REG_RIP] == + reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_RIP] = + *reinterpret_cast(uctx->uc_mcontext.gregs[REG_RSP]); + } + _stacktrace[index] = + reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); + ++index; + ctx = *reinterpret_cast(uctx); +#elif defined(REG_EIP) // x86_32 + if (uctx->uc_mcontext.gregs[REG_EIP] == + reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_EIP] = + *reinterpret_cast(uctx->uc_mcontext.gregs[REG_ESP]); + } + _stacktrace[index] = + reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); + ++index; + ctx = *reinterpret_cast(uctx); +#elif defined(__arm__) // clang libunwind/arm + // libunwind uses its own context type for ARM unwinding. + // Copy the registers from the signal handler's context so we can + // unwind + unw_getcontext(&ctx); + ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0; + ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1; + ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2; + ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3; + ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4; + ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5; + ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6; + ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7; + ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8; + ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9; + ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10; + ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp; + ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip; + ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp; + ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr; + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc; + + // If we have crashed in the PC use the LR instead, as this was + // a bad function dereference + if (reinterpret_cast(error_addr()) == + uctx->uc_mcontext.arm_pc) { + ctx.regs[UNW_ARM_R15] = + uctx->uc_mcontext.arm_lr - sizeof(unsigned long); + } + _stacktrace[index] = reinterpret_cast(ctx.regs[UNW_ARM_R15]); + ++index; +#elif defined(__aarch64__) // gcc libunwind/arm64 + unw_getcontext(&ctx); + // If the IP is the same as the crash address we have a bad function + // dereference The caller's address is pointed to by the link pointer, so + // we dereference that value and set it to be the next frame's IP. + if (uctx->uc_mcontext.pc == reinterpret_cast<__uint64_t>(error_addr())) { + uctx->uc_mcontext.pc = uctx->uc_mcontext.regs[UNW_TDEP_IP]; + } + + // 29 general purpose registers + for (int i = UNW_AARCH64_X0; i <= UNW_AARCH64_X28; i++) { + ctx.uc_mcontext.regs[i] = uctx->uc_mcontext.regs[i]; + } + ctx.uc_mcontext.sp = uctx->uc_mcontext.sp; + ctx.uc_mcontext.pc = uctx->uc_mcontext.pc; + ctx.uc_mcontext.fault_address = uctx->uc_mcontext.fault_address; + _stacktrace[index] = reinterpret_cast(ctx.uc_mcontext.pc); + ++index; +#elif defined(__APPLE__) && defined(__x86_64__) + unw_getcontext(&ctx); + // OS X's implementation of libunwind uses its own context object + // so we need to convert the passed context to libunwind's format + // (information about the data layout taken from unw_getcontext.s + // in Apple's libunwind source + ctx.data[0] = uctx->uc_mcontext->__ss.__rax; + ctx.data[1] = uctx->uc_mcontext->__ss.__rbx; + ctx.data[2] = uctx->uc_mcontext->__ss.__rcx; + ctx.data[3] = uctx->uc_mcontext->__ss.__rdx; + ctx.data[4] = uctx->uc_mcontext->__ss.__rdi; + ctx.data[5] = uctx->uc_mcontext->__ss.__rsi; + ctx.data[6] = uctx->uc_mcontext->__ss.__rbp; + ctx.data[7] = uctx->uc_mcontext->__ss.__rsp; + ctx.data[8] = uctx->uc_mcontext->__ss.__r8; + ctx.data[9] = uctx->uc_mcontext->__ss.__r9; + ctx.data[10] = uctx->uc_mcontext->__ss.__r10; + ctx.data[11] = uctx->uc_mcontext->__ss.__r11; + ctx.data[12] = uctx->uc_mcontext->__ss.__r12; + ctx.data[13] = uctx->uc_mcontext->__ss.__r13; + ctx.data[14] = uctx->uc_mcontext->__ss.__r14; + ctx.data[15] = uctx->uc_mcontext->__ss.__r15; + ctx.data[16] = uctx->uc_mcontext->__ss.__rip; + + // If the IP is the same as the crash address we have a bad function + // dereference The caller's address is pointed to by %rsp, so we + // dereference that value and set it to be the next frame's IP. + if (uctx->uc_mcontext->__ss.__rip == + reinterpret_cast<__uint64_t>(error_addr())) { + ctx.data[16] = + *reinterpret_cast<__uint64_t *>(uctx->uc_mcontext->__ss.__rsp); + } + _stacktrace[index] = reinterpret_cast(ctx.data[16]); + ++index; +#elif defined(__APPLE__) + unw_getcontext(&ctx) + // TODO: Convert the ucontext_t to libunwind's unw_context_t like + // we do in 64 bits + if (ctx.uc_mcontext->__ss.__eip == + reinterpret_cast(error_addr())) { + ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp; + } + _stacktrace[index] = + reinterpret_cast(ctx.uc_mcontext->__ss.__eip); + ++index; +#endif + } + + unw_cursor_t cursor; + if (context()) { +#if defined(UNW_INIT_SIGNAL_FRAME) + result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME); +#else + result = unw_init_local(&cursor, &ctx); +#endif + } else { + unw_getcontext(&ctx); + ; + result = unw_init_local(&cursor, &ctx); + } + + if (result != 0) + return 1; + + unw_word_t ip = 0; + + while (index <= depth && unw_step(&cursor) > 0) { + result = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (result == 0) { + _stacktrace[index] = reinterpret_cast(--ip); + ++index; + } + } + --index; + + _stacktrace.resize(index + 1); + skip_n_firsts(0); + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i]); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + +#elif defined(BACKWARD_HAS_BACKTRACE) + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + set_context(context); + set_error_addr(error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + size_t trace_cnt = backtrace(&_stacktrace[0], _stacktrace.size()); + _stacktrace.resize(trace_cnt); + skip_n_firsts(1); + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void *)((uintptr_t)_stacktrace[i] + 1); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + +#elif defined(BACKWARD_SYSTEM_WINDOWS) + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + // We have to load the machine type from the image info + // So we first initialize the resolver, and it tells us this info + void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; } + void set_context(CONTEXT *ctx) { ctx_ = ctx; } + void set_thread_handle(HANDLE handle) { thd_ = handle; } + + NOINLINE + size_t load_here(size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + set_context(static_cast(context)); + set_error_addr(error_addr); + CONTEXT localCtx; // used when no context is provided + + if (depth == 0) { + return 0; + } + + if (!ctx_) { + ctx_ = &localCtx; + RtlCaptureContext(ctx_); + } + + if (!thd_) { + thd_ = GetCurrentThread(); + } + + HANDLE process = GetCurrentProcess(); + + STACKFRAME64 s; + memset(&s, 0, sizeof(STACKFRAME64)); + + // TODO: 32 bit context capture + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrPC.Mode = AddrModeFlat; +#if defined(_M_X64) + s.AddrPC.Offset = ctx_->Rip; + s.AddrStack.Offset = ctx_->Rsp; + s.AddrFrame.Offset = ctx_->Rbp; +#elif defined(_M_ARM64) + s.AddrPC.Offset = ctx_->Pc; + s.AddrStack.Offset = ctx_->Sp; + s.AddrFrame.Offset = ctx_->Fp; +#elif defined(_M_ARM) + s.AddrPC.Offset = ctx_->Pc; + s.AddrStack.Offset = ctx_->Sp; + s.AddrFrame.Offset = ctx_->R11; +#else + s.AddrPC.Offset = ctx_->Eip; + s.AddrStack.Offset = ctx_->Esp; + s.AddrFrame.Offset = ctx_->Ebp; +#endif + + if (!machine_type_) { +#if defined(_M_X64) + machine_type_ = IMAGE_FILE_MACHINE_AMD64; +#elif defined(_M_ARM64) + machine_type_ = IMAGE_FILE_MACHINE_ARM64; +#elif defined(_M_ARM) + machine_type_ = IMAGE_FILE_MACHINE_ARMNT; +#else + machine_type_ = IMAGE_FILE_MACHINE_I386; +#endif + } + + for (;;) { + // NOTE: this only works if PDBs are already loaded! + SetLastError(0); + if (!StackWalk64(machine_type_, process, thd_, &s, ctx_, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + break; + + if (s.AddrReturn.Offset == 0) + break; + + _stacktrace.push_back(reinterpret_cast(s.AddrPC.Offset)); + + if (size() >= depth) + break; + } + + return size(); + } + + size_t load_from(void *addr, size_t depth = 32, void *context = nullptr, + void *error_addr = nullptr) { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } + +private: + DWORD machine_type_ = 0; + HANDLE thd_ = 0; + CONTEXT *ctx_ = nullptr; +}; + +#endif + +class StackTrace : public StackTraceImpl {}; + +/*************** TRACE RESOLVER ***************/ + +class TraceResolverImplBase { +public: + virtual ~TraceResolverImplBase() {} + + virtual void load_addresses(void *const*addresses, int address_count) { + (void)addresses; + (void)address_count; + } + + template void load_stacktrace(ST &st) { + load_addresses(st.begin(), static_cast(st.size())); + } + + virtual ResolvedTrace resolve(ResolvedTrace t) { return t; } + +protected: + std::string demangle(const char *funcname) { + return _demangler.demangle(funcname); + } + +private: + details::demangler _demangler; +}; + +template class TraceResolverImpl; + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +template <> class TraceResolverImpl + : public TraceResolverImplBase {}; + +#endif + +#ifdef BACKWARD_SYSTEM_LINUX + +class TraceResolverLinuxBase : public TraceResolverImplBase { +public: + TraceResolverLinuxBase() + : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {} + std::string resolve_exec_path(Dl_info &symbol_info) const { + // mutates symbol_info.dli_fname to be filename to open and returns filename + // to display + if (symbol_info.dli_fname == argv0_) { + // dladdr returns argv[0] in dli_fname for symbols contained in + // the main executable, which is not a valid path if the + // executable was found by a search of the PATH environment + // variable; In that case, we actually open /proc/self/exe, which + // is always the actual executable (even if it was deleted/replaced!) + // but display the path that /proc/self/exe links to. + // However, this right away reduces probability of successful symbol + // resolution, because libbfd may try to find *.debug files in the + // same dir, in case symbols are stripped. As a result, it may try + // to find a file /proc/self/.debug, which obviously does + // not exist. /proc/self/exe is a last resort. First load attempt + // should go for the original executable file path. + symbol_info.dli_fname = "/proc/self/exe"; + return exec_path_; + } else { + return symbol_info.dli_fname; + } + } + +private: + std::string argv0_; + std::string exec_path_; + + static std::string get_argv0() { + std::string argv0; + std::ifstream ifs("/proc/self/cmdline"); + std::getline(ifs, argv0, '\0'); + return argv0; + } + + static std::string read_symlink(std::string const &symlink_path) { + std::string path; + path.resize(100); + + while (true) { + ssize_t len = + ::readlink(symlink_path.c_str(), &*path.begin(), path.size()); + if (len < 0) { + return ""; + } + if (static_cast(len) == path.size()) { + path.resize(path.size() * 2); + } else { + path.resize(static_cast(len)); + break; + } + } + + return path; + } +}; + +template class TraceResolverLinuxImpl; + +#if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + void load_addresses(void *const*addresses, int address_count) override { + if (address_count == 0) { + return; + } + _symbols.reset(backtrace_symbols(addresses, address_count)); + } + + ResolvedTrace resolve(ResolvedTrace trace) override { + char *filename = _symbols[trace.idx]; + char *funcname = filename; + while (*funcname && *funcname != '(') { + funcname += 1; + } + trace.object_filename.assign(filename, + funcname); // ok even if funcname is the ending + // \0 (then we assign entire string) + + if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) + funcname += 1; + char *funcname_end = funcname; + while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { + funcname_end += 1; + } + *funcname_end = '\0'; + trace.object_function = this->demangle(funcname); + trace.source.function = trace.object_function; // we cannot do better. + } + return trace; + } + +private: + details::handle _symbols; +}; + +#endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +#if BACKWARD_HAS_BFD == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _bfd_loaded(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + Dl_info symbol_info; + + // trace.addr is a virtual address in memory pointing to some code. + // Let's try to find from which loaded object it comes from. + // The loaded object can be yourself btw. + if (!dladdr(trace.addr, &symbol_info)) { + return trace; // dat broken trace... + } + + // Now we get in symbol_info: + // .dli_fname: + // pathname of the shared object that contains the address. + // .dli_fbase: + // where the object is loaded in memory. + // .dli_sname: + // the name of the nearest symbol to trace.addr, we expect a + // function name. + // .dli_saddr: + // the exact address corresponding to .dli_sname. + + if (symbol_info.dli_sname) { + trace.object_function = demangle(symbol_info.dli_sname); + } + + if (!symbol_info.dli_fname) { + return trace; + } + + trace.object_filename = resolve_exec_path(symbol_info); + bfd_fileobject *fobj; + // Before rushing to resolution need to ensure the executable + // file still can be used. For that compare inode numbers of + // what is stored by the executable's file path, and in the + // dli_fname, which not necessarily equals to the executable. + // It can be a shared library, or /proc/self/exe, and in the + // latter case has drawbacks. See the exec path resolution for + // details. In short - the dli object should be used only as + // the last resort. + // If inode numbers are equal, it is known dli_fname and the + // executable file are the same. This is guaranteed by Linux, + // because if the executable file is changed/deleted, it will + // be done in a new inode. The old file will be preserved in + // /proc/self/exe, and may even have inode 0. The latter can + // happen if the inode was actually reused, and the file was + // kept only in the main memory. + // + struct stat obj_stat; + struct stat dli_stat; + if (stat(trace.object_filename.c_str(), &obj_stat) == 0 && + stat(symbol_info.dli_fname, &dli_stat) == 0 && + obj_stat.st_ino == dli_stat.st_ino) { + // The executable file, and the shared object containing the + // address are the same file. Safe to use the original path. + // this is preferable. Libbfd will search for stripped debug + // symbols in the same directory. + fobj = load_object_with_bfd(trace.object_filename); + } else{ + // The original object file was *deleted*! The only hope is + // that the debug symbols are either inside the shared + // object file, or are in the same directory, and this is + // not /proc/self/exe. + fobj = nullptr; + } + if (fobj == nullptr || !fobj->handle) { + fobj = load_object_with_bfd(symbol_info.dli_fname); + if (!fobj->handle) { + return trace; + } + } + + find_sym_result *details_selected; // to be filled. + + // trace.addr is the next instruction to be executed after returning + // from the nested stack frame. In C++ this usually relate to the next + // statement right after the function call that leaded to a new stack + // frame. This is not usually what you want to see when printing out a + // stacktrace... + find_sym_result details_call_site = + find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); + details_selected = &details_call_site; + +#if BACKWARD_HAS_UNWIND == 0 + // ...this is why we also try to resolve the symbol that is right + // before the return address. If we are lucky enough, we will get the + // line of the function that was called. But if the code is optimized, + // we might get something absolutely not related since the compiler + // can reschedule the return address with inline functions and + // tail-call optimization (among other things that I don't even know + // or cannot even dream about with my tiny limited brain). + find_sym_result details_adjusted_call_site = find_symbol_details( + fobj, (void *)(uintptr_t(trace.addr) - 1), symbol_info.dli_fbase); + + // In debug mode, we should always get the right thing(TM). + if (details_call_site.found && details_adjusted_call_site.found) { + // Ok, we assume that details_adjusted_call_site is a better estimation. + details_selected = &details_adjusted_call_site; + trace.addr = (void *)(uintptr_t(trace.addr) - 1); + } + + if (details_selected == &details_call_site && details_call_site.found) { + // we have to re-resolve the symbol in order to reset some + // internal state in BFD... so we can call backtrace_inliners + // thereafter... + details_call_site = + find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); + } +#endif // BACKWARD_HAS_UNWIND + + if (details_selected->found) { + if (details_selected->filename) { + trace.source.filename = details_selected->filename; + } + trace.source.line = details_selected->line; + + if (details_selected->funcname) { + // this time we get the name of the function where the code is + // located, instead of the function were the address is + // located. In short, if the code was inlined, we get the + // function corresponding to the code. Else we already got in + // trace.function. + trace.source.function = demangle(details_selected->funcname); + + if (!symbol_info.dli_sname) { + // for the case dladdr failed to find the symbol name of + // the function, we might as well try to put something + // here. + trace.object_function = trace.source.function; + } + } + + // Maybe the source of the trace got inlined inside the function + // (trace.source.function). Let's see if we can get all the inlined + // calls along the way up to the initial call site. + trace.inliners = backtrace_inliners(fobj, *details_selected); + +#if 0 + if (trace.inliners.size() == 0) { + // Maybe the trace was not inlined... or maybe it was and we + // are lacking the debug information. Let's try to make the + // world better and see if we can get the line number of the + // function (trace.source.function) now. + // + // We will get the location of where the function start (to be + // exact: the first instruction that really start the + // function), not where the name of the function is defined. + // This can be quite far away from the name of the function + // btw. + // + // If the source of the function is the same as the source of + // the trace, we cannot say if the trace was really inlined or + // not. However, if the filename of the source is different + // between the function and the trace... we can declare it as + // an inliner. This is not 100% accurate, but better than + // nothing. + + if (symbol_info.dli_saddr) { + find_sym_result details = find_symbol_details(fobj, + symbol_info.dli_saddr, + symbol_info.dli_fbase); + + if (details.found) { + ResolvedTrace::SourceLoc diy_inliner; + diy_inliner.line = details.line; + if (details.filename) { + diy_inliner.filename = details.filename; + } + if (details.funcname) { + diy_inliner.function = demangle(details.funcname); + } else { + diy_inliner.function = trace.source.function; + } + if (diy_inliner != trace.source) { + trace.inliners.push_back(diy_inliner); + } + } + } + } +#endif + } + + return trace; + } + +private: + bool _bfd_loaded; + + typedef details::handle > + bfd_handle_t; + + typedef details::handle bfd_symtab_t; + + struct bfd_fileobject { + bfd_handle_t handle; + bfd_vma base_addr; + bfd_symtab_t symtab; + bfd_symtab_t dynamic_symtab; + }; + + typedef details::hashtable::type fobj_bfd_map_t; + fobj_bfd_map_t _fobj_bfd_map; + + bfd_fileobject *load_object_with_bfd(const std::string &filename_object) { + using namespace details; + + if (!_bfd_loaded) { + using namespace details; + bfd_init(); + _bfd_loaded = true; + } + + fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); + if (it != _fobj_bfd_map.end()) { + return &it->second; + } + + // this new object is empty for now. + bfd_fileobject *r = &_fobj_bfd_map[filename_object]; + + // we do the work temporary in this one; + bfd_handle_t bfd_handle; + + int fd = open(filename_object.c_str(), O_RDONLY); + bfd_handle.reset(bfd_fdopenr(filename_object.c_str(), "default", fd)); + if (!bfd_handle) { + close(fd); + return r; + } + + if (!bfd_check_format(bfd_handle.get(), bfd_object)) { + return r; // not an object? You lose. + } + + if ((bfd_get_file_flags(bfd_handle.get()) & HAS_SYMS) == 0) { + return r; // that's what happen when you forget to compile in debug. + } + + ssize_t symtab_storage_size = bfd_get_symtab_upper_bound(bfd_handle.get()); + + ssize_t dyn_symtab_storage_size = + bfd_get_dynamic_symtab_upper_bound(bfd_handle.get()); + + if (symtab_storage_size <= 0 && dyn_symtab_storage_size <= 0) { + return r; // weird, is the file is corrupted? + } + + bfd_symtab_t symtab, dynamic_symtab; + ssize_t symcount = 0, dyn_symcount = 0; + + if (symtab_storage_size > 0) { + symtab.reset(static_cast( + malloc(static_cast(symtab_storage_size)))); + symcount = bfd_canonicalize_symtab(bfd_handle.get(), symtab.get()); + } + + if (dyn_symtab_storage_size > 0) { + dynamic_symtab.reset(static_cast( + malloc(static_cast(dyn_symtab_storage_size)))); + dyn_symcount = bfd_canonicalize_dynamic_symtab(bfd_handle.get(), + dynamic_symtab.get()); + } + + if (symcount <= 0 && dyn_symcount <= 0) { + return r; // damned, that's a stripped file that you got there! + } + + r->handle = move(bfd_handle); + r->symtab = move(symtab); + r->dynamic_symtab = move(dynamic_symtab); + return r; + } + + struct find_sym_result { + bool found; + const char *filename; + const char *funcname; + unsigned int line; + }; + + struct find_sym_context { + TraceResolverLinuxImpl *self; + bfd_fileobject *fobj; + void *addr; + void *base_addr; + find_sym_result result; + }; + + find_sym_result find_symbol_details(bfd_fileobject *fobj, void *addr, + void *base_addr) { + find_sym_context context; + context.self = this; + context.fobj = fobj; + context.addr = addr; + context.base_addr = base_addr; + context.result.found = false; + bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline, + static_cast(&context)); + return context.result; + } + + static void find_in_section_trampoline(bfd *, asection *section, void *data) { + find_sym_context *context = static_cast(data); + context->self->find_in_section( + reinterpret_cast(context->addr), + reinterpret_cast(context->base_addr), context->fobj, section, + context->result); + } + + void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject *fobj, + asection *section, find_sym_result &result) { + if (result.found) + return; + +#ifdef bfd_get_section_flags + if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0) +#else + if ((bfd_section_flags(section) & SEC_ALLOC) == 0) +#endif + return; // a debug section is never loaded automatically. + +#ifdef bfd_get_section_vma + bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section); +#else + bfd_vma sec_addr = bfd_section_vma(section); +#endif +#ifdef bfd_get_section_size + bfd_size_type size = bfd_get_section_size(section); +#else + bfd_size_type size = bfd_section_size(section); +#endif + + // are we in the boundaries of the section? + if (addr < sec_addr || addr >= sec_addr + size) { + addr -= base_addr; // oops, a relocated object, lets try again... + if (addr < sec_addr || addr >= sec_addr + size) { + return; + } + } + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + if (!result.found && fobj->symtab) { + result.found = bfd_find_nearest_line( + fobj->handle.get(), section, fobj->symtab.get(), addr - sec_addr, + &result.filename, &result.funcname, &result.line); + } + + if (!result.found && fobj->dynamic_symtab) { + result.found = bfd_find_nearest_line( + fobj->handle.get(), section, fobj->dynamic_symtab.get(), + addr - sec_addr, &result.filename, &result.funcname, &result.line); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + } + + ResolvedTrace::source_locs_t + backtrace_inliners(bfd_fileobject *fobj, find_sym_result previous_result) { + // This function can be called ONLY after a SUCCESSFUL call to + // find_symbol_details. The state is global to the bfd_handle. + ResolvedTrace::source_locs_t results; + while (previous_result.found) { + find_sym_result result; + result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename, + &result.funcname, &result.line); + + if (result + .found) /* and not ( + cstrings_eq(previous_result.filename, + result.filename) and + cstrings_eq(previous_result.funcname, result.funcname) + and result.line == previous_result.line + )) */ + { + ResolvedTrace::SourceLoc src_loc; + src_loc.line = result.line; + if (result.filename) { + src_loc.filename = result.filename; + } + if (result.funcname) { + src_loc.function = demangle(result.funcname); + } + results.push_back(src_loc); + } + previous_result = result; + } + return results; + } + + bool cstrings_eq(const char *a, const char *b) { + if (!a || !b) { + return false; + } + return strcmp(a, b) == 0; + } +}; +#endif // BACKWARD_HAS_BFD == 1 + +#if BACKWARD_HAS_DW == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + using namespace details; + + Dwarf_Addr trace_addr = reinterpret_cast(trace.addr); + + if (!_dwfl_handle_initialized) { + // initialize dwfl... + _dwfl_cb.reset(new Dwfl_Callbacks); + _dwfl_cb->find_elf = &dwfl_linux_proc_find_elf; + _dwfl_cb->find_debuginfo = &dwfl_standard_find_debuginfo; + _dwfl_cb->debuginfo_path = 0; + + _dwfl_handle.reset(dwfl_begin(_dwfl_cb.get())); + _dwfl_handle_initialized = true; + + if (!_dwfl_handle) { + return trace; + } + + // ...from the current process. + dwfl_report_begin(_dwfl_handle.get()); + int r = dwfl_linux_proc_report(_dwfl_handle.get(), getpid()); + dwfl_report_end(_dwfl_handle.get(), NULL, NULL); + if (r < 0) { + return trace; + } + } + + if (!_dwfl_handle) { + return trace; + } + + // find the module (binary object) that contains the trace's address. + // This is not using any debug information, but the addresses ranges of + // all the currently loaded binary object. + Dwfl_Module *mod = dwfl_addrmodule(_dwfl_handle.get(), trace_addr); + if (mod) { + // now that we found it, lets get the name of it, this will be the + // full path to the running binary or one of the loaded library. + const char *module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0); + if (module_name) { + trace.object_filename = module_name; + } + // We also look after the name of the symbol, equal or before this + // address. This is found by walking the symtab. We should get the + // symbol corresponding to the function (mangled) containing the + // address. If the code corresponding to the address was inlined, + // this is the name of the out-most inliner function. + const char *sym_name = dwfl_module_addrname(mod, trace_addr); + if (sym_name) { + trace.object_function = demangle(sym_name); + } + } + + // now let's get serious, and find out the source location (file and + // line number) of the address. + + // This function will look in .debug_aranges for the address and map it + // to the location of the compilation unit DIE in .debug_info and + // return it. + Dwarf_Addr mod_bias = 0; + Dwarf_Die *cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias); + +#if 1 + if (!cudie) { + // Sadly clang does not generate the section .debug_aranges, thus + // dwfl_module_addrdie will fail early. Clang doesn't either set + // the lowpc/highpc/range info for every compilation unit. + // + // So in order to save the world: + // for every compilation unit, we will iterate over every single + // DIEs. Normally functions should have a lowpc/highpc/range, which + // we will use to infer the compilation unit. + + // note that this is probably badly inefficient. + while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias))) { + Dwarf_Die die_mem; + Dwarf_Die *fundie = + find_fundie_by_pc(cudie, trace_addr - mod_bias, &die_mem); + if (fundie) { + break; + } + } + } +#endif + +//#define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE +#ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE + if (!cudie) { + // If it's still not enough, lets dive deeper in the shit, and try + // to save the world again: for every compilation unit, we will + // load the corresponding .debug_line section, and see if we can + // find our address in it. + + Dwarf_Addr cfi_bias; + Dwarf_CFI *cfi_cache = dwfl_module_eh_cfi(mod, &cfi_bias); + + Dwarf_Addr bias; + while ((cudie = dwfl_module_nextcu(mod, cudie, &bias))) { + if (dwarf_getsrc_die(cudie, trace_addr - bias)) { + + // ...but if we get a match, it might be a false positive + // because our (address - bias) might as well be valid in a + // different compilation unit. So we throw our last card on + // the table and lookup for the address into the .eh_frame + // section. + + handle frame; + dwarf_cfi_addrframe(cfi_cache, trace_addr - cfi_bias, &frame); + if (frame) { + break; + } + } + } + } +#endif + + if (!cudie) { + return trace; // this time we lost the game :/ + } + + // Now that we have a compilation unit DIE, this function will be able + // to load the corresponding section in .debug_line (if not already + // loaded) and hopefully find the source location mapped to our + // address. + Dwarf_Line *srcloc = dwarf_getsrc_die(cudie, trace_addr - mod_bias); + + if (srcloc) { + const char *srcfile = dwarf_linesrc(srcloc, 0, 0); + if (srcfile) { + trace.source.filename = srcfile; + } + int line = 0, col = 0; + dwarf_lineno(srcloc, &line); + dwarf_linecol(srcloc, &col); + trace.source.line = static_cast(line); + trace.source.col = static_cast(col); + } + + deep_first_search_by_pc(cudie, trace_addr - mod_bias, + inliners_search_cb(trace)); + if (trace.source.function.size() == 0) { + // fallback. + trace.source.function = trace.object_function; + } + + return trace; + } + +private: + typedef details::handle > + dwfl_handle_t; + details::handle > + _dwfl_cb; + dwfl_handle_t _dwfl_handle; + bool _dwfl_handle_initialized; + + // defined here because in C++98, template function cannot take locally + // defined types... grrr. + struct inliners_search_cb { + void operator()(Dwarf_Die *die) { + switch (dwarf_tag(die)) { + const char *name; + case DW_TAG_subprogram: + if ((name = dwarf_diename(die))) { + trace.source.function = name; + } + break; + + case DW_TAG_inlined_subroutine: + ResolvedTrace::SourceLoc sloc; + Dwarf_Attribute attr_mem; + + if ((name = dwarf_diename(die))) { + sloc.function = name; + } + if ((name = die_call_file(die))) { + sloc.filename = name; + } + + Dwarf_Word line = 0, col = 0; + dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr_mem), &line); + dwarf_formudata(dwarf_attr(die, DW_AT_call_column, &attr_mem), &col); + sloc.line = static_cast(line); + sloc.col = static_cast(col); + + trace.inliners.push_back(sloc); + break; + }; + } + ResolvedTrace &trace; + inliners_search_cb(ResolvedTrace &t) : trace(t) {} + }; + + static bool die_has_pc(Dwarf_Die *die, Dwarf_Addr pc) { + Dwarf_Addr low, high; + + // continuous range + if (dwarf_hasattr(die, DW_AT_low_pc) && dwarf_hasattr(die, DW_AT_high_pc)) { + if (dwarf_lowpc(die, &low) != 0) { + return false; + } + if (dwarf_highpc(die, &high) != 0) { + Dwarf_Attribute attr_mem; + Dwarf_Attribute *attr = dwarf_attr(die, DW_AT_high_pc, &attr_mem); + Dwarf_Word value; + if (dwarf_formudata(attr, &value) != 0) { + return false; + } + high = low + value; + } + return pc >= low && pc < high; + } + + // non-continuous range. + Dwarf_Addr base; + ptrdiff_t offset = 0; + while ((offset = dwarf_ranges(die, offset, &base, &low, &high)) > 0) { + if (pc >= low && pc < high) { + return true; + } + } + return false; + } + + static Dwarf_Die *find_fundie_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, + Dwarf_Die *result) { + if (dwarf_child(parent_die, result) != 0) { + return 0; + } + + Dwarf_Die *die = result; + do { + switch (dwarf_tag(die)) { + case DW_TAG_subprogram: + case DW_TAG_inlined_subroutine: + if (die_has_pc(die, pc)) { + return result; + } + }; + bool declaration = false; + Dwarf_Attribute attr_mem; + dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), + &declaration); + if (!declaration) { + // let's be curious and look deeper in the tree, + // function are not necessarily at the first level, but + // might be nested inside a namespace, structure etc. + Dwarf_Die die_mem; + Dwarf_Die *indie = find_fundie_by_pc(die, pc, &die_mem); + if (indie) { + *result = die_mem; + return result; + } + } + } while (dwarf_siblingof(die, result) == 0); + return 0; + } + + template + static bool deep_first_search_by_pc(Dwarf_Die *parent_die, Dwarf_Addr pc, + CB cb) { + Dwarf_Die die_mem; + if (dwarf_child(parent_die, &die_mem) != 0) { + return false; + } + + bool branch_has_pc = false; + Dwarf_Die *die = &die_mem; + do { + bool declaration = false; + Dwarf_Attribute attr_mem; + dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), + &declaration); + if (!declaration) { + // let's be curious and look deeper in the tree, function are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + branch_has_pc = deep_first_search_by_pc(die, pc, cb); + } + if (!branch_has_pc) { + branch_has_pc = die_has_pc(die, pc); + } + if (branch_has_pc) { + cb(die); + } + } while (dwarf_siblingof(die, &die_mem) == 0); + return branch_has_pc; + } + + static const char *die_call_file(Dwarf_Die *die) { + Dwarf_Attribute attr_mem; + Dwarf_Word file_idx = 0; + + dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); + + if (file_idx == 0) { + return 0; + } + + Dwarf_Die die_mem; + Dwarf_Die *cudie = dwarf_diecu(die, &die_mem, 0, 0); + if (!cudie) { + return 0; + } + + Dwarf_Files *files = 0; + size_t nfiles; + dwarf_getsrcfiles(cudie, &files, &nfiles); + if (!files) { + return 0; + } + + return dwarf_filesrc(files, file_idx, 0, 0); + } +}; +#endif // BACKWARD_HAS_DW == 1 + +#if BACKWARD_HAS_DWARF == 1 + +template <> +class TraceResolverLinuxImpl + : public TraceResolverLinuxBase { +public: + TraceResolverLinuxImpl() : _dwarf_loaded(false) {} + + ResolvedTrace resolve(ResolvedTrace trace) override { + // trace.addr is a virtual address in memory pointing to some code. + // Let's try to find from which loaded object it comes from. + // The loaded object can be yourself btw. + + Dl_info symbol_info; + int dladdr_result = 0; +#if defined(__GLIBC__) + link_map *link_map; + // We request the link map so we can get information about offsets + dladdr_result = + dladdr1(trace.addr, &symbol_info, reinterpret_cast(&link_map), + RTLD_DL_LINKMAP); +#else + // Android doesn't have dladdr1. Don't use the linker map. + dladdr_result = dladdr(trace.addr, &symbol_info); +#endif + if (!dladdr_result) { + return trace; // dat broken trace... + } + + // Now we get in symbol_info: + // .dli_fname: + // pathname of the shared object that contains the address. + // .dli_fbase: + // where the object is loaded in memory. + // .dli_sname: + // the name of the nearest symbol to trace.addr, we expect a + // function name. + // .dli_saddr: + // the exact address corresponding to .dli_sname. + // + // And in link_map: + // .l_addr: + // difference between the address in the ELF file and the address + // in memory + // l_name: + // absolute pathname where the object was found + + if (symbol_info.dli_sname) { + trace.object_function = demangle(symbol_info.dli_sname); + } + + if (!symbol_info.dli_fname) { + return trace; + } + + trace.object_filename = resolve_exec_path(symbol_info); + dwarf_fileobject &fobj = load_object_with_dwarf(symbol_info.dli_fname); + if (!fobj.dwarf_handle) { + return trace; // sad, we couldn't load the object :( + } + +#if defined(__GLIBC__) + // Convert the address to a module relative one by looking at + // the module's loading address in the link map + Dwarf_Addr address = reinterpret_cast(trace.addr) - + reinterpret_cast(link_map->l_addr); +#else + Dwarf_Addr address = reinterpret_cast(trace.addr); +#endif + + if (trace.object_function.empty()) { + symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address); + + if (it != fobj.symbol_cache.end()) { + if (it->first != address) { + if (it != fobj.symbol_cache.begin()) { + --it; + } + } + trace.object_function = demangle(it->second.c_str()); + } + } + + // Get the Compilation Unit DIE for the address + Dwarf_Die die = find_die(fobj, address); + + if (!die) { + return trace; // this time we lost the game :/ + } + + // libdwarf doesn't give us direct access to its objects, it always + // allocates a copy for the caller. We keep that copy alive in a cache + // and we deallocate it later when it's no longer required. + die_cache_entry &die_object = get_die_cache(fobj, die); + if (die_object.isEmpty()) + return trace; // We have no line section for this DIE + + die_linemap_t::iterator it = die_object.line_section.lower_bound(address); + + if (it != die_object.line_section.end()) { + if (it->first != address) { + if (it == die_object.line_section.begin()) { + // If we are on the first item of the line section + // but the address does not match it means that + // the address is below the range of the DIE. Give up. + return trace; + } else { + --it; + } + } + } else { + return trace; // We didn't find the address. + } + + // Get the Dwarf_Line that the address points to and call libdwarf + // to get source file, line and column info. + Dwarf_Line line = die_object.line_buffer[it->second]; + Dwarf_Error error = DW_DLE_NE; + + char *filename; + if (dwarf_linesrc(line, &filename, &error) == DW_DLV_OK) { + trace.source.filename = std::string(filename); + dwarf_dealloc(fobj.dwarf_handle.get(), filename, DW_DLA_STRING); + } + + Dwarf_Unsigned number = 0; + if (dwarf_lineno(line, &number, &error) == DW_DLV_OK) { + trace.source.line = number; + } else { + trace.source.line = 0; + } + + if (dwarf_lineoff_b(line, &number, &error) == DW_DLV_OK) { + trace.source.col = number; + } else { + trace.source.col = 0; + } + + std::vector namespace_stack; + deep_first_search_by_pc(fobj, die, address, namespace_stack, + inliners_search_cb(trace, fobj, die)); + + dwarf_dealloc(fobj.dwarf_handle.get(), die, DW_DLA_DIE); + + return trace; + } + +public: + static int close_dwarf(Dwarf_Debug dwarf) { + return dwarf_finish(dwarf, NULL); + } + +private: + bool _dwarf_loaded; + + typedef details::handle > + dwarf_file_t; + + typedef details::handle > + dwarf_elf_t; + + typedef details::handle > + dwarf_handle_t; + + typedef std::map die_linemap_t; + + typedef std::map die_specmap_t; + + struct die_cache_entry { + die_specmap_t spec_section; + die_linemap_t line_section; + Dwarf_Line *line_buffer; + Dwarf_Signed line_count; + Dwarf_Line_Context line_context; + + inline bool isEmpty() { + return line_buffer == NULL || line_count == 0 || line_context == NULL || + line_section.empty(); + } + + die_cache_entry() : line_buffer(0), line_count(0), line_context(0) {} + + ~die_cache_entry() { + if (line_context) { + dwarf_srclines_dealloc_b(line_context); + } + } + }; + + typedef std::map die_cache_t; + + typedef std::map symbol_cache_t; + + struct dwarf_fileobject { + dwarf_file_t file_handle; + dwarf_elf_t elf_handle; + dwarf_handle_t dwarf_handle; + symbol_cache_t symbol_cache; + + // Die cache + die_cache_t die_cache; + die_cache_entry *current_cu; + }; + + typedef details::hashtable::type + fobj_dwarf_map_t; + fobj_dwarf_map_t _fobj_dwarf_map; + + static bool cstrings_eq(const char *a, const char *b) { + if (!a || !b) { + return false; + } + return strcmp(a, b) == 0; + } + + dwarf_fileobject &load_object_with_dwarf(const std::string &filename_object) { + + if (!_dwarf_loaded) { + // Set the ELF library operating version + // If that fails there's nothing we can do + _dwarf_loaded = elf_version(EV_CURRENT) != EV_NONE; + } + + fobj_dwarf_map_t::iterator it = _fobj_dwarf_map.find(filename_object); + if (it != _fobj_dwarf_map.end()) { + return it->second; + } + + // this new object is empty for now + dwarf_fileobject &r = _fobj_dwarf_map[filename_object]; + + dwarf_file_t file_handle; + file_handle.reset(open(filename_object.c_str(), O_RDONLY)); + if (file_handle.get() < 0) { + return r; + } + + // Try to get an ELF handle. We need to read the ELF sections + // because we want to see if there is a .gnu_debuglink section + // that points to a split debug file + dwarf_elf_t elf_handle; + elf_handle.reset(elf_begin(file_handle.get(), ELF_C_READ, NULL)); + if (!elf_handle) { + return r; + } + + const char *e_ident = elf_getident(elf_handle.get(), 0); + if (!e_ident) { + return r; + } + + // Get the number of sections + // We use the new APIs as elf_getshnum is deprecated + size_t shdrnum = 0; + if (elf_getshdrnum(elf_handle.get(), &shdrnum) == -1) { + return r; + } + + // Get the index to the string section + size_t shdrstrndx = 0; + if (elf_getshdrstrndx(elf_handle.get(), &shdrstrndx) == -1) { + return r; + } + + std::string debuglink; + // Iterate through the ELF sections to try to get a gnu_debuglink + // note and also to cache the symbol table. + // We go the preprocessor way to avoid having to create templated + // classes or using gelf (which might throw a compiler error if 64 bit + // is not supported +#define ELF_GET_DATA(ARCH) \ + Elf_Scn *elf_section = 0; \ + Elf_Data *elf_data = 0; \ + Elf##ARCH##_Shdr *section_header = 0; \ + Elf_Scn *symbol_section = 0; \ + size_t symbol_count = 0; \ + size_t symbol_strings = 0; \ + Elf##ARCH##_Sym *symbol = 0; \ + const char *section_name = 0; \ + \ + while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ + section_header = elf##ARCH##_getshdr(elf_section); \ + if (section_header == NULL) { \ + return r; \ + } \ + \ + if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, \ + section_header->sh_name)) == NULL) { \ + return r; \ + } \ + \ + if (cstrings_eq(section_name, ".gnu_debuglink")) { \ + elf_data = elf_getdata(elf_section, NULL); \ + if (elf_data && elf_data->d_size > 0) { \ + debuglink = \ + std::string(reinterpret_cast(elf_data->d_buf)); \ + } \ + } \ + \ + switch (section_header->sh_type) { \ + case SHT_SYMTAB: \ + symbol_section = elf_section; \ + symbol_count = section_header->sh_size / section_header->sh_entsize; \ + symbol_strings = section_header->sh_link; \ + break; \ + \ + /* We use .dynsyms as a last resort, we prefer .symtab */ \ + case SHT_DYNSYM: \ + if (!symbol_section) { \ + symbol_section = elf_section; \ + symbol_count = section_header->sh_size / section_header->sh_entsize; \ + symbol_strings = section_header->sh_link; \ + } \ + break; \ + } \ + } \ + \ + if (symbol_section && symbol_count && symbol_strings) { \ + elf_data = elf_getdata(symbol_section, NULL); \ + symbol = reinterpret_cast(elf_data->d_buf); \ + for (size_t i = 0; i < symbol_count; ++i) { \ + int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ + if (type == STT_FUNC && symbol->st_value > 0) { \ + r.symbol_cache[symbol->st_value] = std::string( \ + elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ + } \ + ++symbol; \ + } \ + } + + if (e_ident[EI_CLASS] == ELFCLASS32) { + ELF_GET_DATA(32) + } else if (e_ident[EI_CLASS] == ELFCLASS64) { + // libelf might have been built without 64 bit support +#if __LIBELF64 + ELF_GET_DATA(64) +#endif + } + + if (!debuglink.empty()) { + // We have a debuglink section! Open an elf instance on that + // file instead. If we can't open the file, then return + // the elf handle we had already opened. + dwarf_file_t debuglink_file; + debuglink_file.reset(open(debuglink.c_str(), O_RDONLY)); + if (debuglink_file.get() > 0) { + dwarf_elf_t debuglink_elf; + debuglink_elf.reset(elf_begin(debuglink_file.get(), ELF_C_READ, NULL)); + + // If we have a valid elf handle, return the new elf handle + // and file handle and discard the original ones + if (debuglink_elf) { + elf_handle = move(debuglink_elf); + file_handle = move(debuglink_file); + } + } + } + + // Ok, we have a valid ELF handle, let's try to get debug symbols + Dwarf_Debug dwarf_debug; + Dwarf_Error error = DW_DLE_NE; + dwarf_handle_t dwarf_handle; + + int dwarf_result = dwarf_elf_init(elf_handle.get(), DW_DLC_READ, NULL, NULL, + &dwarf_debug, &error); + + // We don't do any special handling for DW_DLV_NO_ENTRY specially. + // If we get an error, or the file doesn't have debug information + // we just return. + if (dwarf_result != DW_DLV_OK) { + return r; + } + + dwarf_handle.reset(dwarf_debug); + + r.file_handle = move(file_handle); + r.elf_handle = move(elf_handle); + r.dwarf_handle = move(dwarf_handle); + + return r; + } + + die_cache_entry &get_die_cache(dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Error error = DW_DLE_NE; + + // Get the die offset, we use it as the cache key + Dwarf_Off die_offset; + if (dwarf_dieoffset(die, &die_offset, &error) != DW_DLV_OK) { + die_offset = 0; + } + + die_cache_t::iterator it = fobj.die_cache.find(die_offset); + + if (it != fobj.die_cache.end()) { + fobj.current_cu = &it->second; + return it->second; + } + + die_cache_entry &de = fobj.die_cache[die_offset]; + fobj.current_cu = &de; + + Dwarf_Addr line_addr; + Dwarf_Small table_count; + + // The addresses in the line section are not fully sorted (they might + // be sorted by block of code belonging to the same file), which makes + // it necessary to do so before searching is possible. + // + // As libdwarf allocates a copy of everything, let's get the contents + // of the line section and keep it around. We also create a map of + // program counter to line table indices so we can search by address + // and get the line buffer index. + // + // To make things more difficult, the same address can span more than + // one line, so we need to keep the index pointing to the first line + // by using insert instead of the map's [ operator. + + // Get the line context for the DIE + if (dwarf_srclines_b(die, 0, &table_count, &de.line_context, &error) == + DW_DLV_OK) { + // Get the source lines for this line context, to be deallocated + // later + if (dwarf_srclines_from_linecontext(de.line_context, &de.line_buffer, + &de.line_count, + &error) == DW_DLV_OK) { + + // Add all the addresses to our map + for (int i = 0; i < de.line_count; i++) { + if (dwarf_lineaddr(de.line_buffer[i], &line_addr, &error) != + DW_DLV_OK) { + line_addr = 0; + } + de.line_section.insert(std::pair(line_addr, i)); + } + } + } + + // For each CU, cache the function DIEs that contain the + // DW_AT_specification attribute. When building with -g3 the function + // DIEs are separated in declaration and specification, with the + // declaration containing only the name and parameters and the + // specification the low/high pc and other compiler attributes. + // + // We cache those specifications so we don't skip over the declarations, + // because they have no pc, and we can do namespace resolution for + // DWARF function names. + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Die current_die = 0; + if (dwarf_child(die, ¤t_die, &error) == DW_DLV_OK) { + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + if (tag_value == DW_TAG_subprogram || + tag_value == DW_TAG_inlined_subroutine) { + + Dwarf_Bool has_attr = 0; + if (dwarf_hasattr(current_die, DW_AT_specification, &has_attr, + &error) == DW_DLV_OK) { + if (has_attr) { + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_specification, &attr_mem, + &error) == DW_DLV_OK) { + Dwarf_Off spec_offset = 0; + if (dwarf_formref(attr_mem, &spec_offset, &error) == + DW_DLV_OK) { + Dwarf_Off spec_die_offset; + if (dwarf_dieoffset(current_die, &spec_die_offset, &error) == + DW_DLV_OK) { + de.spec_section[spec_offset] = spec_die_offset; + } + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + } + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + break; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + } + return de; + } + + static Dwarf_Die get_referenced_die(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Half attr, bool global) { + Dwarf_Error error = DW_DLE_NE; + Dwarf_Attribute attr_mem; + + Dwarf_Die found_die = NULL; + if (dwarf_attr(die, attr, &attr_mem, &error) == DW_DLV_OK) { + Dwarf_Off offset; + int result = 0; + if (global) { + result = dwarf_global_formref(attr_mem, &offset, &error); + } else { + result = dwarf_formref(attr_mem, &offset, &error); + } + + if (result == DW_DLV_OK) { + if (dwarf_offdie(dwarf, offset, &found_die, &error) != DW_DLV_OK) { + found_die = NULL; + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + return found_die; + } + + static std::string get_referenced_die_name(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Half attr, bool global) { + Dwarf_Error error = DW_DLE_NE; + std::string value; + + Dwarf_Die found_die = get_referenced_die(dwarf, die, attr, global); + + if (found_die) { + char *name; + if (dwarf_diename(found_die, &name, &error) == DW_DLV_OK) { + if (name) { + value = std::string(name); + } + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } + dwarf_dealloc(dwarf, found_die, DW_DLA_DIE); + } + + return value; + } + + // Returns a spec DIE linked to the passed one. The caller should + // deallocate the DIE + static Dwarf_Die get_spec_die(dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Off die_offset; + if (fobj.current_cu && + dwarf_die_CU_offset(die, &die_offset, &error) == DW_DLV_OK) { + die_specmap_t::iterator it = + fobj.current_cu->spec_section.find(die_offset); + + // If we have a DIE that completes the current one, check if + // that one has the pc we are looking for + if (it != fobj.current_cu->spec_section.end()) { + Dwarf_Die spec_die = 0; + if (dwarf_offdie(dwarf, it->second, &spec_die, &error) == DW_DLV_OK) { + return spec_die; + } + } + } + + // Maybe we have an abstract origin DIE with the function information? + return get_referenced_die(fobj.dwarf_handle.get(), die, + DW_AT_abstract_origin, true); + } + + static bool die_has_pc(dwarf_fileobject &fobj, Dwarf_Die die, Dwarf_Addr pc) { + Dwarf_Addr low_pc = 0, high_pc = 0; + Dwarf_Half high_pc_form = 0; + Dwarf_Form_Class return_class; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + bool has_lowpc = false; + bool has_highpc = false; + bool has_ranges = false; + + if (dwarf_lowpc(die, &low_pc, &error) == DW_DLV_OK) { + // If we have a low_pc check if there is a high pc. + // If we don't have a high pc this might mean we have a base + // address for the ranges list or just an address. + has_lowpc = true; + + if (dwarf_highpc_b(die, &high_pc, &high_pc_form, &return_class, &error) == + DW_DLV_OK) { + // We do have a high pc. In DWARF 4+ this is an offset from the + // low pc, but in earlier versions it's an absolute address. + + has_highpc = true; + // In DWARF 2/3 this would be a DW_FORM_CLASS_ADDRESS + if (return_class == DW_FORM_CLASS_CONSTANT) { + high_pc = low_pc + high_pc; + } + + // We have low and high pc, check if our address + // is in that range + return pc >= low_pc && pc < high_pc; + } + } else { + // Reset the low_pc, in case dwarf_lowpc failing set it to some + // undefined value. + low_pc = 0; + } + + // Check if DW_AT_ranges is present and search for the PC in the + // returned ranges list. We always add the low_pc, as it not set it will + // be 0, in case we had a DW_AT_low_pc and DW_AT_ranges pair + bool result = false; + + Dwarf_Attribute attr; + if (dwarf_attr(die, DW_AT_ranges, &attr, &error) == DW_DLV_OK) { + + Dwarf_Off offset; + if (dwarf_global_formref(attr, &offset, &error) == DW_DLV_OK) { + Dwarf_Ranges *ranges; + Dwarf_Signed ranges_count = 0; + Dwarf_Unsigned byte_count = 0; + + if (dwarf_get_ranges_a(dwarf, offset, die, &ranges, &ranges_count, + &byte_count, &error) == DW_DLV_OK) { + has_ranges = ranges_count != 0; + for (int i = 0; i < ranges_count; i++) { + if (ranges[i].dwr_addr1 != 0 && + pc >= ranges[i].dwr_addr1 + low_pc && + pc < ranges[i].dwr_addr2 + low_pc) { + result = true; + break; + } + } + dwarf_ranges_dealloc(dwarf, ranges, ranges_count); + } + } + } + + // Last attempt. We might have a single address set as low_pc. + if (!result && low_pc != 0 && pc == low_pc) { + result = true; + } + + // If we don't have lowpc, highpc and ranges maybe this DIE is a + // declaration that relies on a DW_AT_specification DIE that happens + // later. Use the specification cache we filled when we loaded this CU. + if (!result && (!has_lowpc && !has_highpc && !has_ranges)) { + Dwarf_Die spec_die = get_spec_die(fobj, die); + if (spec_die) { + result = die_has_pc(fobj, spec_die, pc); + dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); + } + } + + return result; + } + + static void get_type(Dwarf_Debug dwarf, Dwarf_Die die, std::string &type) { + Dwarf_Error error = DW_DLE_NE; + + Dwarf_Die child = 0; + if (dwarf_child(die, &child, &error) == DW_DLV_OK) { + get_type(dwarf, child, type); + } + + if (child) { + type.insert(0, "::"); + dwarf_dealloc(dwarf, child, DW_DLA_DIE); + } + + char *name; + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + type.insert(0, std::string(name)); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + type.insert(0, ""); + } + } + + static std::string get_type_by_signature(Dwarf_Debug dwarf, Dwarf_Die die) { + Dwarf_Error error = DW_DLE_NE; + + Dwarf_Sig8 signature; + Dwarf_Bool has_attr = 0; + if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) { + if (has_attr) { + Dwarf_Attribute attr_mem; + if (dwarf_attr(die, DW_AT_signature, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formsig8(attr_mem, &signature, &error) != DW_DLV_OK) { + return std::string(""); + } + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + } + + Dwarf_Unsigned next_cu_header; + Dwarf_Sig8 tu_signature; + std::string result; + bool found = false; + + while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, &tu_signature, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + + if (strncmp(signature.signature, tu_signature.signature, 8) == 0) { + Dwarf_Die type_cu_die = 0; + if (dwarf_siblingof_b(dwarf, 0, 0, &type_cu_die, &error) == DW_DLV_OK) { + Dwarf_Die child_die = 0; + if (dwarf_child(type_cu_die, &child_die, &error) == DW_DLV_OK) { + get_type(dwarf, child_die, result); + found = !result.empty(); + dwarf_dealloc(dwarf, child_die, DW_DLA_DIE); + } + dwarf_dealloc(dwarf, type_cu_die, DW_DLA_DIE); + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Unfortunately, libdwarf's + // next_cu_header API keeps its own iterator per Dwarf_Debug + // that can't be reset. We need to keep fetching elements until + // the end. + } + } else { + // If we couldn't resolve the type just print out the signature + std::ostringstream string_stream; + string_stream << "<0x" << std::hex << std::setfill('0'); + for (int i = 0; i < 8; ++i) { + string_stream << std::setw(2) << std::hex + << (int)(unsigned char)(signature.signature[i]); + } + string_stream << ">"; + result = string_stream.str(); + } + return result; + } + + struct type_context_t { + bool is_const; + bool is_typedef; + bool has_type; + bool has_name; + std::string text; + + type_context_t() + : is_const(false), is_typedef(false), has_type(false), has_name(false) { + } + }; + + // Types are resolved from right to left: we get the variable name first + // and then all specifiers (like const or pointer) in a chain of DW_AT_type + // DIEs. Call this function recursively until we get a complete type + // string. + static void set_parameter_string(dwarf_fileobject &fobj, Dwarf_Die die, + type_context_t &context) { + char *name; + Dwarf_Error error = DW_DLE_NE; + + // typedefs contain also the base type, so we skip it and only + // print the typedef name + if (!context.is_typedef) { + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + if (!context.text.empty()) { + context.text.insert(0, " "); + } + context.text.insert(0, std::string(name)); + dwarf_dealloc(fobj.dwarf_handle.get(), name, DW_DLA_STRING); + } + } else { + context.is_typedef = false; + context.has_type = true; + if (context.is_const) { + context.text.insert(0, "const "); + context.is_const = false; + } + } + + bool next_type_is_const = false; + bool is_keyword = true; + + Dwarf_Half tag = 0; + Dwarf_Bool has_attr = 0; + if (dwarf_tag(die, &tag, &error) == DW_DLV_OK) { + switch (tag) { + case DW_TAG_structure_type: + case DW_TAG_union_type: + case DW_TAG_class_type: + case DW_TAG_enumeration_type: + context.has_type = true; + if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == + DW_DLV_OK) { + // If we have a signature it means the type is defined + // in .debug_types, so we need to load the DIE pointed + // at by the signature and resolve it + if (has_attr) { + std::string type = + get_type_by_signature(fobj.dwarf_handle.get(), die); + if (context.is_const) + type.insert(0, "const "); + + if (!context.text.empty()) + context.text.insert(0, " "); + context.text.insert(0, type); + } + + // Treat enums like typedefs, and skip printing its + // base type + context.is_typedef = (tag == DW_TAG_enumeration_type); + } + break; + case DW_TAG_const_type: + next_type_is_const = true; + break; + case DW_TAG_pointer_type: + context.text.insert(0, "*"); + break; + case DW_TAG_reference_type: + context.text.insert(0, "&"); + break; + case DW_TAG_restrict_type: + context.text.insert(0, "restrict "); + break; + case DW_TAG_rvalue_reference_type: + context.text.insert(0, "&&"); + break; + case DW_TAG_volatile_type: + context.text.insert(0, "volatile "); + break; + case DW_TAG_typedef: + // Propagate the const-ness to the next type + // as typedefs are linked to its base type + next_type_is_const = context.is_const; + context.is_typedef = true; + context.has_type = true; + break; + case DW_TAG_base_type: + context.has_type = true; + break; + case DW_TAG_formal_parameter: + context.has_name = true; + break; + default: + is_keyword = false; + break; + } + } + + if (!is_keyword && context.is_const) { + context.text.insert(0, "const "); + } + + context.is_const = next_type_is_const; + + Dwarf_Die ref = + get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_type, true); + if (ref) { + set_parameter_string(fobj, ref, context); + dwarf_dealloc(fobj.dwarf_handle.get(), ref, DW_DLA_DIE); + } + + if (!context.has_type && context.has_name) { + context.text.insert(0, "void "); + context.has_type = true; + } + } + + // Resolve the function return type and parameters + static void set_function_parameters(std::string &function_name, + std::vector &ns, + dwarf_fileobject &fobj, Dwarf_Die die) { + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Die current_die = 0; + std::string parameters; + bool has_spec = true; + // Check if we have a spec DIE. If we do we use it as it contains + // more information, like parameter names. + Dwarf_Die spec_die = get_spec_die(fobj, die); + if (!spec_die) { + has_spec = false; + spec_die = die; + } + + std::vector::const_iterator it = ns.begin(); + std::string ns_name; + for (it = ns.begin(); it < ns.end(); ++it) { + ns_name.append(*it).append("::"); + } + + if (!ns_name.empty()) { + function_name.insert(0, ns_name); + } + + // See if we have a function return type. It can be either on the + // current die or in its spec one (usually true for inlined functions) + std::string return_type = + get_referenced_die_name(dwarf, die, DW_AT_type, true); + if (return_type.empty()) { + return_type = get_referenced_die_name(dwarf, spec_die, DW_AT_type, true); + } + if (!return_type.empty()) { + return_type.append(" "); + function_name.insert(0, return_type); + } + + if (dwarf_child(spec_die, ¤t_die, &error) == DW_DLV_OK) { + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + if (tag_value == DW_TAG_formal_parameter) { + // Ignore artificial (ie, compiler generated) parameters + bool is_artificial = false; + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_artificial, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + is_artificial = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!is_artificial) { + type_context_t context; + set_parameter_string(fobj, current_die, context); + + if (parameters.empty()) { + parameters.append("("); + } else { + parameters.append(", "); + } + parameters.append(context.text); + } + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + break; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + } + if (parameters.empty()) + parameters = "("; + parameters.append(")"); + + // If we got a spec DIE we need to deallocate it + if (has_spec) + dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); + + function_name.append(parameters); + } + + // defined here because in C++98, template function cannot take locally + // defined types... grrr. + struct inliners_search_cb { + void operator()(Dwarf_Die die, std::vector &ns) { + Dwarf_Error error = DW_DLE_NE; + Dwarf_Half tag_value; + Dwarf_Attribute attr_mem; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + + dwarf_tag(die, &tag_value, &error); + + switch (tag_value) { + char *name; + case DW_TAG_subprogram: + if (!trace.source.function.empty()) + break; + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + trace.source.function = std::string(name); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + // We don't have a function name in this DIE. + // Check if there is a referenced non-defining + // declaration. + trace.source.function = + get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); + if (trace.source.function.empty()) { + trace.source.function = + get_referenced_die_name(dwarf, die, DW_AT_specification, true); + } + } + + // Append the function parameters, if available + set_function_parameters(trace.source.function, ns, fobj, die); + + // If the object function name is empty, it's possible that + // there is no dynamic symbol table (maybe the executable + // was stripped or not built with -rdynamic). See if we have + // a DWARF linkage name to use instead. We try both + // linkage_name and MIPS_linkage_name because the MIPS tag + // was the unofficial one until it was adopted in DWARF4. + // Old gcc versions generate MIPS_linkage_name + if (trace.object_function.empty()) { + details::demangler demangler; + + if (dwarf_attr(die, DW_AT_linkage_name, &attr_mem, &error) != + DW_DLV_OK) { + if (dwarf_attr(die, DW_AT_MIPS_linkage_name, &attr_mem, &error) != + DW_DLV_OK) { + break; + } + } + + char *linkage; + if (dwarf_formstring(attr_mem, &linkage, &error) == DW_DLV_OK) { + trace.object_function = demangler.demangle(linkage); + dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + break; + + case DW_TAG_inlined_subroutine: + ResolvedTrace::SourceLoc sloc; + + if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { + sloc.function = std::string(name); + dwarf_dealloc(dwarf, name, DW_DLA_STRING); + } else { + // We don't have a name for this inlined DIE, it could + // be that there is an abstract origin instead. + // Get the DW_AT_abstract_origin value, which is a + // reference to the source DIE and try to get its name + sloc.function = + get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); + } + + set_function_parameters(sloc.function, ns, fobj, die); + + std::string file = die_call_file(dwarf, die, cu_die); + if (!file.empty()) + sloc.filename = file; + + Dwarf_Unsigned number = 0; + if (dwarf_attr(die, DW_AT_call_line, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { + sloc.line = number; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (dwarf_attr(die, DW_AT_call_column, &attr_mem, &error) == + DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { + sloc.col = number; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + trace.inliners.push_back(sloc); + break; + }; + } + ResolvedTrace &trace; + dwarf_fileobject &fobj; + Dwarf_Die cu_die; + inliners_search_cb(ResolvedTrace &t, dwarf_fileobject &f, Dwarf_Die c) + : trace(t), fobj(f), cu_die(c) {} + }; + + static Dwarf_Die find_fundie_by_pc(dwarf_fileobject &fobj, + Dwarf_Die parent_die, Dwarf_Addr pc, + Dwarf_Die result) { + Dwarf_Die current_die = 0; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + + if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { + return NULL; + } + + for (;;) { + Dwarf_Die sibling_die = 0; + Dwarf_Half tag_value; + dwarf_tag(current_die, &tag_value, &error); + + switch (tag_value) { + case DW_TAG_subprogram: + case DW_TAG_inlined_subroutine: + if (die_has_pc(fobj, current_die, pc)) { + return current_die; + } + }; + bool declaration = false; + Dwarf_Attribute attr_mem; + if (dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + declaration = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!declaration) { + // let's be curious and look deeper in the tree, functions are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + Dwarf_Die die_mem = 0; + Dwarf_Die indie = find_fundie_by_pc(fobj, current_die, pc, die_mem); + if (indie) { + result = die_mem; + return result; + } + } + + int res = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (res == DW_DLV_ERROR) { + return NULL; + } else if (res == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != parent_die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + current_die = sibling_die; + } + return NULL; + } + + template + static bool deep_first_search_by_pc(dwarf_fileobject &fobj, + Dwarf_Die parent_die, Dwarf_Addr pc, + std::vector &ns, CB cb) { + Dwarf_Die current_die = 0; + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + + if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { + return false; + } + + bool branch_has_pc = false; + bool has_namespace = false; + for (;;) { + Dwarf_Die sibling_die = 0; + + Dwarf_Half tag; + if (dwarf_tag(current_die, &tag, &error) == DW_DLV_OK) { + if (tag == DW_TAG_namespace || tag == DW_TAG_class_type) { + char *ns_name = NULL; + if (dwarf_diename(current_die, &ns_name, &error) == DW_DLV_OK) { + if (ns_name) { + ns.push_back(std::string(ns_name)); + } else { + ns.push_back(""); + } + dwarf_dealloc(dwarf, ns_name, DW_DLA_STRING); + } else { + ns.push_back(""); + } + has_namespace = true; + } + } + + bool declaration = false; + Dwarf_Attribute attr_mem; + if (tag != DW_TAG_class_type && + dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == + DW_DLV_OK) { + Dwarf_Bool flag = 0; + if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { + declaration = flag != 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + } + + if (!declaration) { + // let's be curious and look deeper in the tree, function are + // not necessarily at the first level, but might be nested + // inside a namespace, structure, a function, an inlined + // function etc. + branch_has_pc = deep_first_search_by_pc(fobj, current_die, pc, ns, cb); + } + + if (!branch_has_pc) { + branch_has_pc = die_has_pc(fobj, current_die, pc); + } + + if (branch_has_pc) { + cb(current_die, ns); + } + + int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); + if (result == DW_DLV_ERROR) { + return false; + } else if (result == DW_DLV_NO_ENTRY) { + break; + } + + if (current_die != parent_die) { + dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); + current_die = 0; + } + + if (has_namespace) { + has_namespace = false; + ns.pop_back(); + } + current_die = sibling_die; + } + + if (has_namespace) { + ns.pop_back(); + } + return branch_has_pc; + } + + static std::string die_call_file(Dwarf_Debug dwarf, Dwarf_Die die, + Dwarf_Die cu_die) { + Dwarf_Attribute attr_mem; + Dwarf_Error error = DW_DLE_NE; + Dwarf_Unsigned file_index; + + std::string file; + + if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) { + file_index = 0; + } + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); + + if (file_index == 0) { + return file; + } + + char **srcfiles = 0; + Dwarf_Signed file_count = 0; + if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { + if (file_count > 0 && file_index <= static_cast(file_count)) { + file = std::string(srcfiles[file_index - 1]); + } + + // Deallocate all strings! + for (int i = 0; i < file_count; ++i) { + dwarf_dealloc(dwarf, srcfiles[i], DW_DLA_STRING); + } + dwarf_dealloc(dwarf, srcfiles, DW_DLA_LIST); + } + } + return file; + } + + Dwarf_Die find_die(dwarf_fileobject &fobj, Dwarf_Addr addr) { + // Let's get to work! First see if we have a debug_aranges section so + // we can speed up the search + + Dwarf_Debug dwarf = fobj.dwarf_handle.get(); + Dwarf_Error error = DW_DLE_NE; + Dwarf_Arange *aranges; + Dwarf_Signed arange_count; + + Dwarf_Die returnDie; + bool found = false; + if (dwarf_get_aranges(dwarf, &aranges, &arange_count, &error) != + DW_DLV_OK) { + aranges = NULL; + } + + if (aranges) { + // We have aranges. Get the one where our address is. + Dwarf_Arange arange; + if (dwarf_get_arange(aranges, arange_count, addr, &arange, &error) == + DW_DLV_OK) { + + // We found our address. Get the compilation-unit DIE offset + // represented by the given address range. + Dwarf_Off cu_die_offset; + if (dwarf_get_cu_die_offset(arange, &cu_die_offset, &error) == + DW_DLV_OK) { + // Get the DIE at the offset returned by the aranges search. + // We set is_info to 1 to specify that the offset is from + // the .debug_info section (and not .debug_types) + int dwarf_result = + dwarf_offdie_b(dwarf, cu_die_offset, 1, &returnDie, &error); + + found = dwarf_result == DW_DLV_OK; + } + dwarf_dealloc(dwarf, arange, DW_DLA_ARANGE); + } + } + + if (found) + return returnDie; // The caller is responsible for freeing the die + + // The search for aranges failed. Try to find our address by scanning + // all compilation units. + Dwarf_Unsigned next_cu_header; + Dwarf_Half tag = 0; + returnDie = 0; + + while (!found && + dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + + if (returnDie) + dwarf_dealloc(dwarf, returnDie, DW_DLA_DIE); + + if (dwarf_siblingof(dwarf, 0, &returnDie, &error) == DW_DLV_OK) { + if ((dwarf_tag(returnDie, &tag, &error) == DW_DLV_OK) && + tag == DW_TAG_compile_unit) { + if (die_has_pc(fobj, returnDie, addr)) { + found = true; + } + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Libdwarf's next_cu_header API + // keeps its own iterator per Dwarf_Debug that can't be reset. + // We need to keep fetching elements until the end. + } + } + + if (found) + return returnDie; + + // We couldn't find any compilation units with ranges or a high/low pc. + // Try again by looking at all DIEs in all compilation units. + Dwarf_Die cudie; + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + if (dwarf_siblingof(dwarf, 0, &cudie, &error) == DW_DLV_OK) { + Dwarf_Die die_mem = 0; + Dwarf_Die resultDie = find_fundie_by_pc(fobj, cudie, addr, die_mem); + + if (resultDie) { + found = true; + break; + } + } + } + + if (found) { + while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, + &next_cu_header, 0, &error) == DW_DLV_OK) { + // Reset the cu header state. Libdwarf's next_cu_header API + // keeps its own iterator per Dwarf_Debug that can't be reset. + // We need to keep fetching elements until the end. + } + } + + if (found) + return cudie; + + // We failed. + return NULL; + } +}; +#endif // BACKWARD_HAS_DWARF == 1 + +template <> +class TraceResolverImpl + : public TraceResolverLinuxImpl {}; + +#endif // BACKWARD_SYSTEM_LINUX + +#ifdef BACKWARD_SYSTEM_DARWIN + +template class TraceResolverDarwinImpl; + +template <> +class TraceResolverDarwinImpl + : public TraceResolverImplBase { +public: + void load_addresses(void *const*addresses, int address_count) override { + if (address_count == 0) { + return; + } + _symbols.reset(backtrace_symbols(addresses, address_count)); + } + + ResolvedTrace resolve(ResolvedTrace trace) override { + // parse: + // + + char *filename = _symbols[trace.idx]; + + // skip " " + while (*filename && *filename != ' ') + filename++; + while (*filename == ' ') + filename++; + + // find start of from end ( may contain a space) + char *p = filename + strlen(filename) - 1; + // skip to start of " + " + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + char *funcname_end = p + 1; + + // skip to start of "" + while (p > filename && *p != ' ') + p--; + char *funcname = p + 1; + + // skip to start of " " + while (p > filename && *p == ' ') + p--; + while (p > filename && *p != ' ') + p--; + while (p > filename && *p == ' ') + p--; + + // skip "", handling the case where it contains a + char *filename_end = p + 1; + if (p == filename) { + // something went wrong, give up + filename_end = filename + strlen(filename); + funcname = filename_end; + } + trace.object_filename.assign( + filename, filename_end); // ok even if filename_end is the ending \0 + // (then we assign entire string) + + if (*funcname) { // if it's not end of string + *funcname_end = '\0'; + + trace.object_function = this->demangle(funcname); + trace.object_function += " "; + trace.object_function += (funcname_end + 1); + trace.source.function = trace.object_function; // we cannot do better. + } + return trace; + } + +private: + details::handle _symbols; +}; + +template <> +class TraceResolverImpl + : public TraceResolverDarwinImpl {}; + +#endif // BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +// Load all symbol info +// Based on: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 + +struct module_data { + std::string image_name; + std::string module_name; + void *base_address; + DWORD load_size; +}; + +class get_mod_info { + HANDLE process; + static const int buffer_length = 4096; + +public: + get_mod_info(HANDLE h) : process(h) {} + + module_data operator()(HMODULE module) { + module_data ret; + char temp[buffer_length]; + MODULEINFO mi; + + GetModuleInformation(process, module, &mi, sizeof(mi)); + ret.base_address = mi.lpBaseOfDll; + ret.load_size = mi.SizeOfImage; + + GetModuleFileNameExA(process, module, temp, sizeof(temp)); + ret.image_name = temp; + GetModuleBaseNameA(process, module, temp, sizeof(temp)); + ret.module_name = temp; + std::vector img(ret.image_name.begin(), ret.image_name.end()); + std::vector mod(ret.module_name.begin(), ret.module_name.end()); + SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, + ret.load_size); + return ret; + } +}; + +template <> class TraceResolverImpl + : public TraceResolverImplBase { +public: + TraceResolverImpl() { + + HANDLE process = GetCurrentProcess(); + + std::vector modules; + DWORD cbNeeded; + std::vector module_handles(1); + SymInitialize(process, NULL, false); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + EnumProcessModules(process, &module_handles[0], + static_cast(module_handles.size() * sizeof(HMODULE)), + &cbNeeded); + module_handles.resize(cbNeeded / sizeof(HMODULE)); + EnumProcessModules(process, &module_handles[0], + static_cast(module_handles.size() * sizeof(HMODULE)), + &cbNeeded); + std::transform(module_handles.begin(), module_handles.end(), + std::back_inserter(modules), get_mod_info(process)); + void *base = modules[0].base_address; + IMAGE_NT_HEADERS *h = ImageNtHeader(base); + image_type = h->FileHeader.Machine; + } + + static const int max_sym_len = 255; + struct symbol_t { + SYMBOL_INFO sym; + char buffer[max_sym_len]; + } sym; + + DWORD64 displacement; + + ResolvedTrace resolve(ResolvedTrace t) override { + HANDLE process = GetCurrentProcess(); + + char name[256]; + + memset(&sym, 0, sizeof(sym)); + sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); + sym.sym.MaxNameLen = max_sym_len; + + if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { + // TODO: error handling everywhere + char* lpMsgBuf; + DWORD dw = GetLastError(); + + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char*)&lpMsgBuf, 0, NULL)) { + std::fprintf(stderr, "%s\n", lpMsgBuf); + LocalFree(lpMsgBuf); + } + + // abort(); + } + UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE); + + DWORD offset = 0; + IMAGEHLP_LINE line; + if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) { + t.object_filename = line.FileName; + t.source.filename = line.FileName; + t.source.line = line.LineNumber; + t.source.col = offset; + } + + t.source.function = name; + t.object_filename = ""; + t.object_function = name; + + return t; + } + + DWORD machine_type() const { return image_type; } + +private: + DWORD image_type; +}; + +#endif + +class TraceResolver : public TraceResolverImpl {}; + +/*************** CODE SNIPPET ***************/ + +class SourceFile { +public: + typedef std::vector > lines_t; + + SourceFile() {} + SourceFile(const std::string &path) { + // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains + // a colon-separated list of path prefixes. Try prepending each + // to the given path until a valid file is found. + const std::vector &prefixes = get_paths_from_env_variable(); + for (size_t i = 0; i < prefixes.size(); ++i) { + // Double slashes (//) should not be a problem. + std::string new_path = prefixes[i] + '/' + path; + _file.reset(new std::ifstream(new_path.c_str())); + if (is_open()) + break; + } + // 2. If no valid file found then fallback to opening the path as-is. + if (!_file || !is_open()) { + _file.reset(new std::ifstream(path.c_str())); + } + } + bool is_open() const { return _file->is_open(); } + + lines_t &get_lines(unsigned line_start, unsigned line_count, lines_t &lines) { + using namespace std; + // This function make uses of the dumbest algo ever: + // 1) seek(0) + // 2) read lines one by one and discard until line_start + // 3) read line one by one until line_start + line_count + // + // If you are getting snippets many time from the same file, it is + // somewhat a waste of CPU, feel free to benchmark and propose a + // better solution ;) + + _file->clear(); + _file->seekg(0); + string line; + unsigned line_idx; + + for (line_idx = 1; line_idx < line_start; ++line_idx) { + std::getline(*_file, line); + if (!*_file) { + return lines; + } + } + + // think of it like a lambda in C++98 ;) + // but look, I will reuse it two times! + // What a good boy am I. + struct isspace { + bool operator()(char c) { return std::isspace(c); } + }; + + bool started = false; + for (; line_idx < line_start + line_count; ++line_idx) { + getline(*_file, line); + if (!*_file) { + return lines; + } + if (!started) { + if (std::find_if(line.begin(), line.end(), not_isspace()) == line.end()) + continue; + started = true; + } + lines.push_back(make_pair(line_idx, line)); + } + + lines.erase( + std::find_if(lines.rbegin(), lines.rend(), not_isempty()).base(), + lines.end()); + return lines; + } + + lines_t get_lines(unsigned line_start, unsigned line_count) { + lines_t lines; + return get_lines(line_start, line_count, lines); + } + + // there is no find_if_not in C++98, lets do something crappy to + // workaround. + struct not_isspace { + bool operator()(char c) { return !std::isspace(c); } + }; + // and define this one here because C++98 is not happy with local defined + // struct passed to template functions, fuuuu. + struct not_isempty { + bool operator()(const lines_t::value_type &p) { + return !(std::find_if(p.second.begin(), p.second.end(), not_isspace()) == + p.second.end()); + } + }; + + void swap(SourceFile &b) { _file.swap(b._file); } + +#ifdef BACKWARD_ATLEAST_CXX11 + SourceFile(SourceFile &&from) : _file(nullptr) { swap(from); } + SourceFile &operator=(SourceFile &&from) { + swap(from); + return *this; + } +#else + explicit SourceFile(const SourceFile &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + } + SourceFile &operator=(const SourceFile &from) { + // some sort of poor man's move semantic. + swap(const_cast(from)); + return *this; + } +#endif + + // Allow adding to paths gotten from BACKWARD_CXX_SOURCE_PREFIXES after loading the + // library; this can be useful when the library is loaded when the locations are unknown + // Warning: Because this edits the static paths variable, it is *not* intrinsiclly thread safe + static void add_paths_to_env_variable_impl(const std::string & to_add) { + get_mutable_paths_from_env_variable().push_back(to_add); + } + +private: + details::handle > + _file; + + static std::vector get_paths_from_env_variable_impl() { + std::vector paths; + const char *prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES"); + if (prefixes_str && prefixes_str[0]) { + paths = details::split_source_prefixes(prefixes_str); + } + return paths; + } + + static std::vector &get_mutable_paths_from_env_variable() { + static volatile std::vector paths = get_paths_from_env_variable_impl(); + return const_cast&>(paths); + } + + static const std::vector &get_paths_from_env_variable() { + return get_mutable_paths_from_env_variable(); + } + +#ifdef BACKWARD_ATLEAST_CXX11 + SourceFile(const SourceFile &) = delete; + SourceFile &operator=(const SourceFile &) = delete; +#endif +}; + +class SnippetFactory { +public: + typedef SourceFile::lines_t lines_t; + + lines_t get_snippet(const std::string &filename, unsigned line_start, + unsigned context_size) { + + SourceFile &src_file = get_src_file(filename); + unsigned start = line_start - context_size / 2; + return src_file.get_lines(start, context_size); + } + + lines_t get_combined_snippet(const std::string &filename_a, unsigned line_a, + const std::string &filename_b, unsigned line_b, + unsigned context_size) { + SourceFile &src_file_a = get_src_file(filename_a); + SourceFile &src_file_b = get_src_file(filename_b); + + lines_t lines = + src_file_a.get_lines(line_a - context_size / 4, context_size / 2); + src_file_b.get_lines(line_b - context_size / 4, context_size / 2, lines); + return lines; + } + + lines_t get_coalesced_snippet(const std::string &filename, unsigned line_a, + unsigned line_b, unsigned context_size) { + SourceFile &src_file = get_src_file(filename); + + using std::max; + using std::min; + unsigned a = min(line_a, line_b); + unsigned b = max(line_a, line_b); + + if ((b - a) < (context_size / 3)) { + return src_file.get_lines((a + b - context_size + 1) / 2, context_size); + } + + lines_t lines = src_file.get_lines(a - context_size / 4, context_size / 2); + src_file.get_lines(b - context_size / 4, context_size / 2, lines); + return lines; + } + +private: + typedef details::hashtable::type src_files_t; + src_files_t _src_files; + + SourceFile &get_src_file(const std::string &filename) { + src_files_t::iterator it = _src_files.find(filename); + if (it != _src_files.end()) { + return it->second; + } + SourceFile &new_src_file = _src_files[filename]; + new_src_file = SourceFile(filename); + return new_src_file; + } +}; + +/*************** PRINTER ***************/ + +namespace ColorMode { +enum type { automatic, never, always }; +} + +class cfile_streambuf : public std::streambuf { +public: + cfile_streambuf(FILE *_sink) : sink(_sink) {} + int_type underflow() override { return traits_type::eof(); } + int_type overflow(int_type ch) override { + if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) { + return ch; + } + return traits_type::eof(); + } + + std::streamsize xsputn(const char_type *s, std::streamsize count) override { + return static_cast( + fwrite(s, sizeof *s, static_cast(count), sink)); + } + +#ifdef BACKWARD_ATLEAST_CXX11 +public: + cfile_streambuf(const cfile_streambuf &) = delete; + cfile_streambuf &operator=(const cfile_streambuf &) = delete; +#else +private: + cfile_streambuf(const cfile_streambuf &); + cfile_streambuf &operator=(const cfile_streambuf &); +#endif + +private: + FILE *sink; + std::vector buffer; +}; + +#ifdef BACKWARD_SYSTEM_LINUX + +namespace Color { +enum type { yellow = 33, purple = 35, reset = 39 }; +} // namespace Color + +class Colorize { +public: + Colorize(std::ostream &os) : _os(os), _reset(false), _enabled(false) {} + + void activate(ColorMode::type mode) { _enabled = mode == ColorMode::always; } + + void activate(ColorMode::type mode, FILE *fp) { activate(mode, fileno(fp)); } + + void set_color(Color::type ccode) { + if (!_enabled) + return; + + // I assume that the terminal can handle basic colors. Seriously I + // don't want to deal with all the termcap shit. + _os << "\033[" << static_cast(ccode) << "m"; + _reset = (ccode != Color::reset); + } + + ~Colorize() { + if (_reset) { + set_color(Color::reset); + } + } + +private: + void activate(ColorMode::type mode, int fd) { + activate(mode == ColorMode::automatic && isatty(fd) ? ColorMode::always + : mode); + } + + std::ostream &_os; + bool _reset; + bool _enabled; +}; + +#else // ndef BACKWARD_SYSTEM_LINUX + +namespace Color { +enum type { yellow = 0, purple = 0, reset = 0 }; +} // namespace Color + +class Colorize { +public: + Colorize(std::ostream &) {} + void activate(ColorMode::type) {} + void activate(ColorMode::type, FILE *) {} + void set_color(Color::type) {} +}; + +#endif // BACKWARD_SYSTEM_LINUX + +class Printer { +public: + bool snippet; + ColorMode::type color_mode; + bool address; + bool object; + int inliner_context_size; + int trace_context_size; + bool reverse; + + Printer() + : snippet(true), color_mode(ColorMode::automatic), address(false), + object(false), inliner_context_size(5), trace_context_size(7), + reverse(true) {} + + template FILE *print(ST &st, FILE *fp = stderr) { + cfile_streambuf obuf(fp); + std::ostream os(&obuf); + Colorize colorize(os); + colorize.activate(color_mode, fp); + print_stacktrace(st, os, colorize); + return fp; + } + + template std::ostream &print(ST &st, std::ostream &os) { + Colorize colorize(os); + colorize.activate(color_mode); + print_stacktrace(st, os, colorize); + return os; + } + + template + FILE *print(IT begin, IT end, FILE *fp = stderr, size_t thread_id = 0) { + cfile_streambuf obuf(fp); + std::ostream os(&obuf); + Colorize colorize(os); + colorize.activate(color_mode, fp); + print_stacktrace(begin, end, os, thread_id, colorize); + return fp; + } + + template + std::ostream &print(IT begin, IT end, std::ostream &os, + size_t thread_id = 0) { + Colorize colorize(os); + colorize.activate(color_mode); + print_stacktrace(begin, end, os, thread_id, colorize); + return os; + } + + TraceResolver const &resolver() const { return _resolver; } + +private: + TraceResolver _resolver; + SnippetFactory _snippets; + + template + void print_stacktrace(ST &st, std::ostream &os, Colorize &colorize) { + print_header(os, st.thread_id()); + _resolver.load_stacktrace(st); + if ( reverse ) { + for (size_t trace_idx = st.size(); trace_idx > 0; --trace_idx) { + print_trace(os, _resolver.resolve(st[trace_idx - 1]), colorize); + } + } else { + for (size_t trace_idx = 0; trace_idx < st.size(); ++trace_idx) { + print_trace(os, _resolver.resolve(st[trace_idx]), colorize); + } + } + } + + template + void print_stacktrace(IT begin, IT end, std::ostream &os, size_t thread_id, + Colorize &colorize) { + print_header(os, thread_id); + for (; begin != end; ++begin) { + print_trace(os, *begin, colorize); + } + } + + void print_header(std::ostream &os, size_t thread_id) { + os << "Stack trace (most recent call last)"; + if (thread_id) { + os << " in thread " << thread_id; + } + os << ":\n"; + } + + void print_trace(std::ostream &os, const ResolvedTrace &trace, + Colorize &colorize) { + os << "#" << std::left << std::setw(2) << trace.idx << std::right; + bool already_indented = true; + + if (!trace.source.filename.size() || object) { + os << " Object \"" << trace.object_filename << "\", at " << trace.addr + << ", in " << trace.object_function << "\n"; + already_indented = false; + } + + for (size_t inliner_idx = trace.inliners.size(); inliner_idx > 0; + --inliner_idx) { + if (!already_indented) { + os << " "; + } + const ResolvedTrace::SourceLoc &inliner_loc = + trace.inliners[inliner_idx - 1]; + print_source_loc(os, " | ", inliner_loc); + if (snippet) { + print_snippet(os, " | ", inliner_loc, colorize, Color::purple, + inliner_context_size); + } + already_indented = false; + } + + if (trace.source.filename.size()) { + if (!already_indented) { + os << " "; + } + print_source_loc(os, " ", trace.source, trace.addr); + if (snippet) { + print_snippet(os, " ", trace.source, colorize, Color::yellow, + trace_context_size); + } + } + } + + void print_snippet(std::ostream &os, const char *indent, + const ResolvedTrace::SourceLoc &source_loc, + Colorize &colorize, Color::type color_code, + int context_size) { + using namespace std; + typedef SnippetFactory::lines_t lines_t; + + lines_t lines = _snippets.get_snippet(source_loc.filename, source_loc.line, + static_cast(context_size)); + + for (lines_t::const_iterator it = lines.begin(); it != lines.end(); ++it) { + if (it->first == source_loc.line) { + colorize.set_color(color_code); + os << indent << ">"; + } else { + os << indent << " "; + } + os << std::setw(4) << it->first << ": " << it->second << "\n"; + if (it->first == source_loc.line) { + colorize.set_color(Color::reset); + } + } + } + + void print_source_loc(std::ostream &os, const char *indent, + const ResolvedTrace::SourceLoc &source_loc, + void *addr = nullptr) { + os << indent << "Source \"" << source_loc.filename << "\", line " + << source_loc.line << ", in " << source_loc.function; + + if (address && addr != nullptr) { + os << " [" << addr << "]"; + } + os << "\n"; + } +}; + +/*************** SIGNALS HANDLING ***************/ + +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) + +class SignalHandling { +public: + static std::vector make_default_signals() { + const int posix_signals[] = { + // Signals for which the default action is "Core". + SIGABRT, // Abort signal from abort(3) + SIGBUS, // Bus error (bad memory access) + SIGFPE, // Floating point exception + SIGILL, // Illegal Instruction + SIGIOT, // IOT trap. A synonym for SIGABRT + SIGQUIT, // Quit from keyboard + SIGSEGV, // Invalid memory reference + SIGSYS, // Bad argument to routine (SVr4) + SIGTRAP, // Trace/breakpoint trap + SIGXCPU, // CPU time limit exceeded (4.2BSD) + SIGXFSZ, // File size limit exceeded (4.2BSD) +#if defined(BACKWARD_SYSTEM_DARWIN) + SIGEMT, // emulation instruction executed +#endif + }; + return std::vector(posix_signals, + posix_signals + + sizeof posix_signals / sizeof posix_signals[0]); + } + + SignalHandling(const std::vector &posix_signals = make_default_signals()) + : _loaded(false) { + bool success = true; + + const size_t stack_size = 1024 * 1024 * 8; + _stack_content.reset(static_cast(malloc(stack_size))); + if (_stack_content) { + stack_t ss; + ss.ss_sp = _stack_content.get(); + ss.ss_size = stack_size; + ss.ss_flags = 0; + if (sigaltstack(&ss, nullptr) < 0) { + success = false; + } + } else { + success = false; + } + + for (size_t i = 0; i < posix_signals.size(); ++i) { + struct sigaction action; + memset(&action, 0, sizeof action); + action.sa_flags = + static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); + sigfillset(&action.sa_mask); + sigdelset(&action.sa_mask, posix_signals[i]); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif + action.sa_sigaction = &sig_handler; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + int r = sigaction(posix_signals[i], &action, nullptr); + if (r < 0) + success = false; + } + + _loaded = success; + } + + bool loaded() const { return _loaded; } + + static void handleSignal(int, siginfo_t *info, void *_ctx) { + ucontext_t *uctx = static_cast(_ctx); + + StackTrace st; + void *error_addr = nullptr; +#ifdef REG_RIP // x86_64 + error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); +#elif defined(REG_EIP) // x86_32 + error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); +#elif defined(__arm__) + error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); +#elif defined(__aarch64__) + #if defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); + #else + error_addr = reinterpret_cast(uctx->uc_mcontext.pc); + #endif +#elif defined(__mips__) + error_addr = reinterpret_cast( + reinterpret_cast(&uctx->uc_mcontext)->sc_pc); +#elif defined(__APPLE__) && defined(__POWERPC__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__srr0); +#elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ + defined(__POWERPC__) + error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); +#elif defined(__riscv) + error_addr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); +#elif defined(__s390x__) + error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); +#elif defined(__APPLE__) && defined(__x86_64__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); +#elif defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); +#elif defined(__loongarch__) + error_addr = reinterpret_cast(uctx->uc_mcontext.__pc); +#else +#warning ":/ sorry, ain't know no nothing none not of your architecture!" +#endif + if (error_addr) { + st.load_from(error_addr, 32, reinterpret_cast(uctx), + info->si_addr); + } else { + st.load_here(32, reinterpret_cast(uctx), info->si_addr); + } + + Printer printer; + printer.address = true; + printer.print(st, stderr); + +#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) + psiginfo(info, nullptr); +#else + (void)info; +#endif + } + +private: + details::handle _stack_content; + bool _loaded; + +#ifdef __GNUC__ + __attribute__((noreturn)) +#endif + static void + sig_handler(int signo, siginfo_t *info, void *_ctx) { + handleSignal(signo, info, _ctx); + + // try to forward the signal. + raise(info->si_signo); + + // terminate the process immediately. + puts("watf? exit"); + _exit(EXIT_FAILURE); + } +}; + +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +class SignalHandling { +public: + SignalHandling(const std::vector & = std::vector()) + : reporter_thread_([]() { + /* We handle crashes in a utility thread: + backward structures and some Windows functions called here + need stack space, which we do not have when we encounter a + stack overflow. + To support reporting stack traces during a stack overflow, + we create a utility thread at startup, which waits until a + crash happens or the program exits normally. */ + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::running; }); + } + if (crashed() == crash_status::crashed) { + handle_stacktrace(skip_recs()); + } + { + std::unique_lock lk(mtx()); + crashed() = crash_status::ending; + } + cv().notify_one(); + }) { + SetUnhandledExceptionFilter(crash_handler); + + signal(SIGABRT, signal_handler); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + std::set_terminate(&terminator); +#ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); +#endif + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalid_parameter_handler); + } + bool loaded() const { return true; } + + ~SignalHandling() { + { + std::unique_lock lk(mtx()); + crashed() = crash_status::normal_exit; + } + + cv().notify_one(); + + reporter_thread_.join(); + } + +private: + static CONTEXT *ctx() { + static CONTEXT data; + return &data; + } + + enum class crash_status { running, crashed, normal_exit, ending }; + + static crash_status &crashed() { + static crash_status data; + return data; + } + + static std::mutex &mtx() { + static std::mutex data; + return data; + } + + static std::condition_variable &cv() { + static std::condition_variable data; + return data; + } + + static HANDLE &thread_handle() { + static HANDLE handle; + return handle; + } + + std::thread reporter_thread_; + + // TODO: how not to hardcode these? + static const constexpr int signal_skip_recs = +#ifdef __clang__ + // With clang, RtlCaptureContext also captures the stack frame of the + // current function Below that, there are 3 internal Windows functions + 4 +#else + // With MSVC cl, RtlCaptureContext misses the stack frame of the current + // function The first entries during StackWalk are the 3 internal Windows + // functions + 3 +#endif + ; + + static int &skip_recs() { + static int data; + return data; + } + + static inline void terminator() { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void signal_handler(int) { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void __cdecl invalid_parameter_handler(const wchar_t *, + const wchar_t *, + const wchar_t *, + unsigned int, + uintptr_t) { + crash_handler(signal_skip_recs); + abort(); + } + + NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) { + // The exception info supplies a trace from exactly where the issue was, + // no need to skip records + crash_handler(0, info->ContextRecord); + return EXCEPTION_CONTINUE_SEARCH; + } + + NOINLINE static void crash_handler(int skip, CONTEXT *ct = nullptr) { + + if (ct == nullptr) { + RtlCaptureContext(ctx()); + } else { + memcpy(ctx(), ct, sizeof(CONTEXT)); + } + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &thread_handle(), 0, FALSE, + DUPLICATE_SAME_ACCESS); + + skip_recs() = skip; + + { + std::unique_lock lk(mtx()); + crashed() = crash_status::crashed; + } + + cv().notify_one(); + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::crashed; }); + } + } + + static void handle_stacktrace(int skip_frames = 0) { + // printer creates the TraceResolver, which can supply us a machine type + // for stack walking. Without this, StackTrace can only guess using some + // macros. + // StackTrace also requires that the PDBs are already loaded, which is done + // in the constructor of TraceResolver + Printer printer; + + StackTrace st; + st.set_machine_type(printer.resolver().machine_type()); + st.set_thread_handle(thread_handle()); + st.load_here(32 + skip_frames, ctx()); + st.skip_n_firsts(skip_frames); + + printer.address = true; + printer.print(st, std::cerr); + } +}; + +#endif // BACKWARD_SYSTEM_WINDOWS + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +class SignalHandling { +public: + SignalHandling(const std::vector & = std::vector()) {} + bool init() { return false; } + bool loaded() { return false; } +}; + +#endif // BACKWARD_SYSTEM_UNKNOWN + +} // namespace backward + +#endif /* H_GUARD */ diff --git a/src/common/concurrent/concurrent_lock.h b/src/common/concurrent/concurrent_lock.h index 0352d3a24..a302c947b 100644 --- a/src/common/concurrent/concurrent_lock.h +++ b/src/common/concurrent/concurrent_lock.h @@ -62,8 +62,8 @@ namespace concurrent void spinlock() const { __spinlock(); } protected: - mutable std::mutex m_mutex; //! Mutex used for change locking. - mutable bool m_locked = false; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + mutable std::mutex m_mutex; //!< Mutex used for change locking. + mutable bool m_locked = false; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. /** * @brief Lock the object. diff --git a/src/common/concurrent/concurrent_shared_lock.h b/src/common/concurrent/concurrent_shared_lock.h new file mode 100644 index 000000000..72b67be7d --- /dev/null +++ b/src/common/concurrent/concurrent_shared_lock.h @@ -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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file concurrent_shared_lock.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_CONCURRENT_SHARED_LOCK_H__) +#define __CONCURRENCY_CONCURRENT_SHARED_LOCK_H__ + +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Base class for a concurrently shared locked container. + * @ingroup concurrency + */ + class concurrent_shared_lock + { + public: + /** + * @brief Initializes a new instance of the concurrent_shared_lock class. + */ + concurrent_shared_lock() : + m_mutex() + { + /* stub */ + } + + /** + * @brief Locks the object. + */ + void lock() const { __lock(); } + /** + * @brief Unlocks the object. + */ + void unlock() const { __unlock(); } + + /** + * @brief Share locks the object. + */ + void shared_lock() const { __shared_lock(); } + /** + * @brief Share unlocks the object. + */ + void shared_unlock() const { __shared_unlock(); } + + protected: + mutable std::shared_timed_mutex m_mutex; //!< Mutex used for locking. + + /** + * @brief Lock the object. + */ + inline void __lock() const { m_mutex.lock(); } + /** + * @brief Lock the object. + */ + inline void __shared_lock() const { m_mutex.lock_shared(); } + + /** + * @brief Unlock the object. + */ + inline void __unlock() const { m_mutex.unlock(); } + /** + * @brief Unlock the object. + */ + inline void __shared_unlock() const { m_mutex.unlock_shared(); } + }; +} // namespace concurrent + +#endif // __CONCURRENCY_CONCURRENT_SHARED_LOCK_H__ diff --git a/src/common/concurrent/shared_unordered_map.h b/src/common/concurrent/shared_unordered_map.h new file mode 100644 index 000000000..bbcaa8b91 --- /dev/null +++ b/src/common/concurrent/shared_unordered_map.h @@ -0,0 +1,369 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file shared_unordered_map.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_SHARED_UNORDERED_MAP_H__) +#define __CONCURRENCY_SHARED_UNORDERED_MAP_H__ + +#include "common/concurrent/concurrent_shared_lock.h" +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe share-locked std::unordered_map. Read operations + * must use shared_lock()/shared_unlock() to ensure thread-safety. (This includes iterators.) + * @ingroup concurrency + */ + template + class shared_unordered_map : public concurrent_shared_lock + { + using __std = std::unordered_map; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the shared_unordered_map class. + */ + shared_unordered_map() : concurrent_shared_lock(), + m_map() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the shared_unordered_map class. + * @param size Initial size of the shared_unordered_map. + */ + shared_unordered_map(size_t size) : concurrent_shared_lock(), + m_map(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the shared_unordered_map class. + */ + virtual ~shared_unordered_map() + { + m_map.clear(); + } + + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + shared_unordered_map& operator=(const shared_unordered_map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + shared_unordered_map& operator=(const std::unordered_map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + shared_unordered_map& operator=(shared_unordered_map& other) + { + __lock(); + m_map = other.m_map; + __unlock(); + return *this; + } + /** + * @brief Unordered map assignment operator. + * @param other A map of identical element and allocator types. + */ + shared_unordered_map& operator=(std::unordered_map& other) + { + __lock(); + m_map = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a unordered_map. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_map.assign(size, value); + __unlock(); + } + + /** + * @brief Returns a read/write iterator that points to the first + * element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + return m_map.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + return m_map.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + return m_map.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + return m_map.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + return m_map.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the unordered_map. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + return m_map.cend(); + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& operator[](const Key& key) + { + return m_map[key]; + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& operator[](const Key& key) const + { + return m_map[key]; + } + + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns T& Element at the specified key. + */ + T& at(const Key& key) + { + return m_map.at(key); + } + /** + * @brief Gets the element at the specified key. + * @param key Key of the element to get. + * @returns const T& Element at the specified key. + */ + const T& at(const Key& key) const + { + return m_map.at(key); + } + + /** + * @brief Gets the total number of elements in the unordered_map. + * @returns size_t Total number of elements in the unordered_map. + */ + size_t size() const + { + return m_map.size(); + } + + /** + * @brief Checks if the unordered_map is empty. + * @returns bool True if the unordered_map is empty, false otherwise. + */ + bool empty() const + { + return m_map.empty(); + } + + /** + * @brief Checks if the unordered_map contains the specified key. + * @param key Key to check. + * @returns bool True if the unordered_map contains the specified key, false otherwise. + */ + bool contains(const Key& key) const + { + return m_map.contains(key); + } + + /** + * @brief Inserts a new element into the unordered_map. + * @param key Key of the element to insert. + * @param value Value of the element to insert. + */ + void insert(const Key& key, const T& value) + { + __lock(); + m_map.insert({key, value}); + __unlock(); + } + + /** + * @brief Removes the element at the specified key. + * @param key Key of the element to remove. + */ + void erase(const Key& key) + { + __lock(); + m_map.erase(key); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_map.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_map.erase(first, last); + __unlock(); + } + + /** + * @brief Clears the unordered_map. + */ + void clear() + { + __lock(); + m_map.clear(); + __unlock(); + } + + /** + * @brief Tries to locate an element in an unordered_map. + * @param key Key to be located. + * @return iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + iterator find(const Key& key) + { + return m_map.find(key); + } + /** + * @brief Tries to locate an element in an unordered_map. + * @param key Key to be located. + * @return const_iterator Iterator pointing to sought-after element, or end() if not + * found. + */ + const_iterator find(const Key& key) const + { + return m_map.find(key); + } + + /** + * @brief Finds the number of elements. + * @param key Key to count. + * @return size_t Number of elements with specified key. + */ + size_t count(const Key& key) const + { + return m_map.count(key); + } + + /** + * @brief Gets the underlying unordered_map. + * @returns std::unordered_map& Underlying unordered_map. + */ + std::unordered_map& get() + { + return m_map; + } + /** + * @brief Gets the underlying unordered_map. + * @returns const std::unordered_map& Underlying unordered_map. + */ + const std::unordered_map& get() const + { + return m_map; + } + + /** + * @brief Prepare the underlying unordered_map for a specified number of + * elements. + * @param n Number of elements required. + */ + void reserve(size_t n) + { + m_map.reserve(n); + } + + private: + std::unordered_map m_map; + }; +} // namespace concurrent + +#endif // __CONCURRENCY_SHARED_UNORDERED_MAP_H__ diff --git a/src/common/concurrent/shared_vector.h b/src/common/concurrent/shared_vector.h new file mode 100644 index 000000000..34aac3ac7 --- /dev/null +++ b/src/common/concurrent/shared_vector.h @@ -0,0 +1,426 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file shared_vector.h + * @ingroup concurrency + */ +#if !defined(__CONCURRENCY_SHARED_VECTOR_H__) +#define __CONCURRENCY_SHARED_VECTOR_H__ + +#include "common/concurrent/concurrent_shared_lock.h" +#include "common/Thread.h" + +#include +#include + +namespace concurrent +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Thread-safe share-locked std::vector. Read operations + * must use shared_lock()/shared_unlock() to ensure thread-safety. (This includes iterators.) + * @ingroup concurrency + */ + template + class shared_vector : public concurrent_shared_lock + { + using __std = std::vector; + public: + using iterator = typename __std::iterator; + using const_iterator = typename __std::const_iterator; + + /** + * @brief Initializes a new instance of the shared_vector class. + */ + shared_vector() : concurrent_shared_lock(), + m_vector() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the shared_vector class. + * @param size Initial size of the vector. + */ + shared_vector(size_t size) : concurrent_shared_lock(), + m_vector(size) + { + /* stub */ + } + /** + * @brief Finalizes a instance of the shared_vector class. + */ + virtual ~shared_vector() + { + m_vector.clear(); + } + + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + shared_vector& operator=(const shared_vector& other) + { + __lock(); + m_vector = other.m_vector; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + shared_vector& operator=(const std::vector& other) + { + __lock(); + m_vector = other; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + shared_vector& operator=(shared_vector& other) + { + __lock(); + m_vector = other.m_vector; + __unlock(); + return *this; + } + /** + * @brief Vector assignment operator. + * @param other A vector of identical element and allocator types. + */ + shared_vector& operator=(std::vector& other) + { + __lock(); + m_vector = other; + __unlock(); + return *this; + } + + /** + * @brief Assigns a given value to a %vector. + * @param size Number of elements to be assigned. + * @param value Value to be assigned. + */ + void assign(size_t size, const T& value) + { + __lock(); + m_vector.assign(size, value); + __unlock(); + } + + /** + * @brief Returns a read/write iterator that points to the first + * element in the vector. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator begin() + { + return m_vector.begin(); + } + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator begin() const + { + return m_vector.begin(); + } + /** + * @brief Returns a read/write iterator that points one past the last + * element in the vector. Iteration is done in ordinary + * element order. + * @returns iterator + */ + iterator end() + { + return m_vector.end(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator end() const + { + return m_vector.end(); + } + + /** + * @brief Returns a read-only (constant) iterator that points to the + * first element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cbegin() const + { + return m_vector.cbegin(); + } + /** + * @brief Returns a read-only (constant) iterator that points one past + * the last element in the vector. Iteration is done in ordinary + * element order. + * @returns const_iterator + */ + const_iterator cend() const + { + return m_vector.cend(); + } + + /** + * @brief Gets the number of elements in the vector. + * @returns size_t Number of elements in the vector. + */ + size_t size() const + { + return m_vector.size(); + } + /** + * @brief Resizes the %vector to the specified number of elements. + * @param size Number of elements the %vector should contain. + */ + void resize(size_t size) + { + __lock(); + m_vector.resize(size); + __unlock(); + } + + /** + * @brief Returns the total number of elements that the %vector can + * hold before needing to allocate more memory. + * @returns size_t + */ + size_t capacity() const + { + return m_vector.capacity(); + } + + /** + * @brief Checks if the vector is empty. + * @returns bool True if the vector is empty, false otherwise. + */ + bool empty() const + { + return m_vector.empty(); + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& operator[](size_t index) + { + return m_vector[index]; + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& operator[](size_t index) const + { + return m_vector[index]; + } + + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns T& Element at the specified index. + */ + T& at(size_t index) + { + return m_vector.at(index); + } + /** + * @brief Gets the element at the specified index. + * @param index Index of the element to get. + * @returns const T& Element at the specified index. + */ + const T& at(size_t index) const + { + return m_vector.at(index); + } + + /** + * @brief Gets the first element of the vector. + * @returns T& First element of the vector. + */ + T& front() + { + return m_vector.front(); + } + /** + * @brief Gets the first element of the vector. + * @returns const T& First element of the vector. + */ + const T& front() const + { + return m_vector.front(); + } + + /** + * @brief Gets the last element of the vector. + * @returns T& Last element of the vector. + */ + T& back() + { + return m_vector.back(); + } + /** + * @brief Gets the last element of the vector. + * @returns const T& Last element of the vector. + */ + const T& back() const + { + return m_vector.back(); + } + + /** + * @brief Adds an element to the end of the vector. + * @param value Value to add. + */ + void push_back(const T& value) + { + __lock(); + m_vector.push_back(value); + __unlock(); + } + /** + * @brief Adds an element to the end of the vector. + * @param value Value to add. + */ + void push_back(T&& value) + { + __lock(); + m_vector.push_back(std::move(value)); + __unlock(); + } + + /** + * @brief Removes last element. + */ + void pop_back() + { + __lock(); + m_vector.pop_back(); + __unlock(); + } + + /** + * @brief Inserts given value into vector before specified iterator. + * @param position A const_iterator into the vector. + * @param value Data to be inserted. + * @return iterator An iterator that points to the inserted data. + */ + iterator insert(iterator position, const T& value) + { + __lock(); + auto it = m_vector.insert(position, value); + __unlock(); + return it; + } + + /** + * @brief Removes the element at the specified index. + * @param index Index of the element to remove. + */ + void erase(size_t index) + { + __lock(); + m_vector.erase(m_vector.begin() + index); + __unlock(); + } + /** + * @brief Removes the element at the specified iterator. + * @param position Iterator of the element to remove. + */ + void erase(const_iterator position) + { + __lock(); + m_vector.erase(position); + __unlock(); + } + /** + * @brief Removes the elements in the specified range. + * @param first Iterator of the first element to remove. + * @param last Iterator of the last element to remove. + */ + void erase(const_iterator first, const_iterator last) + { + __lock(); + m_vector.erase(first, last); + __unlock(); + } + + /** + * @brief Swaps data with another vector. + * @param other A vector of the same element and allocator types. + */ + void swap(shared_vector& other) + { + __lock(); + m_vector.swap(other.m_vector); + __unlock(); + } + + /** + * @brief Clears the vector. + */ + void clear() + { + __lock(); + m_vector.clear(); + __unlock(); + } + + /** + * @brief Gets the underlying vector. + * @returns std::vector& Underlying vector. + */ + std::vector& get() + { + return m_vector; + } + /** + * @brief Gets the underlying vector. + * @returns const std::vector& Underlying vector. + */ + const std::vector& get() const + { + return m_vector; + } + + /** + * @brief Prepare the underlying vector for a specified number of + * elements. + * @param n Number of elements required. + */ + void reserve(size_t n) + { + m_vector.reserve(n); + } + + private: + std::vector m_vector; + }; +} // namespace concurrent + +#endif // __CONCURRENCY_SHARED_VECTOR_H__ diff --git a/src/common/concurrent/unordered_map.h b/src/common/concurrent/unordered_map.h index 0a131c383..715d8fc55 100644 --- a/src/common/concurrent/unordered_map.h +++ b/src/common/concurrent/unordered_map.h @@ -368,6 +368,16 @@ namespace concurrent return m_map; } + /** + * @brief Prepare the underlying unordered_map for a specified number of + * elements. + * @param n Number of elements required. + */ + void reserve(size_t n) + { + m_map.reserve(n); + } + private: std::unordered_map m_map; }; diff --git a/src/common/concurrent/vector.h b/src/common/concurrent/vector.h index a365bf332..d395cd188 100644 --- a/src/common/concurrent/vector.h +++ b/src/common/concurrent/vector.h @@ -430,6 +430,16 @@ namespace concurrent return m_vector; } + /** + * @brief Prepare the underlying vector for a specified number of + * elements. + * @param n Number of elements required. + */ + void reserve(size_t n) + { + m_vector.reserve(n); + } + private: std::vector m_vector; }; diff --git a/src/common/dmr/DMRDefines.h b/src/common/dmr/DMRDefines.h index 2277faa29..187912dda 100644 --- a/src/common/dmr/DMRDefines.h +++ b/src/common/dmr/DMRDefines.h @@ -112,12 +112,14 @@ namespace dmr const uint32_t MAX_PDU_COUNT = 32U; - const uint32_t DMR_PDU_UNCONFIRMED_LENGTH_BYTES = 12U; - const uint32_t DMR_PDU_CONFIRMED_LENGTH_BYTES = 18U; - const uint32_t DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES = 16U; - const uint32_t DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES = 10U; + const uint32_t DMR_PDU_HALFRATE_LENGTH_BYTES = 12U; + const uint32_t DMR_PDU_THREEQUARTER_LENGTH_BYTES = 18U; const uint32_t DMR_PDU_UNCODED_LENGTH_BYTES = 24U; + const uint32_t DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES = 16U; + const uint32_t DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES = 10U; + const uint32_t DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES = 22U; + const uint32_t MI_LENGTH_BYTES = 4U; // This was guessed based on OTA data captures -- the message indicator seems to be the same length as a source/destination address const uint32_t RAW_AMBE_LENGTH_BYTES = 9U; /** @} */ @@ -147,16 +149,16 @@ namespace dmr const uint16_t DMR_LOGICAL_CH_ABSOLUTE = 0xFFFU; - const uint32_t WUID_SUPLI = 0xFFFEC4U; //! Supplementary Data Service Working Unit ID - const uint32_t WUID_SDMI = 0xFFFEC5U; //! UDT Short Data Service Working Unit ID - const uint32_t WUID_REGI = 0xFFFEC6U; //! Registration Working Unit ID - const uint32_t WUID_STUNI = 0xFFFECCU; //! MS Stun/Revive Identifier - const uint32_t WUID_AUTHI = 0xFFFECDU; //! Authentication Working Unit ID - const uint32_t WUID_KILLI = 0xFFFECFU; //! MS Kill Identifier - const uint32_t WUID_TATTSI = 0xFFFED7U; //! Talkgroup Subscription/Attachement Service Working Unit ID - const uint32_t WUID_ALLL = 0xFFFFFDU; //! All-call Site-wide Working Unit ID - const uint32_t WUID_ALLZ = 0xFFFFFEU; //! All-call System-wide Working Unit ID - const uint32_t WUID_ALL = 0xFFFFFFU; //! All-call Network-wide Working Unit ID + const uint32_t WUID_SUPLI = 0xFFFEC4U; //!< Supplementary Data Service Working Unit ID + const uint32_t WUID_SDMI = 0xFFFEC5U; //!< UDT Short Data Service Working Unit ID + const uint32_t WUID_REGI = 0xFFFEC6U; //!< Registration Working Unit ID + const uint32_t WUID_STUNI = 0xFFFECCU; //!< MS Stun/Revive Identifier + const uint32_t WUID_AUTHI = 0xFFFECDU; //!< Authentication Working Unit ID + const uint32_t WUID_KILLI = 0xFFFECFU; //!< MS Kill Identifier + const uint32_t WUID_TATTSI = 0xFFFED7U; //!< Talkgroup Subscription/Attachement Service Working Unit ID + const uint32_t WUID_ALLL = 0xFFFFFDU; //!< All-call Site-wide Working Unit ID + const uint32_t WUID_ALLZ = 0xFFFFFEU; //!< All-call System-wide Working Unit ID + const uint32_t WUID_ALL = 0xFFFFFFU; //!< All-call Network-wide Working Unit ID const uint32_t NO_HEADERS_SIMPLEX = 8U; const uint32_t NO_HEADERS_DUPLEX = 3U; @@ -167,13 +169,13 @@ namespace dmr namespace DPF { /** @brief Data Packet Format */ enum E : uint8_t { - UDT = 0x00U, //! Unified Data Transport Header - RESPONSE = 0x01U, //! Response Data Header - UNCONFIRMED_DATA = 0x02U, //! Unconfirmed Data Header - CONFIRMED_DATA = 0x03U, //! Confirmed Data Header - DEFINED_SHORT = 0x0DU, //! Defined Short Data Header - DEFINED_RAW = 0x0EU, //! Defined Raw Data Header - PROPRIETARY = 0x0FU, //! Proprietary + UDT = 0x00U, //!< Unified Data Transport Header + RESPONSE = 0x01U, //!< Response Data Header + UNCONFIRMED_DATA = 0x02U, //!< Unconfirmed Data Header + CONFIRMED_DATA = 0x03U, //!< Confirmed Data Header + DEFINED_SHORT = 0x0DU, //!< Defined Short Data Header + DEFINED_RAW = 0x0EU, //!< Defined Raw Data Header + PROPRIETARY = 0x0FU, //!< Proprietary }; }; @@ -181,9 +183,9 @@ namespace dmr namespace PDUResponseClass { /** @brief Data Response Class */ enum : uint8_t { - ACK = 0x00U, //! Acknowledge - NACK = 0x01U, //! Negative Acknowledge - ACK_RETRY = 0x02U //! Acknowlege Retry + ACK = 0x00U, //!< Acknowledge + NACK = 0x01U, //!< Negative Acknowledge + ACK_RETRY = 0x02U //!< Acknowlege Retry }; }; @@ -191,15 +193,20 @@ namespace dmr namespace PDUResponseType { /** @brief Data Response Type */ enum : uint8_t { - ACK = 0x01U, //! Acknowledge + ACK = 0x01U, //!< Acknowledge - NACK_ILLEGAL = 0x00U, //! Illegal Format - NACK_PACKET_CRC = 0x01U, //! Packet CRC - NACK_MEMORY_FULL = 0x02U, //! Memory Full - NACK_UNDELIVERABLE = 0x04U //! Undeliverable + NACK_ILLEGAL = 0x00U, //!< Illegal Format + NACK_PACKET_CRC = 0x01U, //!< Packet CRC + NACK_MEMORY_FULL = 0x02U, //!< Memory Full + NACK_UNDELIVERABLE = 0x04U //!< Undeliverable }; }; + /** @brief ARP Request */ + const uint8_t DMR_PDU_ARP_REQUEST = 0x01U; + /** @brief ARP Reply */ + const uint8_t DMR_PDU_ARP_REPLY = 0x02U; + /** @name Feature Set IDs */ /** @brief ETSI Standard Feature Set */ const uint8_t FID_ETSI = 0x00U; @@ -239,10 +246,10 @@ namespace dmr namespace SLCO { /** @brief Short-Link Control Opcode(s) */ enum E : uint8_t { - NONE = 0x00U, //! NULL + NONE = 0x00U, //!< NULL ACT = 0x01U, //! - TSCC = 0x02U, //! TSCC - PAYLOAD = 0x03U //! Payload + TSCC = 0x02U, //!< TSCC + PAYLOAD = 0x03U //!< Payload }; } @@ -250,8 +257,8 @@ namespace dmr namespace FLCO { /** @brief Full-Link Control Opcode(s) */ enum E : uint8_t { - GROUP = 0x00U, //! GRP VCH USER - Group Voice Channel User - PRIVATE = 0x03U, //! UU VCH USER - Unit-to-Unit Voice Channel User + GROUP = 0x00U, //!< GRP VCH USER - Group Voice Channel User + PRIVATE = 0x03U, //!< UU VCH USER - Unit-to-Unit Voice Channel User TALKER_ALIAS_HEADER = 0x04U, //! TALKER_ALIAS_BLOCK1 = 0x05U, //! @@ -266,12 +273,12 @@ namespace dmr namespace ExtendedFunctions { /** @brief FID_MOT Extended Functions. */ enum : uint16_t { - CHECK = 0x0000U, //! Radio Check - UNINHIBIT = 0x007EU, //! Radio Uninhibit - INHIBIT = 0x007FU, //! Radio Inhibit - CHECK_ACK = 0x0080U, //! Radio Check Ack - UNINHIBIT_ACK = 0x00FEU, //! Radio Uninhibit Ack - INHIBIT_ACK = 0x00FFU //! Radio Inhibit Ack + CHECK = 0x0000U, //!< Radio Check + UNINHIBIT = 0x007EU, //!< Radio Uninhibit + INHIBIT = 0x007FU, //!< Radio Inhibit + CHECK_ACK = 0x0080U, //!< Radio Check Ack + UNINHIBIT_ACK = 0x00FEU, //!< Radio Uninhibit Ack + INHIBIT_ACK = 0x00FFU //!< Radio Inhibit Ack }; }; @@ -279,30 +286,31 @@ namespace dmr namespace DataType { /** @brief Data Type(s) */ enum E : uint8_t { - VOICE_PI_HEADER = 0x00U, //! Voice with Privacy Indicator Header - VOICE_LC_HEADER = 0x01U, //! Voice with Link Control Header + VOICE_PI_HEADER = 0x00U, //!< Voice with Privacy Indicator Header + VOICE_LC_HEADER = 0x01U, //!< Voice with Link Control Header - TERMINATOR_WITH_LC = 0x02U, //! Terminator with Link Control + TERMINATOR_WITH_LC = 0x02U, //!< Terminator with Link Control - CSBK = 0x03U, //! CSBK + CSBK = 0x03U, //!< CSBK - MBC_HEADER = 0x04U, //! Multi-Block Control Header - MBC_DATA = 0x05U, //! Multi-Block Control Data + MBC_HEADER = 0x04U, //!< Multi-Block Control Header + MBC_DATA = 0x05U, //!< Multi-Block Control Data - DATA_HEADER = 0x06U, //! Data Header - RATE_12_DATA = 0x07U, //! 1/2 Rate Data - RATE_34_DATA = 0x08U, //! 3/4 Rate Data + DATA_HEADER = 0x06U, //!< Data Header + RATE_12_DATA = 0x07U, //!< 1/2 Rate Data + RATE_34_DATA = 0x08U, //!< 3/4 Rate Data - IDLE = 0x09U, //! Idle + IDLE = 0x09U, //!< Idle - RATE_1_DATA = 0x0AU, //! Rate 1 Data + RATE_1_DATA = 0x0AU, //!< Rate 1 Data /* ** Internal Data Type(s) */ - VOICE_SYNC = 0xF0U, //! Internal - Voice Sync - VOICE = 0xF1U //! Internal - Voice + VOICE_SYNC = 0xF0U, //!< Internal - Voice Sync + VOICE = 0xF1U, //!< Internal - Voice + GENERIC_DATA = 0xF2U //!< Internal - Data }; } @@ -321,10 +329,10 @@ namespace dmr namespace SiteModel { /** @brief Site Models */ enum E : uint8_t { - SM_TINY = 0x00U, //! Tiny - SM_SMALL = 0x01U, //! Small - SM_LARGE = 0x02U, //! Large - SM_HUGE = 0x03U //! Huge + SM_TINY = 0x00U, //!< Tiny + SM_SMALL = 0x01U, //!< Small + SM_LARGE = 0x02U, //!< Large + SM_HUGE = 0x03U //!< Huge }; } @@ -337,13 +345,13 @@ namespace dmr namespace TalkerID { /** @brief Talker ID */ enum : uint8_t { - NONE = 0x00U, //! No Talker ID + NONE = 0x00U, //!< No Talker ID - HEADER = 0x01U, //! Talker ID Header + HEADER = 0x01U, //!< Talker ID Header - BLOCK1 = 0x02U, //! Talker ID Block 1 - BLOCK2 = 0x04U, //! Talker ID Block 2 - BLOCK3 = 0x08U //! Talker ID Block 3 + BLOCK1 = 0x02U, //!< Talker ID Block 1 + BLOCK2 = 0x04U, //!< Talker ID Block 2 + BLOCK3 = 0x08U //!< Talker ID Block 3 }; } @@ -351,34 +359,34 @@ namespace dmr namespace ReasonCode { /** @brief Reason Code(s) */ enum : uint8_t { - TS_ACK_RSN_MSG = 0x60U, //! TS - Message Accepted - TS_ACK_RSN_REG = 0x62U, //! TS - Registration Accepted - TS_ACK_RSN_AUTH_RESP = 0x64U, //! TS - Authentication Challenge Response - TS_ACK_RSN_REG_SUB_ATTACH = 0x65U, //! TS - Registration Response with subscription - MS_ACK_RSN_MSG = 0x44U, //! MS - Message Accepted - MS_ACK_RSN_AUTH_RESP = 0x48U, //! MS - Authentication Challenge Response - - TS_DENY_RSN_SYS_UNSUPPORTED_SVC = 0x20U,//! System Unsupported Service - TS_DENY_RSN_PERM_USER_REFUSED = 0x21U, //! User Permenantly Refused - TS_DENY_RSN_TEMP_USER_REFUSED = 0x22U, //! User Temporarily Refused - TS_DENY_RSN_TRSN_SYS_REFUSED = 0x23U, //! System Refused - TS_DENY_RSN_TGT_NOT_REG = 0x24U, //! Target Not Registered - TS_DENY_RSN_TGT_UNAVAILABLE = 0x25U, //! Target Unavailable - TS_DENY_RSN_SYS_BUSY = 0x27U, //! System Busy - TS_DENY_RSN_SYS_NOT_READY = 0x28U, //! System Not Ready - TS_DENY_RSN_CALL_CNCL_REFUSED = 0x29U, //! Call Cancel Refused - TS_DENY_RSN_REG_REFUSED = 0x2AU, //! Registration Refused - TS_DENY_RSN_REG_DENIED = 0x2BU, //! Registration Denied - TS_DENY_RSN_MS_NOT_REG = 0x2DU, //! MS Not Registered - TS_DENY_RSN_TGT_BUSY = 0x2EU, //! Target Busy - TS_DENY_RSN_TGT_GROUP_NOT_VALID = 0x2FU,//! Group Not Valid - - TS_QUEUED_RSN_NO_RESOURCE = 0xA0U, //! No Resources Available - TS_QUEUED_RSN_SYS_BUSY = 0xA1U, //! System Busy - - TS_WAIT_RSN = 0xE0U, //! Wait - - MS_DENY_RSN_UNSUPPORTED_SVC = 0x00U, //! Service Unsupported + TS_ACK_RSN_MSG = 0x60U, //!< TS - Message Accepted + TS_ACK_RSN_REG = 0x62U, //!< TS - Registration Accepted + TS_ACK_RSN_AUTH_RESP = 0x64U, //!< TS - Authentication Challenge Response + TS_ACK_RSN_REG_SUB_ATTACH = 0x65U, //!< TS - Registration Response with subscription + MS_ACK_RSN_MSG = 0x44U, //!< MS - Message Accepted + MS_ACK_RSN_AUTH_RESP = 0x48U, //!< MS - Authentication Challenge Response + + TS_DENY_RSN_SYS_UNSUPPORTED_SVC = 0x20U,//!< System Unsupported Service + TS_DENY_RSN_PERM_USER_REFUSED = 0x21U, //!< User Permenantly Refused + TS_DENY_RSN_TEMP_USER_REFUSED = 0x22U, //!< User Temporarily Refused + TS_DENY_RSN_TRSN_SYS_REFUSED = 0x23U, //!< System Refused + TS_DENY_RSN_TGT_NOT_REG = 0x24U, //!< Target Not Registered + TS_DENY_RSN_TGT_UNAVAILABLE = 0x25U, //!< Target Unavailable + TS_DENY_RSN_SYS_BUSY = 0x27U, //!< System Busy + TS_DENY_RSN_SYS_NOT_READY = 0x28U, //!< System Not Ready + TS_DENY_RSN_CALL_CNCL_REFUSED = 0x29U, //!< Call Cancel Refused + TS_DENY_RSN_REG_REFUSED = 0x2AU, //!< Registration Refused + TS_DENY_RSN_REG_DENIED = 0x2BU, //!< Registration Denied + TS_DENY_RSN_MS_NOT_REG = 0x2DU, //!< MS Not Registered + TS_DENY_RSN_TGT_BUSY = 0x2EU, //!< Target Busy + TS_DENY_RSN_TGT_GROUP_NOT_VALID = 0x2FU,//!< Group Not Valid + + TS_QUEUED_RSN_NO_RESOURCE = 0xA0U, //!< No Resources Available + TS_QUEUED_RSN_SYS_BUSY = 0xA1U, //!< System Busy + + TS_WAIT_RSN = 0xE0U, //!< Wait + + MS_DENY_RSN_UNSUPPORTED_SVC = 0x00U, //!< Service Unsupported }; } @@ -386,19 +394,19 @@ namespace dmr namespace ServiceKind { /** @brief Random Access Service Kind */ enum : uint8_t { - IND_VOICE_CALL = 0x00U, //! Individual Voice Call - GRP_VOICE_CALL = 0x01U, //! Group Voice Call - IND_DATA_CALL = 0x02U, //! Individual Data Call - GRP_DATA_CALL = 0x03U, //! Group Data Call - IND_UDT_DATA_CALL = 0x04U, //! Individual UDT Short Data Call - GRP_UDT_DATA_CALL = 0x05U, //! Group UDT Short Data Call - UDT_SHORT_POLL = 0x06U, //! UDT Short Data Polling Service - STATUS_TRANSPORT = 0x07U, //! Status Transport Service - CALL_DIVERSION = 0x08U, //! Call Diversion Service - CALL_ANSWER = 0x09U, //! Call Answer Service - SUPPLEMENTARY_SVC = 0x0DU, //! Supplementary Service - REG_SVC = 0x0EU, //! Registration Service - CANCEL_CALL = 0x0FU //! Cancel Call Service + IND_VOICE_CALL = 0x00U, //!< Individual Voice Call + GRP_VOICE_CALL = 0x01U, //!< Group Voice Call + IND_DATA_CALL = 0x02U, //!< Individual Data Call + GRP_DATA_CALL = 0x03U, //!< Group Data Call + IND_UDT_DATA_CALL = 0x04U, //!< Individual UDT Short Data Call + GRP_UDT_DATA_CALL = 0x05U, //!< Group UDT Short Data Call + UDT_SHORT_POLL = 0x06U, //!< UDT Short Data Polling Service + STATUS_TRANSPORT = 0x07U, //!< Status Transport Service + CALL_DIVERSION = 0x08U, //!< Call Diversion Service + CALL_ANSWER = 0x09U, //!< Call Answer Service + SUPPLEMENTARY_SVC = 0x0DU, //!< Supplementary Service + REG_SVC = 0x0EU, //!< Registration Service + CANCEL_CALL = 0x0FU //!< Cancel Call Service }; } @@ -406,14 +414,14 @@ namespace dmr namespace BroadcastAnncType { /** @brief Broadcast Announcement Type(s) */ enum : uint8_t { - ANN_WD_TSCC = 0x00U, //! Announce-Withdraw TSCC Channel - CALL_TIMER_PARMS = 0x01U, //! Specify Call Timer Parameters - VOTE_NOW = 0x02U, //! Vote Now Advice - LOCAL_TIME = 0x03U, //! Broadcast Local Time - MASS_REG = 0x04U, //! Mass Registration - CHAN_FREQ = 0x05U, //! Logical Channel/Frequency - ADJ_SITE = 0x06U, //! Adjacent Site Information - SITE_PARMS = 0x07U //! General Site Parameters + ANN_WD_TSCC = 0x00U, //!< Announce-Withdraw TSCC Channel + CALL_TIMER_PARMS = 0x01U, //!< Specify Call Timer Parameters + VOTE_NOW = 0x02U, //!< Vote Now Advice + LOCAL_TIME = 0x03U, //!< Broadcast Local Time + MASS_REG = 0x04U, //!< Mass Registration + CHAN_FREQ = 0x05U, //!< Logical Channel/Frequency + ADJ_SITE = 0x06U, //!< Adjacent Site Information + SITE_PARMS = 0x07U //!< General Site Parameters }; } @@ -423,28 +431,28 @@ namespace dmr enum : uint8_t { // CSBK ISP/OSP Shared Opcode(s) NONE = 0x00U, //! - UU_V_REQ = 0x04U, //! UU VCH REQ - Unit-to-Unit Voice Channel Request - UU_ANS_RSP = 0x05U, //! UU ANS RSP - Unit-to-Unit Answer Response - CTCSBK = 0x07U, //! CT CSBK - Channel Timing CSBK - ALOHA = 0x19U, //! ALOHA - Aloha PDU for Random Access - AHOY = 0x1CU, //! AHOY - Enquiry from TSCC - RAND = 0x1FU, //! (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert - ACK_RSP = 0x20U, //! ACK RSP - Acknowledge Response - EXT_FNCT = 0x24U, //! (DMRA) EXT FNCT - Extended Function - NACK_RSP = 0x26U, //! NACK RSP - Negative Acknowledgement Response - BROADCAST = 0x28U, //! BCAST - Announcement PDU - MAINT = 0x2AU, //! MAINT - Call Maintainence PDU - P_CLEAR = 0x2EU, //! P_CLEAR - Payload Channel Clear - PV_GRANT = 0x30U, //! PV_GRANT - Private Voice Channel Grant - TV_GRANT = 0x31U, //! TV_GRANT - Talkgroup Voice Channel Grant - BTV_GRANT = 0x32U, //! BTV_GRANT - Broadcast Talkgroup Voice Channel Grant - PD_GRANT = 0x33U, //! PD_GRANT - Private Data Channel Grant - TD_GRANT = 0x34U, //! TD_GRANT - Talkgroup Data Channel Grant - BSDWNACT = 0x38U, //! BS DWN ACT - BS Outbound Activation - PRECCSBK = 0x3DU, //! PRE CSBK - Preamble CSBK + UU_V_REQ = 0x04U, //!< UU VCH REQ - Unit-to-Unit Voice Channel Request + UU_ANS_RSP = 0x05U, //!< UU ANS RSP - Unit-to-Unit Answer Response + CTCSBK = 0x07U, //!< CT CSBK - Channel Timing CSBK + ALOHA = 0x19U, //!< ALOHA - Aloha PDU for Random Access + AHOY = 0x1CU, //!< AHOY - Enquiry from TSCC + RAND = 0x1FU, //!< (ETSI) RAND - Random Access / (DMRA) CALL ALRT - Call Alert + ACK_RSP = 0x20U, //!< ACK RSP - Acknowledge Response + EXT_FNCT = 0x24U, //!< (DMRA) EXT FNCT - Extended Function + NACK_RSP = 0x26U, //!< NACK RSP - Negative Acknowledgement Response + BROADCAST = 0x28U, //!< BCAST - Announcement PDU + MAINT = 0x2AU, //!< MAINT - Call Maintainence PDU + P_CLEAR = 0x2EU, //!< P_CLEAR - Payload Channel Clear + PV_GRANT = 0x30U, //!< PV_GRANT - Private Voice Channel Grant + TV_GRANT = 0x31U, //!< TV_GRANT - Talkgroup Voice Channel Grant + BTV_GRANT = 0x32U, //!< BTV_GRANT - Broadcast Talkgroup Voice Channel Grant + PD_GRANT = 0x33U, //!< PD_GRANT - Private Data Channel Grant + TD_GRANT = 0x34U, //!< TD_GRANT - Talkgroup Data Channel Grant + BSDWNACT = 0x38U, //!< BS DWN ACT - BS Outbound Activation + PRECCSBK = 0x3DU, //!< PRE CSBK - Preamble CSBK // CSBK DVM Outbound Signalling Packet (OSP) Opcode(s) - DVM_GIT_HASH = 0x3FU //! + DVM_GIT_HASH = 0x3FU //!< }; } diff --git a/src/common/dmr/data/Assembler.cpp b/src/common/dmr/data/Assembler.cpp new file mode 100644 index 000000000..a876237d1 --- /dev/null +++ b/src/common/dmr/data/Assembler.cpp @@ -0,0 +1,172 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/data/Assembler.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace dmr; +using namespace dmr::defines; +using namespace dmr::data; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +bool Assembler::s_dumpPDUData = false; +bool Assembler::s_verbose = false; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the Assembler class. */ + +Assembler::Assembler() : + m_blockWriter(nullptr) +{ + /* stub */ +} + +/* Finalizes a instance of the Assembler class. */ + +Assembler::~Assembler() = default; + +/* Helper to assemble user data as a DMR PDU packet. */ + +void Assembler::assemble(data::DataHeader& dataHeader, DataType::E dataType, const uint8_t* pduUserData, + uint32_t* assembledBitLength, void* userContext) +{ + assert(pduUserData != nullptr); + assert(m_blockWriter != nullptr); + + if (assembledBitLength != nullptr) + *assembledBitLength = 0U; + + uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * DMR_FRAME_LENGTH_BITS); + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + + UInt8Array dataArray = std::make_unique((bitLength / 8U) + 1U); + uint8_t* data = dataArray.get(); + ::memset(data, 0x00U, (bitLength / 8U) + 1U); + + uint8_t block[DMR_FRAME_LENGTH_BYTES]; + ::memset(block, 0x00U, DMR_FRAME_LENGTH_BYTES); + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + + if (s_verbose) { + LogInfoEx(LOG_DMR, DMR_DT_DATA_HEADER ", dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", + dataHeader.getDPF(), dataHeader.getA(), dataHeader.getSAP(), dataHeader.getFullMesage(), dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(dataType), + dataHeader.getFSN(), dataHeader.getDstId(), dataHeader.getSrcId(), dataHeader.getGI()); + } + + // generate the PDU header + dataHeader.encode(block); + +#if DEBUG_DMR_PDU_DATA + Utils::dump(1U, "DMR, PDU Assembler Block", block, DMR_FRAME_LENGTH_BYTES); +#endif + + m_blockWriter(userContext, 0U, block, DMR_FRAME_LENGTH_BYTES, false); + + if (pduUserData != nullptr && blocksToFollow > 0U) { + uint32_t dataOffset = 0U; + uint32_t pduLength = dataHeader.getPDULength(dataType) + dataHeader.getPadLength(); + uint32_t dataBlockCnt = 1U; + uint32_t secondHeaderOffset = 0U; + + // we pad 20 bytes of extra space -- confirmed data will use various extra space in the PDU + DECLARE_UINT8_ARRAY(packetData, pduLength + 20U); + + uint32_t packetLength = dataHeader.getPacketLength(dataType); + uint32_t padLength = dataHeader.getPadLength(); +#if DEBUG_DMR_PDU_DATA + LogDebugEx(LOG_DMR, "Assembler::assemble()", "packetLength = %u, secondHeaderOffset = %u, padLength = %u, pduLength = %u", packetLength, secondHeaderOffset, padLength, pduLength); +#endif + ::memcpy(packetData + secondHeaderOffset, pduUserData, packetLength); + edac::CRC::addCRC32(packetData, packetLength + 4U); + + if (padLength > 0U) { + // move the CRC-32 to the end of the packet data after the padding + uint8_t crcBytes[4U]; + ::memcpy(crcBytes, packetData + packetLength, 4U); + ::memset(packetData + packetLength, 0x00U, 4U); + ::memcpy(packetData + (packetLength + padLength), crcBytes, 4U); + } + +#if DEBUG_DMR_PDU_DATA + Utils::dump(1U, "DMR, Assembled PDU User Data", packetData, packetLength + padLength + 4U); +#endif + + // generate the PDU data + for (uint32_t i = 0U; i < blocksToFollow; i++) { + DataBlock dataBlock = DataBlock(); + dataBlock.setFormat(dataHeader); + dataBlock.setSerialNo(i); + dataBlock.setData(packetData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); + + if (s_verbose) { + if (dataType == DataType::RATE_34_DATA) { + LogInfoEx(LOG_DMR, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", + (dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat()); + } else if (dataType == DataType::RATE_12_DATA) { + LogInfoEx(LOG_DMR, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", + (dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat()); + } + else { + LogInfoEx(LOG_DMR, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", + (dataHeader.getDPF() == DPF::CONFIRMED_DATA) ? dataBlock.getSerialNo() : i, dataBlock.getDataType(), dataBlock.getFormat()); + } + } + + ::memset(block, 0x00U, DMR_FRAME_LENGTH_BYTES); + dataBlock.encode(block); + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "DMR, PDU Assembler Block", block, DMR_FRAME_LENGTH_BYTES); +#endif + + m_blockWriter(userContext, dataBlockCnt, block, DMR_FRAME_LENGTH_BYTES, dataBlock.getLastBlock()); + + if (dataHeader.getDPF() == DPF::CONFIRMED_DATA) { + if (dataType == DataType::RATE_34_DATA) { + dataOffset += DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES; + } else if (dataType == DataType::RATE_12_DATA) { + dataOffset += DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES; + } + else { + dataOffset += DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES; + } + } else { + if (dataType == DataType::RATE_34_DATA) { + dataOffset += DMR_PDU_THREEQUARTER_LENGTH_BYTES; + } else if (dataType == DataType::RATE_12_DATA) { + dataOffset += DMR_PDU_HALFRATE_LENGTH_BYTES; + } + else { + dataOffset += DMR_PDU_UNCODED_LENGTH_BYTES; + } + } + + dataBlockCnt++; + } + } + + if (assembledBitLength != nullptr) + *assembledBitLength = bitLength; +} diff --git a/src/common/dmr/data/Assembler.h b/src/common/dmr/data/Assembler.h new file mode 100644 index 000000000..b21038082 --- /dev/null +++ b/src/common/dmr/data/Assembler.h @@ -0,0 +1,94 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file Assembler.h + * @ingroup dmr_pdu + * @file Assembler.cpp + * @ingroup dmr_pdu + */ +#if !defined(__DMR_DATA__ASSEMBLER_H__) +#define __DMR_DATA__ASSEMBLER_H__ + +#include "common/Defines.h" +#include "common/dmr/data/DataBlock.h" +#include "common/dmr/data/DataHeader.h" + +#include +#include + +namespace dmr +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a packet assembler for DMR PDU packet streams. + * @ingroup dmr_pdu + */ + class HOST_SW_API Assembler { + public: + /** + * @brief Initializes a new instance of the Assembler class. + */ + Assembler(); + /** + * @brief Finalizes a instance of the Assembler class. + */ + ~Assembler(); + + /** + * @brief Helper to assemble user data as a DMR PDU packet. + * @note When using a custom block writer, this will return null. + * @param dataHeader Instance of a PDU data header. + * @param dataType DMR packet data type. + * @param[in] pduUserData Buffer containing user data to assemble. + * @param[out] assembledBitLength Length of assembled packet in bits. + * @param[in] userContext User supplied context data to pass to custom block writer. + * @returns UInt8Array Assembled PDU buffer. + */ + void assemble(data::DataHeader& dataHeader, DMRDEF::DataType::E dataType, const uint8_t* pduUserData, + uint32_t* assembledBitLength, void* userContext = nullptr); + + /** + * @brief Helper to set the custom block writer callback. + * @param callback + */ + void setBlockWriter(std::function&& callback) + { + m_blockWriter = callback; + } + + /** + * @brief Sets the flag indicating whether or not the assembler will dump PDU data. + * @param dumpPDUData Flag indicating PDU log dumping. + */ + static void setDumpPDUData(bool dumpPDUData) { s_dumpPDUData = dumpPDUData; } + /** + * @brief Sets the flag indicating verbose log output. + * @param verbose Flag indicating verbose log output. + */ + static void setVerbose(bool verbose) { s_verbose = verbose; } + + private: + static bool s_dumpPDUData; + static bool s_verbose; + + /** + * @brief Custom block writing callback. + */ + std::function m_blockWriter; + }; + } // namespace data +} // namespace dmr + +#endif // __DMR_DATA__ASSEMBLER_H__ diff --git a/src/common/dmr/data/DataBlock.cpp b/src/common/dmr/data/DataBlock.cpp index c13b131a1..9321cf124 100644 --- a/src/common/dmr/data/DataBlock.cpp +++ b/src/common/dmr/data/DataBlock.cpp @@ -25,6 +25,13 @@ using namespace dmr::data; // Public Class Members // --------------------------------------------------------------------------- +/* Initializes a copy instance of the DataBlock class. */ + +DataBlock::DataBlock(const DataBlock& data) : DataBlock() +{ + copy(data); +} + /* Initializes a new instance of the DataBlock class. */ DataBlock::DataBlock() : @@ -43,7 +50,10 @@ DataBlock::DataBlock() : DataBlock::~DataBlock() { - delete[] m_data; + if (m_data != nullptr) { + delete[] m_data; + m_data = nullptr; + } } /* Decodes DMR PDU data block. */ @@ -88,9 +98,11 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) ::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); if (m_dataType == DataType::RATE_34_DATA) - ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data + ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); // Payload Data else if (m_dataType == DataType::RATE_12_DATA) - ::memcpy(m_data, buffer + 2U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data + ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); // Payload Data + else if (m_dataType == DataType::RATE_1_DATA) + ::memcpy(m_data, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); // Payload Data else { LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType); return false; @@ -100,22 +112,40 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); // generate CRC buffer - uint32_t crcBitLength = 144U; - if (m_dataType == DataType::RATE_12_DATA) - crcBitLength = 96U; - - for (uint32_t i = 16U; i < crcBitLength; i++) { - bool b = READ_BIT(buffer, i); - WRITE_BIT(crcBuffer, i - 16U, b); + if (m_dataType == DataType::RATE_34_DATA) { + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(crcBuffer, i + 128U, b); + } } - - for (uint32_t i = 0U; i < 6U; i++) { - bool b = READ_BIT(buffer, i); - WRITE_BIT(crcBuffer, i + (crcBitLength - 16U), b); + else if (m_dataType == DataType::RATE_12_DATA) { + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(crcBuffer, i + 80U, b); + } } + else if (m_dataType == DataType::RATE_1_DATA) { + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(crcBuffer, i + 176U, b); + } + } + +#if DEBUG_DMR_PDU_DATA + Utils::dump(2U, "DMR, CRC Bit Buffer", crcBuffer, DMR_PDU_UNCODED_LENGTH_BYTES); +#endif + + uint32_t crcBitLength = 135U; // 3/4 Rate + if (m_dataType == DataType::RATE_12_DATA) + crcBitLength = 87U; + if (m_dataType == DataType::RATE_1_DATA) + crcBitLength = 183U; // compute CRC-9 for the packet - uint16_t calculated = edac::CRC::createCRC9(crcBuffer, crcBitLength - 9U); + uint16_t calculated = edac::CRC::createCRC9(crcBuffer, crcBitLength); calculated = ~calculated & 0x1FFU; if ((crc ^ calculated) != 0) { @@ -151,9 +181,9 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) ::memset(m_data, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); if (m_dataType == DataType::RATE_34_DATA) - ::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data + ::memcpy(m_data, buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES); // Payload Data else if (m_dataType == DataType::RATE_12_DATA) - ::memcpy(m_data, buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); // Payload Data + ::memcpy(m_data, buffer, DMR_PDU_HALFRATE_LENGTH_BYTES); // Payload Data else { LogError(LOG_DMR, "DataBlock::decode(), failed to decode block, invalid dataType = $%02X", m_dataType); return false; @@ -180,98 +210,122 @@ void DataBlock::encode(uint8_t* data) if (m_DPF == DPF::CONFIRMED_DATA) { if (m_dataType == DataType::RATE_34_DATA) { - uint8_t buffer[DMR_PDU_CONFIRMED_LENGTH_BYTES]; - ::memset(buffer, 0x00U, DMR_PDU_CONFIRMED_LENGTH_BYTES); + uint8_t buffer[DMR_PDU_THREEQUARTER_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_PDU_THREEQUARTER_LENGTH_BYTES); buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No. - ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); // Payload Data + ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); // Payload Data uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES]; ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); // generate CRC buffer - uint32_t bufferLen = DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES; - uint32_t crcBitLength = 144U; - - for (uint32_t i = 2U; i < bufferLen; i++) - crcBuffer[i - 2U] = buffer[2U]; - for (uint32_t i = 0; i < 6U; i++) { + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { bool b = READ_BIT(buffer, i); - WRITE_BIT(crcBuffer, i + (crcBitLength - 15U), b); + WRITE_BIT(crcBuffer, i + 128U, b); } uint16_t crc = edac::CRC::createCRC9(crcBuffer, 135U); + crc = ~crc & 0x1FFU; + buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8) buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) #if DEBUG_DMR_PDU_DATA - Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed PDU Data Block", buffer, DMR_PDU_CONFIRMED_LENGTH_BYTES); + Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 3/4 Rate PDU Data Block", buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES); #endif m_trellis.encode34(buffer, data, true); } else if (m_dataType == DataType::RATE_12_DATA) { - uint8_t buffer[DMR_PDU_UNCONFIRMED_LENGTH_BYTES]; - ::memset(buffer, 0x00U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + uint8_t buffer[DMR_PDU_HALFRATE_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_PDU_HALFRATE_LENGTH_BYTES); buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No. - ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES); // Payload Data + ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); // Payload Data uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES]; ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); // generate CRC buffer - uint32_t bufferLen = DMR_PDU_CONFIRMED_HALFRATE_DATA_LENGTH_BYTES; - uint32_t crcBitLength = 96U; - - for (uint32_t i = 2U; i < bufferLen; i++) - crcBuffer[i - 2U] = buffer[2U]; - for (uint32_t i = 0; i < 6U; i++) { + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { bool b = READ_BIT(buffer, i); - WRITE_BIT(crcBuffer, i + (crcBitLength - 15U), b); + WRITE_BIT(crcBuffer, i + 80U, b); } uint16_t crc = edac::CRC::createCRC9(crcBuffer, 87U); + crc = ~crc & 0x1FFU; + buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8) buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) - ::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES); #if DEBUG_DMR_PDU_DATA - Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 1/2 Rate PDU Data Block", buffer, DMR_PDU_HALFRATE_LENGTH_BYTES); #endif m_bptc.encode(buffer, data); } - else { - LogError(LOG_DMR, "DataBlock::encode(), cowardly refusing to encode confirmed full-rate (rate 1) data"); - return; + else if (m_dataType == DataType::RATE_1_DATA) { + uint8_t buffer[DMR_PDU_UNCODED_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); + + buffer[0U] = ((m_serialNo << 1) & 0xFEU); // Confirmed Data Serial No. + + ::memcpy(buffer + 2U, m_data, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); // Payload Data + + uint8_t crcBuffer[DMR_PDU_UNCODED_LENGTH_BYTES]; + ::memset(crcBuffer, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); + + // generate CRC buffer + ::memcpy(crcBuffer, buffer + 2U, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); + for (uint32_t i = 0U; i < 7U; i++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(crcBuffer, i + 176U, b); + } + + uint16_t crc = edac::CRC::createCRC9(crcBuffer, 183U); + crc = ~crc & 0x1FFU; + + buffer[0U] = buffer[0U] + ((crc >> 8) & 0x01U); // CRC-9 Check Sum (b8) + buffer[1U] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) + + ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); + +#if DEBUG_DMR_PDU_DATA + Utils::dump(1U, "DMR, DataBlock::encode(), Confirmed 1 Rate PDU Data Block", buffer, DMR_PDU_UNCODED_LENGTH_BYTES); +#endif + + ::memcpy(data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); } } else if (m_DPF == DPF::UNCONFIRMED_DATA || m_DPF == DPF::RESPONSE || m_DPF == DPF::DEFINED_RAW || m_DPF == DPF::DEFINED_SHORT || m_DPF == DPF::UDT) { if (m_dataType == DataType::RATE_34_DATA) { - uint8_t buffer[DMR_PDU_CONFIRMED_LENGTH_BYTES]; - ::memset(buffer, 0x00U, DMR_PDU_CONFIRMED_LENGTH_BYTES); + uint8_t buffer[DMR_PDU_THREEQUARTER_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_PDU_THREEQUARTER_LENGTH_BYTES); - ::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_LENGTH_BYTES); + ::memcpy(buffer, m_data, DMR_PDU_THREEQUARTER_LENGTH_BYTES); #if DEBUG_DMR_PDU_DATA - Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_CONFIRMED_LENGTH_BYTES); + Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 3/4 Rate PDU Data Block", buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES); #endif m_trellis.encode34(buffer, data, true); } else if (m_dataType == DataType::RATE_12_DATA) { - uint8_t buffer[DMR_PDU_UNCONFIRMED_LENGTH_BYTES]; - ::memset(buffer, 0x00U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + uint8_t buffer[DMR_PDU_HALFRATE_LENGTH_BYTES]; + ::memset(buffer, 0x00U, DMR_PDU_HALFRATE_LENGTH_BYTES); - ::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES); #if DEBUG_DMR_PDU_DATA - Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 1/2 Rate PDU Data Block", buffer, DMR_PDU_HALFRATE_LENGTH_BYTES); #endif m_bptc.encode(buffer, data); @@ -283,6 +337,10 @@ void DataBlock::encode(uint8_t* data) ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); ::memcpy(data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); + +#if DEBUG_DMR_PDU_DATA + Utils::dump(1U, "DMR, DataBlock::encode(), Unconfirmed 1 Rate PDU Data Block", buffer, DMR_PDU_UNCODED_LENGTH_BYTES); +#endif } } else { @@ -333,14 +391,25 @@ void DataBlock::setData(const uint8_t* buffer) assert(buffer != nullptr); assert(m_data != nullptr); - if (m_dataType == DataType::RATE_34_DATA) - ::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); - else if (m_dataType == DataType::RATE_12_DATA) - ::memcpy(m_data, buffer, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); - else if (m_dataType == DataType::RATE_1_DATA) - ::memcpy(m_data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); - else - LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); + if (m_DPF == DPF::CONFIRMED_DATA) { + if (m_dataType == DataType::RATE_34_DATA) + ::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); + else if (m_dataType == DataType::RATE_12_DATA) + ::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); + else if (m_dataType == DataType::RATE_1_DATA) + ::memcpy(m_data, buffer, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); + else + LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); + } else { + if (m_dataType == DataType::RATE_34_DATA) + ::memcpy(m_data, buffer, DMR_PDU_THREEQUARTER_LENGTH_BYTES); + else if (m_dataType == DataType::RATE_12_DATA) + ::memcpy(m_data, buffer, DMR_PDU_HALFRATE_LENGTH_BYTES); + else if (m_dataType == DataType::RATE_1_DATA) + ::memcpy(m_data, buffer, DMR_PDU_UNCODED_LENGTH_BYTES); + else + LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); + } } /* Gets the raw data stored in the data block. */ @@ -350,17 +419,52 @@ uint32_t DataBlock::getData(uint8_t* buffer) const assert(buffer != nullptr); assert(m_data != nullptr); - if (m_dataType == DataType::RATE_34_DATA) { - ::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); - return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES; - } else if (m_dataType == DataType::RATE_12_DATA) { - ::memcpy(buffer, m_data, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); - return DMR_PDU_UNCONFIRMED_LENGTH_BYTES; - } else if (m_dataType == DataType::RATE_1_DATA) { - ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); - return DMR_PDU_UNCODED_LENGTH_BYTES; + if (m_DPF == DPF::CONFIRMED_DATA) { + if (m_dataType == DataType::RATE_34_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); + return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES; + } else if (m_dataType == DataType::RATE_12_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); + return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES; + } else if (m_dataType == DataType::RATE_1_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); + return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES; + } else { + LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); + return 0U; + } } else { - LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); - return 0U; + if (m_dataType == DataType::RATE_34_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_THREEQUARTER_LENGTH_BYTES); + return DMR_PDU_THREEQUARTER_LENGTH_BYTES; + } else if (m_dataType == DataType::RATE_12_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_HALFRATE_LENGTH_BYTES); + return DMR_PDU_HALFRATE_LENGTH_BYTES; + } else if (m_dataType == DataType::RATE_1_DATA) { + ::memcpy(buffer, m_data, DMR_PDU_UNCODED_LENGTH_BYTES); + return DMR_PDU_UNCODED_LENGTH_BYTES; + } else { + LogError(LOG_DMR, "unknown dataType value in PDU, dataType = $%02X", m_dataType); + return 0U; + } + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void DataBlock::copy(const DataBlock& data) +{ + m_serialNo = data.m_serialNo; + m_lastBlock = data.m_lastBlock; + + m_dataType = data.m_dataType; + m_DPF = data.m_DPF; + + if (m_data != nullptr && data.m_data != nullptr) { + ::memcpy(m_data, data.m_data, DMR_PDU_UNCODED_LENGTH_BYTES); } } diff --git a/src/common/dmr/data/DataBlock.h b/src/common/dmr/data/DataBlock.h index 240051f05..93dc3c423 100644 --- a/src/common/dmr/data/DataBlock.h +++ b/src/common/dmr/data/DataBlock.h @@ -38,6 +38,11 @@ namespace dmr */ class HOST_SW_API DataBlock { public: + /** + * @brief Initializes a copy instance of the DataBlock class. + * @param data Instance of DataBlock class to copy from. + */ + DataBlock(const DataBlock& data); /** * @brief Initializes a new instance of the DataBlock class. */ @@ -117,6 +122,11 @@ namespace dmr defines::DPF::E m_DPF; uint8_t* m_data; + + /** + * @brief Internal helper to copy the class. + */ + void copy(const DataBlock& data); }; } // namespace data } // namespace dmr diff --git a/src/common/dmr/data/DataHeader.cpp b/src/common/dmr/data/DataHeader.cpp index a7a6be9d6..77a9a2abc 100644 --- a/src/common/dmr/data/DataHeader.cpp +++ b/src/common/dmr/data/DataHeader.cpp @@ -33,6 +33,13 @@ const uint8_t UDTF_NMEA = 0x05U; // Public Class Members // --------------------------------------------------------------------------- +/* Initializes a copy instance of the DataHeader class. */ + +DataHeader::DataHeader(const DataHeader& data) : DataHeader() +{ + copy(data); +} + /* Initializes a new instance of the DataHeader class. */ DataHeader::DataHeader() : @@ -68,7 +75,10 @@ DataHeader::DataHeader() : DataHeader::~DataHeader() { - delete[] m_data; + if (m_data != nullptr) { + delete[] m_data; + m_data = nullptr; + } } /* Equals operator. */ @@ -404,33 +414,65 @@ void DataHeader::reset() /* Gets the total length in bytes of enclosed packet data. */ -uint32_t DataHeader::getPacketLength() const +uint32_t DataHeader::getPacketLength(DataType::E dataType) const { if (m_DPF == DPF::RESPONSE) { return 0U; // responses have no packet length as they are header only } if (m_DPF == DPF::CONFIRMED_DATA) { - return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } + else { + return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } } else { - return DMR_PDU_UNCONFIRMED_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_THREEQUARTER_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_HALFRATE_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } + else { + return DMR_PDU_UNCODED_LENGTH_BYTES * m_blocksToFollow - 4 - m_padLength; + } } } /* Gets the total length in bytes of entire PDU. */ -uint32_t DataHeader::getPDULength() const +uint32_t DataHeader::getPDULength(DataType::E dataType) const { if (m_DPF == DPF::RESPONSE) { return 0U; // responses have no packet length as they are header only } if (m_DPF == DPF::CONFIRMED_DATA) { - return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES * m_blocksToFollow; + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES * m_blocksToFollow; + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES * m_blocksToFollow; + } + else { + return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES * m_blocksToFollow; + } } else { - return DMR_PDU_UNCONFIRMED_LENGTH_BYTES * m_blocksToFollow; + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_THREEQUARTER_LENGTH_BYTES * m_blocksToFollow; + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_HALFRATE_LENGTH_BYTES * m_blocksToFollow; + } + else { + return DMR_PDU_UNCODED_LENGTH_BYTES * m_blocksToFollow; + } } } @@ -447,10 +489,32 @@ uint32_t DataHeader::getData(uint8_t* buffer) const /* Helper to calculate the number of blocks to follow and padding length for a PDU. */ -void DataHeader::calculateLength(uint32_t packetLength) +void DataHeader::calculateLength(DataType::E dataType, uint32_t packetLength) { uint32_t len = packetLength + 4U; // packet length + CRC32 - uint32_t blockLen = (m_DPF == DPF::CONFIRMED_DATA) ? DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES : DMR_PDU_UNCONFIRMED_LENGTH_BYTES; + uint32_t blockLen = DMR_PDU_UNCODED_LENGTH_BYTES; + if (m_DPF == DPF::CONFIRMED_DATA) { + if (dataType == DataType::RATE_34_DATA) { + blockLen = DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES; + } + else if (dataType == DataType::RATE_12_DATA) { + blockLen = DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES; + } + else { + blockLen = DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES; + } + } + else { + if (dataType == DataType::RATE_34_DATA) { + blockLen = DMR_PDU_THREEQUARTER_LENGTH_BYTES; + } + else if (dataType == DataType::RATE_12_DATA) { + blockLen = DMR_PDU_HALFRATE_LENGTH_BYTES; + } + else { + blockLen = DMR_PDU_UNCODED_LENGTH_BYTES; + } + } if (len > blockLen) { m_padLength = blockLen - (len % blockLen); @@ -463,13 +527,74 @@ void DataHeader::calculateLength(uint32_t packetLength) /* Helper to determine the pad length for a given packet length. */ -uint32_t DataHeader::calculatePadLength(DPF::E dpf, uint32_t packetLength) +uint32_t DataHeader::calculatePadLength(DPF::E dpf, DataType::E dataType, uint32_t packetLength) { uint32_t len = packetLength + 4U; // packet length + CRC32 if (dpf == DPF::CONFIRMED_DATA) { - return DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_DATA_LENGTH_BYTES); + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_TQ_DATA_LENGTH_BYTES); + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_HR_DATA_LENGTH_BYTES); + } + else { + return DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES - (len % DMR_PDU_CONFIRMED_UNCODED_DATA_LENGTH_BYTES); + } } else { - return DMR_PDU_UNCONFIRMED_LENGTH_BYTES - (len % DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + if (dataType == DataType::RATE_34_DATA) { + return DMR_PDU_THREEQUARTER_LENGTH_BYTES - (len % DMR_PDU_THREEQUARTER_LENGTH_BYTES); + } + else if (dataType == DataType::RATE_12_DATA) { + return DMR_PDU_HALFRATE_LENGTH_BYTES - (len % DMR_PDU_HALFRATE_LENGTH_BYTES); + } + else { + return DMR_PDU_UNCODED_LENGTH_BYTES - (len % DMR_PDU_UNCODED_LENGTH_BYTES); + } + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void DataHeader::copy(const DataHeader& data) +{ + m_GI = data.m_GI; + m_A = data.m_A; + + m_DPF = data.m_DPF; + + m_sap = data.m_sap; + + m_fsn = data.m_fsn; + m_Ns = data.m_Ns; + + m_blocksToFollow = data.m_blocksToFollow; + m_padLength = data.m_padLength; + + m_F = data.m_F; + m_S = data.m_S; + + m_dataFormat = data.m_dataFormat; + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + + m_rspClass = data.m_rspClass; + m_rspType = data.m_rspType; + m_rspStatus = data.m_rspStatus; + + m_srcPort = data.m_srcPort; + m_dstPort = data.m_dstPort; + + m_SF = data.m_SF; + m_PF = data.m_PF; + m_UDTO = data.m_UDTO; + + if (m_data != nullptr && data.m_data != nullptr) { + ::memcpy(m_data, data.m_data, DMR_LC_HEADER_LENGTH_BYTES); } } diff --git a/src/common/dmr/data/DataHeader.h b/src/common/dmr/data/DataHeader.h index e308c4918..fb778fd44 100644 --- a/src/common/dmr/data/DataHeader.h +++ b/src/common/dmr/data/DataHeader.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX - * Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -38,6 +38,11 @@ namespace dmr */ class HOST_SW_API DataHeader { public: + /** + * @brief Initializes a copy instance of the DataHeader class. + * @param data Instance of DataHeader class to copy from. + */ + DataHeader(const DataHeader& data); /** * @brief Initializes a new instance of the DataHeader class. */ @@ -72,14 +77,16 @@ namespace dmr /** * @brief Gets the total length in bytes of enclosed packet data. + * @param dataType DMR Data Type. * @returns uint32_t Total length of packet in bytes. */ - uint32_t getPacketLength() const; + uint32_t getPacketLength(defines::DataType::E dataType) const; /** * @brief Gets the total length in bytes of entire PDU. + * @param dataType DMR Data Type. * @returns uint32_t Total length of PDU in bytes. */ - uint32_t getPDULength() const; + uint32_t getPDULength(defines::DataType::E dataType) const; /** * @brief Gets the raw header data. @@ -90,17 +97,19 @@ namespace dmr /** * @brief Helper to calculate the number of blocks to follow and padding length for a PDU. + * @param dataType DMR Data Type. * @param packetLength Length of PDU. */ - void calculateLength(uint32_t packetLength); + void calculateLength(defines::DataType::E dataType, uint32_t packetLength); /** * @brief Helper to determine the pad length for a given packet length. * @param dpf PDU format type. + * @param dataType DMR Data Type. * @param packetLength Length of PDU. * @returns uint32_t Number of pad bytes. */ - static uint32_t calculatePadLength(defines::DPF::E dpf, uint32_t packetLength); + static uint32_t calculatePadLength(defines::DPF::E dpf, defines::DataType::E dataType, uint32_t packetLength); public: /** @@ -190,6 +199,11 @@ namespace dmr bool m_SF; bool m_PF; uint8_t m_UDTO; + + /** + * @brief Internal helper to copy the class. + */ + void copy(const DataHeader& data); }; } // namespace data } // namespace dmr diff --git a/src/common/dmr/data/EmbeddedData.cpp b/src/common/dmr/data/EmbeddedData.cpp index bf9bad19f..8aaedec10 100644 --- a/src/common/dmr/data/EmbeddedData.cpp +++ b/src/common/dmr/data/EmbeddedData.cpp @@ -44,8 +44,15 @@ EmbeddedData::EmbeddedData() : EmbeddedData::~EmbeddedData() { - delete[] m_raw; - delete[] m_data; + if (m_raw != nullptr) { + delete[] m_raw; + m_raw = nullptr; + } + + if (m_data != nullptr) { + delete[] m_data; + m_data = nullptr; + } } /* Add LC data (which may consist of 4 blocks) to the data store. */ diff --git a/src/common/dmr/data/NetData.cpp b/src/common/dmr/data/NetData.cpp index 3514c53bc..bae110c64 100644 --- a/src/common/dmr/data/NetData.cpp +++ b/src/common/dmr/data/NetData.cpp @@ -64,7 +64,10 @@ NetData::NetData() : NetData::~NetData() { - delete[] m_data; + if (m_data != nullptr) { + delete[] m_data; + m_data = nullptr; + } } /* Equals operator. */ diff --git a/src/common/dmr/lc/CSBK.cpp b/src/common/dmr/lc/CSBK.cpp index 9c8a655dd..230fa6fd3 100644 --- a/src/common/dmr/lc/CSBK.cpp +++ b/src/common/dmr/lc/CSBK.cpp @@ -25,9 +25,9 @@ using namespace dmr::lc; // Static Class Members // --------------------------------------------------------------------------- -bool CSBK::m_verbose = false; +bool CSBK::s_verbose = false; -SiteData CSBK::m_siteData = SiteData(); +SiteData CSBK::s_siteData = SiteData(); // --------------------------------------------------------------------------- // Public Class Members @@ -271,7 +271,7 @@ bool CSBK::decode(const uint8_t* data, uint8_t* payload) break; } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "CSBK::decode(), Decoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } @@ -339,7 +339,7 @@ void CSBK::encode(uint8_t* data, const uint8_t* payload) break; } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "CSBK::encode(), Encoded CSBK", csbk, DMR_CSBK_LENGTH_BYTES); } diff --git a/src/common/dmr/lc/CSBK.h b/src/common/dmr/lc/CSBK.h index 91d57da57..04f2eabcf 100644 --- a/src/common/dmr/lc/CSBK.h +++ b/src/common/dmr/lc/CSBK.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX * Copyright (C) 2019-2024 Bryan Biedenkapp, N2PLL * @@ -92,24 +88,24 @@ namespace dmr * @brief Gets the flag indicating verbose log output. * @returns bool True, if the CSBK is verbose logging, otherwise false. */ - static bool getVerbose() { return m_verbose; } + static bool getVerbose() { return s_verbose; } /** * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. */ - static void setVerbose(bool verbose) { m_verbose = verbose; } + static void setVerbose(bool verbose) { s_verbose = verbose; } /** @name Local Site data */ /** * @brief Gets the local site data. * @returns SiteData Currently set site data for the CSBK class. */ - static SiteData getSiteData() { return m_siteData; } + static SiteData getSiteData() { return s_siteData; } /** * @brief Sets the local site data. * @param siteData Site data to set for the CSBK class. */ - static void setSiteData(SiteData siteData) { m_siteData = siteData; } + static void setSiteData(SiteData siteData) { s_siteData = siteData; } /** @} */ public: @@ -230,10 +226,10 @@ namespace dmr /** @} */ protected: - static bool m_verbose; + static bool s_verbose; // Local Site data - static SiteData m_siteData; + static SiteData s_siteData; /** * @brief Internal helper to convert payload bytes to a 64-bit long value. diff --git a/src/common/dmr/lc/FullLC.h b/src/common/dmr/lc/FullLC.h index 61e565153..13274e8f0 100644 --- a/src/common/dmr/lc/FullLC.h +++ b/src/common/dmr/lc/FullLC.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2015,2016 Jonathan Naylor, G4KLX * Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL * diff --git a/src/common/dmr/lc/PrivacyLC.cpp b/src/common/dmr/lc/PrivacyLC.cpp index 80be911b0..5e9957257 100644 --- a/src/common/dmr/lc/PrivacyLC.cpp +++ b/src/common/dmr/lc/PrivacyLC.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2021,2024,2025 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/dmr/lc/PrivacyLC.h b/src/common/dmr/lc/PrivacyLC.h index 9935c51ac..12bd0dc67 100644 --- a/src/common/dmr/lc/PrivacyLC.h +++ b/src/common/dmr/lc/PrivacyLC.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2021,2025 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/dmr/lc/csbk/CSBKFactory.h b/src/common/dmr/lc/csbk/CSBKFactory.h index 571cb59c1..1d07fb4c3 100644 --- a/src/common/dmr/lc/csbk/CSBKFactory.h +++ b/src/common/dmr/lc/csbk/CSBKFactory.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/dmr/lc/csbk/CSBK_ACK_RSP.cpp b/src/common/dmr/lc/csbk/CSBK_ACK_RSP.cpp index f5d9c38af..faac3f753 100644 --- a/src/common/dmr/lc/csbk/CSBK_ACK_RSP.cpp +++ b/src/common/dmr/lc/csbk/CSBK_ACK_RSP.cpp @@ -63,7 +63,7 @@ void CSBK_ACK_RSP::encode(uint8_t* data) csbkValue = 0U; } else { csbkValue = (m_GI ? 0x40U : 0x00U) + // Source Type - (m_siteData.siteId() & 0x3FU); // Net + Site LSB + (s_siteData.siteId() & 0x3FU); // Net + Site LSB } csbkValue = (csbkValue << 7) + (m_response & 0x7FU); // Response Information csbkValue = (csbkValue << 8) + (m_reason & 0xFFU); // Reason Code diff --git a/src/common/dmr/lc/csbk/CSBK_ALOHA.cpp b/src/common/dmr/lc/csbk/CSBK_ALOHA.cpp index 39402c704..ffc3355d2 100644 --- a/src/common/dmr/lc/csbk/CSBK_ALOHA.cpp +++ b/src/common/dmr/lc/csbk/CSBK_ALOHA.cpp @@ -55,13 +55,13 @@ void CSBK_ALOHA::encode(uint8_t* data) csbkValue = (csbkValue << 1) + ((m_siteTSSync) ? 1U : 0U); // Site Time Slot Synchronization csbkValue = (csbkValue << 3) + DMR_ALOHA_VER_151; // DMR Spec. Version (1.5.1) csbkValue = (csbkValue << 1) + ((m_siteOffsetTiming) ? 1U : 0U); // Site Timing: Aligned or Offset - csbkValue = (csbkValue << 1) + ((m_siteData.netActive()) ? 1U : 0U); // Site Networked + csbkValue = (csbkValue << 1) + ((s_siteData.netActive()) ? 1U : 0U); // Site Networked csbkValue = (csbkValue << 5) + (m_alohaMask & 0x1FU); // MS Mask csbkValue = (csbkValue << 2) + 0U; // Service Function csbkValue = (csbkValue << 4) + (m_nRandWait & 0x0FU); // Random Access Wait - csbkValue = (csbkValue << 1) + ((m_siteData.requireReg()) ? 1U : 0U); // Require Registration + csbkValue = (csbkValue << 1) + ((s_siteData.requireReg()) ? 1U : 0U); // Require Registration csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number - csbkValue = (csbkValue << 16) + m_siteData.systemIdentity(); // Site Identity + csbkValue = (csbkValue << 16) + s_siteData.systemIdentity(); // Site Identity csbkValue = (csbkValue << 24) + m_srcId; // Source Radio Address std::unique_ptr csbk = CSBK::fromValue(csbkValue); diff --git a/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp b/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp index 51c35dacf..3a3f0de79 100644 --- a/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp +++ b/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp @@ -142,11 +142,11 @@ void CSBK_BROADCAST::encode(uint8_t* data) break; case BroadcastAnncType::SITE_PARMS: // Broadcast Params 1 - csbkValue = (csbkValue << 14) + m_siteData.systemIdentity(true); // Site Identity (Broadcast Params 1) + csbkValue = (csbkValue << 14) + s_siteData.systemIdentity(true); // Site Identity (Broadcast Params 1) - csbkValue = (csbkValue << 1) + ((m_siteData.requireReg()) ? 1U : 0U); // Require Registration + csbkValue = (csbkValue << 1) + ((s_siteData.requireReg()) ? 1U : 0U); // Require Registration csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number - csbkValue = (csbkValue << 16) + m_siteData.systemIdentity(); // Site Identity + csbkValue = (csbkValue << 16) + s_siteData.systemIdentity(); // Site Identity // Broadcast Params 2 csbkValue = (csbkValue << 1) + 0U; // Roaming TG Subscription/Attach diff --git a/src/common/dmr/lc/csbk/CSBK_MAINT.h b/src/common/dmr/lc/csbk/CSBK_MAINT.h index 6b3f2a743..c5f3300ad 100644 --- a/src/common/dmr/lc/csbk/CSBK_MAINT.h +++ b/src/common/dmr/lc/csbk/CSBK_MAINT.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2023 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/edac/CRC.cpp b/src/common/edac/CRC.cpp index b4f6a852d..9b483ee27 100644 --- a/src/common/edac/CRC.cpp +++ b/src/common/edac/CRC.cpp @@ -320,6 +320,36 @@ bool CRC::checkCRC32(const uint8_t *in, uint32_t length) return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U]; } +/* Check 32-bit CRC (inverted). */ + +bool CRC::checkInvertedCRC32(const uint8_t *in, uint32_t length) +{ + assert(in != nullptr); + assert(length > 4U); + + union { + uint32_t crc32; + uint8_t crc8[4U]; + }; + + uint32_t i = 0; + crc32 = 0x00000000U; + + for (uint32_t j = (length - 4U); j-- > 0; i++) { + uint32_t idx = ((crc32 >> 24) ^ in[i]) & 0xFFU; + crc32 = (CRC32_TABLE[idx] ^ (crc32 << 8)) & 0xFFFFFFFFU; + } + + crc32 &= 0xFFFFFFFFU; + +#if DEBUG_CRC_CHECK + uint32_t inCrc = (in[length - 4U] << 24) | (in[length - 3U] << 16) | (in[length - 2U] << 8) | (in[length - 1U] << 0); + LogDebugEx(LOG_HOST, "CRC::checkInvertedCRC32()", "crc = $%08X, in = $%08X, len = %u", crc32, inCrc, length); +#endif + + return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U] && crc8[2U] == in[length - 3U] && crc8[3U] == in[length - 4U]; +} + /* Encode 32-bit CRC. */ void CRC::addCRC32(uint8_t* in, uint32_t length) @@ -353,6 +383,38 @@ void CRC::addCRC32(uint8_t* in, uint32_t length) in[length - 4U] = crc8[3U]; } +/* Encode 32-bit CRC (inverted). */ + +void CRC::addInvertedCRC32(uint8_t* in, uint32_t length) +{ + assert(in != nullptr); + assert(length > 4U); + + union { + uint32_t crc32; + uint8_t crc8[4U]; + }; + + uint32_t i = 0; + crc32 = 0x00000000U; + + for (uint32_t j = (length - 4U); j-- > 0; i++) { + uint32_t idx = ((crc32 >> 24) ^ in[i]) & 0xFFU; + crc32 = (CRC32_TABLE[idx] ^ (crc32 << 8)) & 0xFFFFFFFFU; + } + + crc32 &= 0xFFFFFFFFU; + +#if DEBUG_CRC_ADD + LogDebugEx(LOG_HOST, "CRC::addInvertedCRC32()", "crc = $%08X, len = %u", crc32, length); +#endif + + in[length - 1U] = crc8[0U]; + in[length - 2U] = crc8[1U]; + in[length - 3U] = crc8[2U]; + in[length - 4U] = crc8[3U]; +} + /* Generate 8-bit CRC. */ uint8_t CRC::crc8(const uint8_t *in, uint32_t length) diff --git a/src/common/edac/CRC.h b/src/common/edac/CRC.h index 4d6cd89c9..8007dd293 100644 --- a/src/common/edac/CRC.h +++ b/src/common/edac/CRC.h @@ -92,12 +92,25 @@ namespace edac * @returns bool True, if CRC is valid, otherwise false. */ static bool checkCRC32(const uint8_t* in, uint32_t length); + /** + * @brief Check 32-bit CRC (inverted). + * @param[in] in Input byte array. + * @param length Length of byte array. + * @returns bool True, if CRC is valid, otherwise false. + */ + static bool checkInvertedCRC32(const uint8_t* in, uint32_t length); /** * @brief Encode 32-bit CRC. * @param[out] in Input byte array. * @param length Length of byte array. */ static void addCRC32(uint8_t* in, uint32_t length); + /** + * @brief Encode 32-bit CRC (inverted). + * @param[out] in Input byte array. + * @param length Length of byte array. + */ + static void addInvertedCRC32(uint8_t* in, uint32_t length); /** * @brief Generate 8-bit CRC. diff --git a/src/common/edac/RS634717.cpp b/src/common/edac/RS634717.cpp index 4ad9b2cb1..ce385e4b7 100644 --- a/src/common/edac/RS634717.cpp +++ b/src/common/edac/RS634717.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2017,2023 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017,2023,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -77,6 +77,149 @@ const uint8_t ENCODE_MATRIX_362017[20U][36U] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 002, 001, 053, 074, 002, 014, 052, 074, 012, 057, 024, 063, 015, 042, 052, 033 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 034, 035, 002, 023, 021, 027, 022, 033, 064, 042, 005, 073, 051, 046, 073, 060 } }; +const uint8_t ENCODE_MATRIX_633529[35U][63U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 014, 023, 076, 015, 077, 050, 062, 015, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 051, 061, 032, 006 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 072, 043, 045, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 043, 011, 056, 016 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 041, 061, 024, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 050, 001, 003, 012 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 015, 032, 011, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 016, 013, 040, 077 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 063, 005, 071, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 036, 021, 046, 044 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 003, 011, 061, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 043, 027, 055, 073 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 037, 073, 045, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 071, 075, 021, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 034, 057, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 056, 004, 032, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 021, 010, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 037, 023, 043, 072 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 046, 056, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 076, 060, 017, 071 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 064, 054, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 001, 001, 002, 037 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 013, 054, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 036, 026, 073, 003 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 035, 010, 076, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 044, 016, 070, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 036, 034, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 015, 031, 051, 030 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 064, 074, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 030, 013, 046, 072 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 031, 013, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 045, 067, 027, 074 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 056, 010, 017, 037, 012, 062, 011, 071, 003, 020, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 020, 052, 064, 031 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 046, 004, 071, 071, 054, 045, 013, 025, 012, 051, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 017, 047, 017, 041 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 044, 040, 054, 046, 036, 022, 061, 022, 062, 014, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 015, 066, 052, 014 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 067, 067, 061, 050, 071, 026, 073, 046, 015, 041, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 023, 016, 010, 002 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 026, 057, 021, 016, 062, 004, 005, 034, 074, 025, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 043, 062, 072, 004 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 046, 040, 054, 072, 013, 042, 010, 050, 014, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 013, 042, 031, 042 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 076, 073, 076, 034, 006, 044, 056, 070, 072, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 062, 042, 001, 020 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 066, 074, 002, 012, 053, 075, 030, 020, 073, 075, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 031, 066, 010, 042 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 044, 041, 034, 072, 027, 022, 024, 040, 051, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 020, 060, 025, 001 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 065, 067, 037, 021, 005, 077, 040, 031, 054, 043, 041, 013, 030, 013, 037, 062, 045, 061, 053, 005, 063, 013, 063, 071, 041, 052, 023 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 053, 032, 074, 007, 035, 062, 040, 036, 042, 010, 024, 031, 067, 054, 021, 001, 072, 072, 073, 006, 061, 017, 020, 067, 005, 055, 045, 003 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 035, 077, 057, 075, 020, 037, 011, 065, 011, 066, 026, 035, 036, 033, 031, 031, 072, 045, 042, 051, 060, 071, 015, 040, 017, 025, 003, 057 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 005, 020, 040, 013, 037, 033, 061, 040, 027, 004, 034, 036, 044, 022, 004, 065, 067, 064, 022, 062, 031, 034, 062, 040, 041, 024, 022, 044 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 003, 077, 044, 074, 025, 047, 001, 027, 076, 055, 043, 045, 011, 040, 046, 041, 057, 014, 030, 043, 042, 074, 044, 051, 036, 050, 050, 017 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 052, 004, 033, 041, 064, 037, 065, 003, 037, 071, 010, 016, 076, 023, 004, 016, 063, 017, 067, 001, 010, 012, 066, 021, 064, 015, 070, 012 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 015, 021, 074, 035, 020, 070, 003, 010, 062, 044, 076, 076, 034, 023, 053, 064, 022, 062, 034, 030, 063, 070, 006, 020, 007, 027, 054, 004 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 054, 075, 036, 001, 051, 051, 036, 016, 074, 002, 014, 042, 013, 016, 034, 012, 022, 007, 022, 044, 023, 022, 001, 005, 062, 006, 074, 064 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 065, 023, 065, 063, 012, 060, 055, 014, 005, 003, 003, 006, 044, 004, 006, 073, 016, 076, 055, 006, 030, 064, 013, 026, 065, 077, 020, 002 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 026, 055, 065, 012, 051, 067, 043, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024, 013, 034 } }; + +const uint8_t ENCODE_MATRIX_523023[30U][52U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 023, 076, 015, 077, 050, 062, 015, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 045, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 061, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 045, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 057, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 010, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 056, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 076, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 013, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 017, 037, 012, 062, 011, 071, 003, 020, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 071, 054, 045, 013, 025, 012, 051, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 046, 036, 022, 061, 022, 062, 014, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 061, 050, 071, 026, 073, 046, 015, 041, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 021, 016, 062, 004, 005, 034, 074, 025, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 040, 054, 072, 013, 042, 010, 050, 014, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 073, 076, 034, 006, 044, 056, 070, 072, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 002, 012, 053, 075, 030, 020, 073, 075, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 041, 034, 072, 027, 022, 024, 040, 051, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 067, 037, 021, 005, 077, 040, 031, 054, 043, 041, 013, 030, 013, 037, 062, 045, 061, 053, 005, 063, 013, 063 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 074, 007, 035, 062, 040, 036, 042, 010, 024, 031, 067, 054, 021, 001, 072, 072, 073, 006, 061, 017, 020, 067 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 057, 075, 020, 037, 011, 065, 011, 066, 026, 035, 036, 033, 031, 031, 072, 045, 042, 051, 060, 071, 015, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 040, 013, 037, 033, 061, 040, 027, 004, 034, 036, 044, 022, 004, 065, 067, 064, 022, 062, 031, 034, 062, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 044, 074, 025, 047, 001, 027, 076, 055, 043, 045, 011, 040, 046, 041, 057, 014, 030, 043, 042, 074, 044, 051 } }; + +const uint8_t ENCODE_MATRIX_462621[26U][46U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 043, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 014, 012, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 012, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 044, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 047, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 041, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 033, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 027, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 007, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 002, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 037, 012, 062, 011, 071, 003 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 042, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 071, 054, 045, 013, 025, 012 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 057, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 046, 036, 022, 061, 022, 062 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 054, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 050, 071, 026, 073, 046, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 067, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 016, 062, 004, 005, 034, 074 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 065, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 054, 072, 013, 042, 010, 050 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 075, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 076, 034, 006, 044, 056, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 027, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 012, 053, 075, 030, 020, 073 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 034, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 034, 072, 027, 022, 024, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 046, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 037, 021, 005, 077, 040, 031 } }; + +const uint8_t ENCODE_MATRIX_452620[26U][45U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 012, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 007, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002, 034 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 030, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 032, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 053, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 003, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 066, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 060, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 015, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 071, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 016, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 022, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 005, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 034, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 047, 075, 015, 015, 045, 066, 031, 063, 031, 067, 012, 076, 047, 037, 012, 062, 011, 071, 003 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 060, 010, 026, 033, 053, 056, 060, 060, 024, 063, 021, 042, 057, 071, 054, 045, 013, 025, 012 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 056, 064, 002, 047, 041, 022, 047, 075, 050, 074, 011, 076, 070, 046, 036, 022, 061, 022, 062 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 015, 060, 007, 052, 032, 065, 010, 043, 072, 041, 001, 067, 066, 050, 071, 026, 073, 046, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 010, 021, 006, 026, 012, 063, 012, 053, 050, 047, 001, 011, 062, 016, 062, 004, 005, 034, 074 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 071, 063, 030, 040, 047, 031, 030, 032, 067, 014, 026, 074, 051, 054, 072, 013, 042, 010, 050 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 051, 014, 041, 027, 001, 001, 014, 070, 042, 074, 055, 057, 077, 076, 034, 006, 044, 056, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 026, 060, 023, 074, 042, 056, 004, 020, 055, 035, 011, 021, 027, 012, 053, 075, 030, 020, 073 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 044, 007, 073, 057, 076, 074, 071, 002, 065, 001, 037, 050, 035, 034, 072, 027, 022, 024, 040 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 067, 075, 030, 046, 032, 021, 071, 045, 027, 012, 064, 043, 020, 037, 021, 005, 077, 040, 031 } }; + +const uint8_t ENCODE_MATRIX_441629[16U][44U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 026, 035, 027, 015, 075, 055, 042, 067, 050, 045, 056, 061, 042, 051, 011, 053, 007, 024, 013, 034, 045, 060, 030, 011, 047, 014, 003, 014 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 045, 023, 071, 050, 064, 010, 016, 022, 071, 077, 020, 021, 020, 011, 012, 002, 034, 045, 060, 030, 011, 047, 014, 003, 014, 026, 004 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 047, 014, 003, 014, 026, 004, 054, 041, 002, 075, 034, 036, 024, 045, 063, 072, 007, 027, 012, 032, 077, 066, 020, 035, 071, 030, 045 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 077, 066, 020, 035, 071, 030, 045, 023, 025, 060, 067, 030, 022, 057, 030, 071, 016, 013, 074, 020, 074, 010, 022, 016, 040, 001, 070, 013 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 010, 022, 016, 040, 001, 070, 013, 012, 041, 045, 074, 021, 034, 045, 005, 050, 044, 071, 003, 060, 053, 024, 017, 061, 040, 020, 030, 011 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 017, 061, 040, 020, 030, 011, 076, 026, 017, 017, 035, 045, 002, 035, 037, 016, 072, 003, 044, 011, 074, 020, 073, 024, 072, 053, 064 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 020, 073, 024, 072, 053, 064, 070, 056, 063, 067, 024, 031, 046, 021, 013, 017, 015, 002, 047, 003, 024, 051, 074, 064, 002, 066, 072 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 024, 051, 074, 064, 002, 066, 072, 071, 057, 041, 040, 025, 013, 053, 071, 033, 046, 075, 016, 041, 066, 014, 054, 075, 003, 076, 017, 064 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 014, 054, 075, 003, 076, 017, 064, 030, 034, 020, 076, 044, 001, 071, 064, 063, 066, 024, 076, 055, 060, 071, 064, 070, 002, 011, 063, 015 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 071, 064, 070, 002, 011, 063, 015, 026, 075, 043, 017, 072, 036, 017, 025, 012, 021, 070, 040, 020, 015, 021, 011, 013, 016, 074, 061, 052 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 021, 011, 013, 016, 074, 061, 052, 016, 023, 013, 017, 075, 071, 007, 041, 020, 075, 010, 030, 020, 071, 053, 015, 003, 065, 013, 033, 060 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 053, 015, 003, 065, 013, 033, 060, 073, 075, 055, 045, 015, 030, 044, 054, 055, 046, 040, 012, 033, 016, 063, 072, 025, 051, 071, 074, 046 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 063, 072, 025, 051, 071, 074, 046, 014, 074, 027, 006, 034, 055, 017, 046, 027, 070, 061, 064, 024, 022, 011, 037, 017, 035, 022, 046, 044 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 011, 037, 017, 035, 022, 046, 044, 064, 072, 064, 025, 066, 020, 037, 020, 054, 072, 012, 062, 027, 005, 035, 061, 013, 060, 027, 037, 044 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 035, 061, 013, 060, 027, 037, 044, 006, 021, 005, 053, 021, 024, 056, 017, 001, 002, 004, 054, 007, 034, 075, 062, 023, 010, 041, 052, 032 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 075, 062, 023, 010, 041, 052, 032, 062, 074, 022, 025, 041, 052, 032, 002, 061, 043, 014, 060, 002, 047, 075, 015, 015, 045, 066, 031, 063 } }; + /** * @brief Define a reed-solomon codec. * @param TYPE Data type primitive @@ -129,6 +272,15 @@ class RS6355 : public __RS_63(55) { }; RS6355 rs24169; // 8 bit / 4 bit corrections max / 2 bytes total +/** + * @brief Implements Reed-Solomon (63,35,29) + */ +class RS6335 : public __RS_63(35) { +public: + RS6335() : __RS_63(35)() { /* stub */ } +}; +RS6335 rs633529; // 28 bit / 14 bit corrections max / 4 bytes total + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -291,6 +443,206 @@ void RS634717::encode362017(uint8_t* data) Utils::hex2Bin(codeword[i], data, offset); } +/* Decode RS (52,30,23) FEC. */ + +bool RS634717::decode523023(uint8_t* data) +{ + assert(data != nullptr); + + std::vector codeword(63, 0); + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 52U; i++, offset += 6) + codeword[11 + i] = Utils::bin2Hex(data, offset); + + int ec = rs633529.decode(codeword); +#if DEBUG_RS + LogDebugEx(LOG_HOST, "RS634717::decode523023()", "errors = %d\n", ec); +#endif + offset = 0U; + for (uint32_t i = 0U; i < 30U; i++, offset += 6) + Utils::hex2Bin(codeword[11 + i], data, offset); + + if ((ec == -1) || (ec >= 11)) { + return false; + } + + return true; +} + +/* Encode RS (52,30,23) FEC. */ + +void RS634717::encode523023(uint8_t* data) +{ + assert(data != nullptr); + + uint8_t codeword[52U]; + + for (uint32_t i = 0U; i < 52U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 30U; j++, offset += 6U) { + uint8_t hexbit = Utils::bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_523023[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 52U; i++, offset += 6U) + Utils::hex2Bin(codeword[i], data, offset); +} + +/* Decode RS (46,26,21) FEC. */ + +bool RS634717::decode462621(uint8_t* data) +{ + assert(data != nullptr); + + std::vector codeword(63, 0); + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 46U; i++, offset += 6) + codeword[17 + i] = Utils::bin2Hex(data, offset); + + int ec = rs633529.decode(codeword); +#if DEBUG_RS + LogDebugEx(LOG_HOST, "RS634717::decode462621()", "errors = %d\n", ec); +#endif + offset = 0U; + for (uint32_t i = 0U; i < 26U; i++, offset += 6) + Utils::hex2Bin(codeword[17 + i], data, offset); + + if ((ec == -1) || (ec >= 10)) { + return false; + } + + return true; +} + +/* Encode RS (46,26,21) FEC. */ + +void RS634717::encode462621(uint8_t* data) +{ + assert(data != nullptr); + + uint8_t codeword[46U]; + + for (uint32_t i = 0U; i < 46U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 26U; j++, offset += 6U) { + uint8_t hexbit = Utils::bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_462621[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 46U; i++, offset += 6U) + Utils::hex2Bin(codeword[i], data, offset); +} + +/* Decode RS (45,26,20) FEC. */ + +bool RS634717::decode452620(uint8_t* data) +{ + assert(data != nullptr); + + std::vector codeword(63, 0); + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 45U; i++, offset += 6) + codeword[18 + i] = Utils::bin2Hex(data, offset); + + int ec = rs633529.decode(codeword); +#if DEBUG_RS + LogDebugEx(LOG_HOST, "RS634717::decode452620()", "errors = %d\n", ec); +#endif + offset = 0U; + for (uint32_t i = 0U; i < 26U; i++, offset += 6) + Utils::hex2Bin(codeword[18 + i], data, offset); + + if ((ec == -1) || (ec >= 9)) { + return false; + } + + return true; +} + +/* Encode RS (45,26,20) FEC. */ + +void RS634717::encode452620(uint8_t* data) +{ + assert(data != nullptr); + + uint8_t codeword[45U]; + + for (uint32_t i = 0U; i < 45U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 26U; j++, offset += 6U) { + uint8_t hexbit = Utils::bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_452620[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 45U; i++, offset += 6U) + Utils::hex2Bin(codeword[i], data, offset); +} + +/* Decode RS (44,16,29) FEC. */ + +bool RS634717::decode441629(uint8_t* data) +{ + assert(data != nullptr); + + std::vector codeword(63, 0); + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 44U; i++, offset += 6) + codeword[19 + i] = Utils::bin2Hex(data, offset); + + int ec = rs633529.decode(codeword); +#if DEBUG_RS + LogDebugEx(LOG_HOST, "RS634717::decode441629()", "errors = %d\n", ec); +#endif + offset = 0U; + for (uint32_t i = 0U; i < 16U; i++, offset += 6) + Utils::hex2Bin(codeword[19 + i], data, offset); + + if ((ec == -1) || (ec >= 14)) { + return false; + } + + return true; +} + +/* Encode RS (44,16,29) FEC. */ + +void RS634717::encode441629(uint8_t* data) +{ + assert(data != nullptr); + + uint8_t codeword[44U]; + + for (uint32_t i = 0U; i < 44U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 16U; j++, offset += 6U) { + uint8_t hexbit = Utils::bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_441629[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 44U; i++, offset += 6U) + Utils::hex2Bin(codeword[i], data, offset); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/common/edac/RS634717.h b/src/common/edac/RS634717.h index bfdb654e1..35739679b 100644 --- a/src/common/edac/RS634717.h +++ b/src/common/edac/RS634717.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2017,2023 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017,2023,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -27,8 +27,8 @@ namespace edac /** * @brief Implements Reed-Solomon (63,47,17). Which is also used to implement - * Reed-Solomon (24,12,13), (24,16,9) and (36,20,17) forward - * error correction. + * Reed-Solomon (24,12,13), (24,16,9), (36,20,17), (52,30,23), (46,26,21), + * (45,26,20). (44,16,29) forward error correction. * @ingroup edac */ class HOST_SW_API RS634717 { @@ -42,6 +42,8 @@ namespace edac */ ~RS634717(); + /** Project 25 Phase I Reed-Solomon (TIA-102.BAAA-B Section 4.9) */ + /** * @brief Decode RS (24,12,13) FEC. * @param data Reed-Solomon FEC encoded data to decode. @@ -78,6 +80,56 @@ namespace edac */ void encode362017(uint8_t* data); + /** Project 25 Phase II Reed-Solomon (TIA-102.BBAC-A Section 5.6) */ + + /** + * @brief Decode RS (52,30,23) FEC. + * @param data Reed-Solomon FEC encoded data to decode. + * @returns bool True, if data was decoded, otherwise false. + */ + bool decode523023(uint8_t* data); + /** + * @brief Encode RS (52,30,23) FEC. + * @param data Raw data to encode with Reed-Solomon FEC. + */ + void encode523023(uint8_t* data); + + /** + * @brief Decode RS (46,26,21) FEC. + * @param data Reed-Solomon FEC encoded data to decode. + * @returns bool True, if data was decoded, otherwise false. + */ + bool decode462621(uint8_t* data); + /** + * @brief Encode RS (46,26,21) FEC. + * @param data Raw data to encode with Reed-Solomon FEC. + */ + void encode462621(uint8_t* data); + + /** + * @brief Decode RS (45,26,20) FEC. + * @param data Reed-Solomon FEC encoded data to decode. + * @returns bool True, if data was decoded, otherwise false. + */ + bool decode452620(uint8_t* data); + /** + * @brief Encode RS (45,26,20) FEC. + * @param data Raw data to encode with Reed-Solomon FEC. + */ + void encode452620(uint8_t* data); + + /** + * @brief Decode RS (44,16,29) FEC. + * @param data Reed-Solomon FEC encoded data to decode. + * @returns bool True, if data was decoded, otherwise false. + */ + bool decode441629(uint8_t* data); + /** + * @brief Encode RS (44,16,29) FEC. + * @param data Raw data to encode with Reed-Solomon FEC. + */ + void encode441629(uint8_t* data); + private: /** * @brief GF(2 ^ 6) multiply (for Reed-Solomon encoder). diff --git a/src/common/network/json/json.h b/src/common/json/json.h similarity index 99% rename from src/common/network/json/json.h rename to src/common/json/json.h index 7a9a2f64e..86b0b67bf 100644 --- a/src/common/network/json/json.h +++ b/src/common/json/json.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @derivedfrom picojson (https://github.com/kazuho/picojson) -* @license BSD-2-Clause License (https://opensource.org/licenses/BSD-2-Clause) -* * Copyright 2009-2010 Cybozu Labs, Inc. * Copyright 2011-2014 Kazuho Oku., All rights reserved. * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL diff --git a/src/common/lookups/AdjSiteMapLookup.cpp b/src/common/lookups/AdjSiteMapLookup.cpp index 9d5686ff0..6a43c90ef 100644 --- a/src/common/lookups/AdjSiteMapLookup.cpp +++ b/src/common/lookups/AdjSiteMapLookup.cpp @@ -21,8 +21,8 @@ using namespace lookups; // Static Class Members // --------------------------------------------------------------------------- -std::mutex AdjSiteMapLookup::m_mutex; -bool AdjSiteMapLookup::m_locked = false; +std::mutex AdjSiteMapLookup::s_mutex; +bool AdjSiteMapLookup::s_locked = false; // --------------------------------------------------------------------------- // Macros @@ -30,16 +30,16 @@ bool AdjSiteMapLookup::m_locked = false; // Lock the table. #define __LOCK_TABLE() \ - std::lock_guard lock(m_mutex); \ - m_locked = true; + std::lock_guard lock(s_mutex); \ + s_locked = true; // Unlock the table. -#define __UNLOCK_TABLE() m_locked = false; +#define __UNLOCK_TABLE() s_locked = false; // Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ - if (m_locked) { \ - while (m_locked) \ + if (s_locked) { \ + while (s_locked) \ Thread::sleep(2U); \ } @@ -133,8 +133,7 @@ void AdjSiteMapLookup::addEntry(AdjPeerMapEntry entry) __LOCK_TABLE(); auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), - [&](AdjPeerMapEntry x) - { + [&](AdjPeerMapEntry& x) { return x.peerId() == id; }); if (it != m_adjPeerMap.end()) { @@ -153,7 +152,10 @@ void AdjSiteMapLookup::eraseEntry(uint32_t id) { __LOCK_TABLE(); - auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), [&](AdjPeerMapEntry x) { return x.peerId() == id; }); + auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), + [&](AdjPeerMapEntry& x) { + return x.peerId() == id; + }); if (it != m_adjPeerMap.end()) { m_adjPeerMap.erase(it); } @@ -169,10 +171,9 @@ AdjPeerMapEntry AdjSiteMapLookup::find(uint32_t id) __SPINLOCK(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); auto it = std::find_if(m_adjPeerMap.begin(), m_adjPeerMap.end(), - [&](AdjPeerMapEntry x) - { + [&](AdjPeerMapEntry& x) { return x.peerId() == id; }); if (it != m_adjPeerMap.end()) { @@ -224,7 +225,7 @@ bool AdjSiteMapLookup::load() if (peerList.size() == 0U) { ::LogError(LOG_HOST, "No adj site map peer list defined!"); - m_locked = false; + s_locked = false; return false; } @@ -254,7 +255,7 @@ bool AdjSiteMapLookup::save() return false; } - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // New list for our new group voice rules yaml::Node peerList; @@ -275,7 +276,7 @@ bool AdjSiteMapLookup::save() } try { - LogMessage(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str()); + LogInfoEx(LOG_HOST, "Saving adjacent site map file to %s", m_rulesFile.c_str()); yaml::Serialize(newRules, m_rulesFile.c_str()); LogDebug(LOG_HOST, "Saved adj. site map file to %s", m_rulesFile.c_str()); } diff --git a/src/common/lookups/AdjSiteMapLookup.h b/src/common/lookups/AdjSiteMapLookup.h index ca196f6cf..d560162c9 100644 --- a/src/common/lookups/AdjSiteMapLookup.h +++ b/src/common/lookups/AdjSiteMapLookup.h @@ -233,8 +233,8 @@ namespace lookups bool m_stop; - static std::mutex m_mutex; //! Mutex used for change locking. - static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + static std::mutex s_mutex; //!< Mutex used for change locking. + static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. /** * @brief Loads the table from the passed lookup table file. diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index d0b53033e..92a185746 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -67,15 +67,19 @@ void AffiliationLookup::unitReg(uint32_t srcId) return; } + __lock(); + m_unitRegTable.push_back(srcId); m_unitRegTimers[srcId] = Timer(1000U, UNIT_REG_TIMEOUT); m_unitRegTimers[srcId].start(); if (m_verbose) { - LogMessage(LOG_HOST, "%s, unit registration, srcId = %u", + LogInfoEx(LOG_HOST, "%s, unit registration, srcId = %u", m_name.c_str(), srcId); } + + __unlock(); } /* Helper to group unaffiliate a source ID. */ @@ -88,13 +92,15 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) return false; } + groupUnaff(srcId); + + __lock(); + if (m_verbose) { - LogMessage(LOG_HOST, "%s, unit deregistration, srcId = %u", + LogInfoEx(LOG_HOST, "%s, unit deregistration, srcId = %u", m_name.c_str(), srcId); } - groupUnaff(srcId); - m_unitRegTimers[srcId].stop(); // remove dynamic unit registration table entry @@ -113,6 +119,8 @@ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) } } + __unlock(); + return ret; } @@ -124,6 +132,8 @@ void AffiliationLookup::touchUnitReg(uint32_t srcId) return; } + __spinlock(); + if (isUnitReg(srcId)) { m_unitRegTimers[srcId].start(); } @@ -137,6 +147,8 @@ uint32_t AffiliationLookup::unitRegTimeout(uint32_t srcId) return 0U; } + __spinlock(); + if (isUnitReg(srcId)) { return m_unitRegTimers[srcId].getTimeout(); } @@ -152,6 +164,8 @@ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId) return 0U; } + __spinlock(); + if (isUnitReg(srcId)) { return m_unitRegTimers[srcId].getTimer(); } @@ -163,6 +177,8 @@ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId) bool AffiliationLookup::isUnitReg(uint32_t srcId) const { + __spinlock(); + // lookup dynamic unit registration table entry m_unitRegTable.lock(false); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { @@ -179,9 +195,11 @@ bool AffiliationLookup::isUnitReg(uint32_t srcId) const void AffiliationLookup::clearUnitReg() { + __lock(); std::vector srcToRel = std::vector(); LogWarning(LOG_HOST, "%s, releasing all unit registrations", m_name.c_str()); m_unitRegTable.clear(); + __unlock(); } /* Helper to group affiliate a source ID. */ @@ -189,13 +207,17 @@ void AffiliationLookup::clearUnitReg() void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId) { if (!isGroupAff(srcId, dstId)) { + __lock(); + // update dynamic affiliation table m_grpAffTable[srcId] = dstId; if (m_verbose) { - LogMessage(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u", + LogInfoEx(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u", m_name.c_str(), srcId, dstId); } + + __unlock(); } } @@ -203,14 +225,17 @@ void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId) bool AffiliationLookup::groupUnaff(uint32_t srcId) { + __lock(); + // lookup dynamic affiliation table entry - if (m_grpAffTable.find(srcId) != m_grpAffTable.end()) { - uint32_t tblDstId = m_grpAffTable.at(srcId); + auto it = m_grpAffTable.find(srcId); + if (it != m_grpAffTable.end()) { if (m_verbose) { - LogMessage(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u", - m_name.c_str(), srcId, tblDstId); + LogInfoEx(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u", + m_name.c_str(), srcId, it->second); } } else { + __unlock(); return false; } @@ -219,9 +244,11 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId) uint32_t entry = m_grpAffTable.at(srcId); // this value will get discarded (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard m_grpAffTable.erase(srcId); + __unlock(); return true; } catch (...) { + __unlock(); return false; } } @@ -230,6 +257,8 @@ bool AffiliationLookup::groupUnaff(uint32_t srcId) bool AffiliationLookup::hasGroupAff(uint32_t dstId) const { + __spinlock(); + // lookup dynamic affiliation table entry m_grpAffTable.lock(false); for (auto entry : m_grpAffTable) { @@ -247,11 +276,13 @@ bool AffiliationLookup::hasGroupAff(uint32_t dstId) const bool AffiliationLookup::isGroupAff(uint32_t srcId, uint32_t dstId) const { + __spinlock(); + // lookup dynamic affiliation table entry m_grpAffTable.lock(false); - if (m_grpAffTable.find(srcId) != m_grpAffTable.end()) { - uint32_t tblDstId = m_grpAffTable.at(srcId); - if (tblDstId == dstId) { + auto it = m_grpAffTable.find(srcId); + if (it != m_grpAffTable.end()) { + if (it->second == dstId) { m_grpAffTable.unlock(); return true; } @@ -288,10 +319,14 @@ std::vector AffiliationLookup::clearGroupAff(uint32_t dstId, bool rele } } + __lock(); + for (auto srcId : srcToRel) { m_grpAffTable.erase(srcId); } + __unlock(); + return srcToRel; } @@ -312,6 +347,8 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi return false; } + __lock(); + m_grantChTable[dstId] = chNo; m_grantSrcIdTable[dstId] = srcId; m_rfGrantChCnt++; @@ -323,10 +360,12 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi m_grantTimers[dstId].start(); if (m_verbose) { - LogMessage(LOG_HOST, "%s, granting channel, chNo = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_HOST, "%s, granting channel, chNo = %u, dstId = %u, srcId = %u, group = %u", m_name.c_str(), chNo, dstId, srcId, grp); } + __unlock(); + return true; } @@ -338,9 +377,17 @@ void AffiliationLookup::touchGrant(uint32_t dstId) return; } + /* + ** bryanb: this doesn't __spinlock(), this is dangerous but necessary + ** otherwise an affiliations lookup lock can cause audio cuts if this is + ** used in an audio processing chain + */ + + m_grantTimers.lock(false); if (isGranted(dstId)) { m_grantTimers[dstId].start(); } + m_grantTimers.unlock(); } /* Helper to release the channel grant for the destination ID. */ @@ -355,7 +402,7 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); - m_grantChTable.lock(); + m_grantChTable.lock(false); std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { uint32_t dstId = entry.first; @@ -372,17 +419,29 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) } if (isGranted(dstId)) { - uint32_t chNo = m_grantChTable.at(dstId); + uint32_t chNo = 0U; + m_grantChTable.lock(false); + for (auto entry : m_grantChTable) { + if (entry.first == dstId) { + chNo = entry.second; + break; + } + } + m_grantChTable.unlock(); + + uint32_t srcId = getGrantedSrcId(dstId); if (m_verbose) { - LogMessage(LOG_HOST, "%s, releasing channel grant, chNo = %u, dstId = %u", + LogInfoEx(LOG_HOST, "%s, releasing channel grant, chNo = %u, dstId = %u", m_name.c_str(), chNo, dstId); } if (m_releaseGrant != nullptr) { - m_releaseGrant(chNo, dstId, 0U); + m_releaseGrant(chNo, srcId, dstId, 0U); } + __lock(); + m_grantChTable.erase(dstId); m_grantSrcIdTable.erase(dstId); m_uuGrantedTable.erase(dstId); @@ -398,6 +457,8 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_grantTimers[dstId].stop(); + __unlock(); + return true; } @@ -412,6 +473,8 @@ bool AffiliationLookup::isChBusy(uint32_t chNo) const return false; } + __spinlock(); + // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { @@ -433,6 +496,12 @@ bool AffiliationLookup::isGranted(uint32_t dstId) const return false; } + /* + ** bryanb: this doesn't __spinlock(), this is dangerous but necessary + ** otherwise an affiliations lookup lock can cause audio cuts if this is + ** used in an audio processing chain + */ + // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { @@ -457,6 +526,8 @@ bool AffiliationLookup::isGroup(uint32_t dstId) const return true; } + __spinlock(); + // lookup U-U grant flag table entry m_uuGrantedTable.lock(false); for (auto entry : m_uuGrantedTable) { @@ -481,6 +552,8 @@ bool AffiliationLookup::isNetGranted(uint32_t dstId) const return false; } + __spinlock(); + // lookup net granted flag table entry m_netGrantedTable.lock(false); for (auto entry : m_netGrantedTable) { @@ -505,8 +578,17 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId) return 0U; } + __spinlock(); + if (isGranted(dstId)) { - return m_grantChTable[dstId]; + // lookup dynamic channel grant table entry + m_grantChTable.lock(false); + auto it = m_grantChTable.find(dstId); + if (it != m_grantChTable.end()) { + m_grantChTable.unlock(); + return m_grantChTable[dstId]; + } + m_grantChTable.unlock(); } return 0U; @@ -516,6 +598,8 @@ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId) uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo) { + __spinlock(); + // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { @@ -537,6 +621,8 @@ uint32_t AffiliationLookup::getGrantedBySrcId(uint32_t srcId) return 0U; } + __spinlock(); + // lookup dynamic channel grant source table entry m_grantSrcIdTable.lock(false); for (auto entry : m_grantSrcIdTable) { @@ -558,8 +644,17 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) return 0U; } + __spinlock(); + if (isGranted(dstId)) { - return m_grantSrcIdTable[dstId]; + // lookup dynamic channel grant source table entry + m_grantSrcIdTable.lock(false); + auto it = m_grantSrcIdTable.find(dstId); + if (it != m_grantSrcIdTable.end()) { + m_grantSrcIdTable.unlock(); + return m_grantSrcIdTable[dstId]; + } + m_grantSrcIdTable.unlock(); } return 0U; diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 23893914a..f14cbd9a8 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -21,6 +21,7 @@ #define __AFFILIATION_LOOKUP_H__ #include "common/Defines.h" +#include "common/concurrent/concurrent_lock.h" #include "common/concurrent/vector.h" #include "common/concurrent/unordered_map.h" #include "common/lookups/ChannelLookup.h" @@ -41,7 +42,7 @@ namespace lookups * and group affiliation information. * @ingroup lookups_aff */ - class HOST_SW_API AffiliationLookup { + class HOST_SW_API AffiliationLookup : public concurrent::concurrent_lock { public: /** * @brief Initializes a new instance of the AffiliationLookup class. @@ -265,11 +266,15 @@ namespace lookups /** * @brief Helper to set the release grant callback. + * @note Do not call AffiliationLookup get functions from within this callback, deadlock protection + * is not guaranteed. * @param callback Relase grant function callback. */ - void setReleaseGrantCallback(std::function&& callback) { m_releaseGrant = callback; } + void setReleaseGrantCallback(std::function&& callback) { m_releaseGrant = callback; } /** * @brief Helper to set the unit deregistration callback. + * @note Do not call AffiliationLookup get functions from within this callback, deadlock protection + * is not guaranteed. * @param callback Unit deregistration function callback. */ void setUnitDeregCallback(std::function&& callback) { m_unitDereg = callback; } @@ -287,8 +292,8 @@ namespace lookups concurrent::unordered_map m_netGrantedTable; concurrent::unordered_map m_grantTimers; - // chNo dstId slot - std::function m_releaseGrant; + // chNo srcId dstId slot + std::function m_releaseGrant; // srcId auto std::function m_unitDereg; diff --git a/src/common/lookups/ChannelLookup.h b/src/common/lookups/ChannelLookup.h index 545fe1519..2c442093d 100644 --- a/src/common/lookups/ChannelLookup.h +++ b/src/common/lookups/ChannelLookup.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/lookups/IdenTableLookup.cpp b/src/common/lookups/IdenTableLookup.cpp index 50bb082e2..3d1a4dd13 100644 --- a/src/common/lookups/IdenTableLookup.cpp +++ b/src/common/lookups/IdenTableLookup.cpp @@ -22,7 +22,7 @@ using namespace lookups; // Static Class Members // --------------------------------------------------------------------------- -std::mutex IdenTableLookup::m_mutex; +std::mutex IdenTableLookup::s_mutex; // --------------------------------------------------------------------------- // Public Class Members @@ -39,7 +39,7 @@ IdenTableLookup::IdenTableLookup(const std::string& filename, uint32_t reloadTim void IdenTableLookup::clear() { - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); m_table.clear(); } @@ -49,7 +49,7 @@ IdenTable IdenTableLookup::find(uint32_t id) { IdenTable entry; - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); try { entry = m_table.at(id); } catch (...) { @@ -103,7 +103,7 @@ bool IdenTableLookup::load() // clear table clear(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // read lines from file std::string line; @@ -143,7 +143,7 @@ bool IdenTableLookup::load() IdenTable entry = IdenTable(channelId, baseFrequency, chSpaceKhz, txOffsetMhz, chBandwidthKhz); - LogMessage(LOG_HOST, "Channel Id %u: BaseFrequency = %uHz, TXOffsetMhz = %fMHz, BandwidthKhz = %fKHz, SpaceKhz = %fKHz", + LogInfoEx(LOG_HOST, "Channel Id %u: BaseFrequency = %uHz, TXOffsetMhz = %fMHz, BandwidthKhz = %fKHz, SpaceKhz = %fKHz", entry.channelId(), entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); m_table[channelId] = entry; @@ -163,7 +163,7 @@ bool IdenTableLookup::load() /* Saves the table to the passed lookup table file. */ -bool IdenTableLookup::save() +bool IdenTableLookup::save(bool quiet) { return false; } \ No newline at end of file diff --git a/src/common/lookups/IdenTableLookup.h b/src/common/lookups/IdenTableLookup.h index 68fd10800..864155b41 100644 --- a/src/common/lookups/IdenTableLookup.h +++ b/src/common/lookups/IdenTableLookup.h @@ -154,12 +154,13 @@ namespace lookups /** * @brief Saves the table to the passed lookup table file. + * @param quiet Disable logging during save operation. * @returns bool True, if lookup table was saved, otherwise false. */ - bool save() override; + bool save(bool quiet = false) override; private: - static std::mutex m_mutex; + static std::mutex s_mutex; }; } // namespace lookups diff --git a/src/common/lookups/LookupTable.h b/src/common/lookups/LookupTable.h index 2a80d90b6..ecd056123 100644 --- a/src/common/lookups/LookupTable.h +++ b/src/common/lookups/LookupTable.h @@ -199,9 +199,10 @@ namespace lookups /** * @brief Saves the table from the lookup table in memory. + * @param quiet Disable logging during save operation. * @returns bool True, if lookup table was saved, otherwise false. */ - virtual bool save() = 0; + virtual bool save(bool quiet = false) = 0; }; } // namespace lookups diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index 9215e3b9c..674c72b33 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -22,8 +22,8 @@ using namespace lookups; // Static Class Members // --------------------------------------------------------------------------- -std::mutex PeerListLookup::m_mutex; -bool PeerListLookup::m_locked = false; +std::mutex PeerListLookup::s_mutex; +bool PeerListLookup::s_locked = false; // --------------------------------------------------------------------------- // Macros @@ -31,16 +31,16 @@ bool PeerListLookup::m_locked = false; // Lock the table. #define __LOCK_TABLE() \ - std::lock_guard lock(m_mutex); \ - m_locked = true; + std::lock_guard lock(s_mutex); \ + s_locked = true; // Unlock the table. -#define __UNLOCK_TABLE() m_locked = false; +#define __UNLOCK_TABLE() s_locked = false; // Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ - if (m_locked) { \ - while (m_locked) \ + if (s_locked) { \ + while (s_locked) \ Thread::sleep(2U); \ } @@ -123,9 +123,9 @@ PeerId PeerListLookup::find(uint32_t id) /* Commit the table. */ -void PeerListLookup::commit() +void PeerListLookup::commit(bool quiet) { - save(); + save(quiet); } /* Gets whether the lookup is enabled. */ @@ -223,9 +223,9 @@ bool PeerListLookup::load() alias = parsed[3].c_str(); // parse peer link flag - bool peerLink = false; + bool peerReplica = false; if (parsed.size() >= 3) - peerLink = ::atoi(parsed[2].c_str()) == 1; + peerReplica = ::atoi(parsed[2].c_str()) == 1; // parse can request keys flag bool canRequestKeys = false; @@ -237,6 +237,11 @@ bool PeerListLookup::load() if (parsed.size() >= 6) canIssueInhibit = ::atoi(parsed[5].c_str()) == 1; + // parse can issue inhibit flag + bool hasCallPriority = false; + if (parsed.size() >= 7) + hasCallPriority = ::atoi(parsed[6].c_str()) == 1; + // parse optional password std::string password = ""; if (parsed.size() >= 2) @@ -244,19 +249,21 @@ bool PeerListLookup::load() // load into table PeerId entry = PeerId(id, alias, password, false); - entry.peerLink(peerLink); + entry.peerReplica(peerReplica); entry.canRequestKeys(canRequestKeys); entry.canIssueInhibit(canIssueInhibit); + entry.hasCallPriority(hasCallPriority); m_table[id] = entry; // log depending on what was loaded - LogMessage(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s", id, + LogInfoEx(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s%s", id, (!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!password.empty() ? "using unique peer password" : "using master password"), - (peerLink) ? ", Peer-Link Enabled" : "", + (peerReplica) ? ", Replication Enabled" : "", (canRequestKeys) ? ", Can Request Keys" : "", - (canIssueInhibit) ? ", Can Issue Inhibit" : ""); + (canIssueInhibit) ? ", Can Issue Inhibit" : "", + (hasCallPriority) ? ", Has Call Priority" : ""); } } @@ -273,7 +280,7 @@ bool PeerListLookup::load() /* Saves the table to the passed lookup table file. */ -bool PeerListLookup::save() +bool PeerListLookup::save(bool quiet) { if (m_filename.empty()) { return false; @@ -285,12 +292,13 @@ bool PeerListLookup::save() return false; } - LogMessage(LOG_HOST, "Saving peer lookup file to %s", m_filename.c_str()); + if (!quiet) + LogInfoEx(LOG_HOST, "Saving peer lookup file to %s", m_filename.c_str()); // Counter for lines written unsigned int lines = 0; - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // String for writing std::string line; @@ -310,9 +318,9 @@ bool PeerListLookup::save() } line += ","; - // add peerLink flag - bool peerLink = entry.second.peerLink(); - if (peerLink) { + // add peer replication flag + bool peerReplica = entry.second.peerReplica(); + if (peerReplica) { line += "1,"; } else { line += "0,"; @@ -342,6 +350,14 @@ bool PeerListLookup::save() line += "0,"; } + // add hasCallPriority flag + bool hasCallPriority = entry.second.hasCallPriority(); + if (hasCallPriority) { + line += "1,"; + } else { + line += "0,"; + } + line += "\n"; file << line; lines++; @@ -352,7 +368,8 @@ bool PeerListLookup::save() if (lines != m_table.size()) return false; - LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); + if (!quiet) + LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); return true; } diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index 5b7dd15b8..9d9563c42 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -49,9 +49,10 @@ namespace lookups m_peerId(0U), m_peerAlias(), m_peerPassword(), - m_peerLink(false), + m_peerReplica(false), m_canRequestKeys(false), m_canIssueInhibit(false), + m_hasCallPriority(false), m_peerDefault(false) { /* stub */ @@ -68,9 +69,10 @@ namespace lookups m_peerId(peerId), m_peerAlias(peerAlias), m_peerPassword(peerPassword), - m_peerLink(false), + m_peerReplica(false), m_canRequestKeys(false), m_canIssueInhibit(false), + m_hasCallPriority(false), m_peerDefault(peerDefault) { /* stub */ @@ -86,9 +88,10 @@ namespace lookups m_peerId = data.m_peerId; m_peerAlias = data.m_peerAlias; m_peerPassword = data.m_peerPassword; - m_peerLink = data.m_peerLink; + m_peerReplica = data.m_peerReplica; m_canRequestKeys = data.m_canRequestKeys; m_canIssueInhibit = data.m_canIssueInhibit; + m_hasCallPriority = data.m_hasCallPriority; m_peerDefault = data.m_peerDefault; } @@ -117,7 +120,7 @@ namespace lookups */ DECLARE_PROPERTY_PLAIN(uint32_t, peerId); /** - * @breif Peer Alias + * @brief Peer Alias */ DECLARE_PROPERTY_PLAIN(std::string, peerAlias); /** @@ -125,9 +128,9 @@ namespace lookups */ DECLARE_PROPERTY_PLAIN(std::string, peerPassword); /** - * @brief Flag indicating if the peer participates in peer link and should be sent configuration. + * @brief Flag indicating if the peer participates in peer replication and should be sent configuration. */ - DECLARE_PROPERTY_PLAIN(bool, peerLink); + DECLARE_PROPERTY_PLAIN(bool, peerReplica); /** * @brief Flag indicating if the peer can request encryption keys. */ @@ -136,6 +139,10 @@ namespace lookups * @brief Flag indicating if the peer can issue inhibit/uninhibit packets. */ DECLARE_PROPERTY_PLAIN(bool, canIssueInhibit); + /** + * @brief Flag indicating if the peer has call transmit priority. + */ + DECLARE_PROPERTY_PLAIN(bool, hasCallPriority); /** * @brief Flag indicating if the peer is default. */ @@ -186,8 +193,9 @@ namespace lookups /** * @brief Commit the table. + * @param quiet Disable logging during save operation. */ - void commit(); + void commit(bool quiet = false); /** * @brief Gets whether the lookup is enabled. @@ -236,13 +244,14 @@ namespace lookups /** * @brief Saves the table to the passed lookup table file. + * @param quiet Disable logging during save operation. * @return True, if lookup table was saved, otherwise false. */ - bool save() override; + bool save(bool quiet = false) override; private: - static std::mutex m_mutex; //! Mutex used for change locking. - static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + static std::mutex s_mutex; //!< Mutex used for change locking. + static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. }; } // namespace lookups diff --git a/src/common/lookups/RadioIdLookup.cpp b/src/common/lookups/RadioIdLookup.cpp index 8cb5fc129..1e1a2bb94 100644 --- a/src/common/lookups/RadioIdLookup.cpp +++ b/src/common/lookups/RadioIdLookup.cpp @@ -24,8 +24,8 @@ using namespace lookups; // Static Class Members // --------------------------------------------------------------------------- -std::mutex RadioIdLookup::m_mutex; -bool RadioIdLookup::m_locked = false; +std::mutex RadioIdLookup::s_mutex; +bool RadioIdLookup::s_locked = false; // --------------------------------------------------------------------------- // Macros @@ -33,16 +33,16 @@ bool RadioIdLookup::m_locked = false; // Lock the table. #define __LOCK_TABLE() \ - std::lock_guard lock(m_mutex); \ - m_locked = true; + std::lock_guard lock(s_mutex); \ + s_locked = true; // Unlock the table. -#define __UNLOCK_TABLE() m_locked = false; +#define __UNLOCK_TABLE() s_locked = false; // Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ - if (m_locked) { \ - while (m_locked) \ + if (s_locked) { \ + while (s_locked) \ Thread::sleep(2U); \ } @@ -148,9 +148,9 @@ RadioId RadioIdLookup::find(uint32_t id) /* Saves loaded talkgroup rules. */ -void RadioIdLookup::commit() +void RadioIdLookup::commit(bool quiet) { - save(); + save(quiet); } /* Flag indicating whether radio ID access control is enabled or not. */ @@ -241,7 +241,7 @@ bool RadioIdLookup::load() /* Saves the table to the passed lookup table file. */ -bool RadioIdLookup::save() +bool RadioIdLookup::save(bool quiet) { if (m_filename.empty()) { return false; @@ -253,12 +253,13 @@ bool RadioIdLookup::save() return false; } - LogMessage(LOG_HOST, "Saving RID lookup file to %s", m_filename.c_str()); + if (!quiet) + LogInfoEx(LOG_HOST, "Saving RID lookup file to %s", m_filename.c_str()); // Counter for lines written unsigned int lines = 0; - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // String for writing std::string line; @@ -296,7 +297,8 @@ bool RadioIdLookup::save() if (lines != m_table.size()) return false; - LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); + if (!quiet) + LogInfoEx(LOG_HOST, "Saved %u entries to lookup table file %s", lines, m_filename.c_str()); return true; } diff --git a/src/common/lookups/RadioIdLookup.h b/src/common/lookups/RadioIdLookup.h index 2e65400a4..54afb4752 100644 --- a/src/common/lookups/RadioIdLookup.h +++ b/src/common/lookups/RadioIdLookup.h @@ -184,8 +184,9 @@ namespace lookups /** * @brief Saves loaded radio ID lookups. + * @param quiet Disable logging during save operation. */ - void commit(); + void commit(bool quiet = false); /** * @brief Flag indicating whether radio ID access control is enabled or not. @@ -203,13 +204,14 @@ namespace lookups /** * @brief Saves the table to the passed lookup table file. + * @param quiet Disable logging during save operation. * @return True, if lookup table was saved, otherwise false. */ - bool save() override; + bool save(bool quiet = false) override; private: - static std::mutex m_mutex; //! Mutex used for change locking. - static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + static std::mutex s_mutex; //!< Mutex used for change locking. + static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. }; } // namespace lookups diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index 5e6d175cd..146890436 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -22,8 +22,8 @@ using namespace lookups; // Static Class Members // --------------------------------------------------------------------------- -std::mutex TalkgroupRulesLookup::m_mutex; -bool TalkgroupRulesLookup::m_locked = false; +std::mutex TalkgroupRulesLookup::s_mutex; +bool TalkgroupRulesLookup::s_locked = false; // --------------------------------------------------------------------------- // Macros @@ -31,16 +31,16 @@ bool TalkgroupRulesLookup::m_locked = false; // Lock the table. #define __LOCK_TABLE() \ - std::lock_guard lock(m_mutex); \ - m_locked = true; + std::lock_guard lock(s_mutex); \ + s_locked = true; // Unlock the table. -#define __UNLOCK_TABLE() m_locked = false; +#define __UNLOCK_TABLE() s_locked = false; // Spinlock wait for table to be read unlocked. #define __SPINLOCK() \ - if (m_locked) { \ - while (m_locked) \ + if (s_locked) { \ + while (s_locked) \ Thread::sleep(2U); \ } @@ -143,8 +143,7 @@ void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled, boo __LOCK_TABLE(); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), - [&](TalkgroupRuleGroupVoice x) - { + [&](TalkgroupRuleGroupVoice& x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } @@ -192,8 +191,7 @@ void TalkgroupRulesLookup::addEntry(TalkgroupRuleGroupVoice groupVoice) __LOCK_TABLE(); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), - [&](TalkgroupRuleGroupVoice x) - { + [&](TalkgroupRuleGroupVoice& x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } @@ -216,7 +214,10 @@ void TalkgroupRulesLookup::eraseEntry(uint32_t id, uint8_t slot) { __LOCK_TABLE(); - auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { return x.source().tgId() == id && x.source().tgSlot() == slot; }); + auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), + [&](TalkgroupRuleGroupVoice& x) { + return x.source().tgId() == id && x.source().tgSlot() == slot; + }); if (it != m_groupVoice.end()) { m_groupVoice.erase(it); } @@ -232,9 +233,9 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot) __SPINLOCK(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), - [&](TalkgroupRuleGroupVoice x) + [&](TalkgroupRuleGroupVoice& x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; @@ -259,17 +260,15 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByRewrite(uint32_t peerId, uin __SPINLOCK(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), - [&](TalkgroupRuleGroupVoice x) - { + [&](TalkgroupRuleGroupVoice& x) { if (x.config().rewrite().size() == 0) return false; std::vector rewrite = x.config().rewrite(); auto innerIt = std::find_if(rewrite.begin(), rewrite.end(), - [&](TalkgroupRuleRewrite y) - { + [&](TalkgroupRuleRewrite& y) { if (slot != 0U) { return y.peerId() == peerId && y.tgId() == id && y.tgSlot() == slot; } @@ -292,9 +291,9 @@ TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByRewrite(uint32_t peerId, uin /* Saves loaded talkgroup rules. */ -bool TalkgroupRulesLookup::commit() +bool TalkgroupRulesLookup::commit(bool quiet) { - return save(); + return save(quiet); } /* Flag indicating whether talkgroup ID access control is enabled or not. */ @@ -337,7 +336,7 @@ bool TalkgroupRulesLookup::load() if (groupVoiceList.size() == 0U) { ::LogError(LOG_HOST, "No group voice rules list defined!"); - m_locked = false; + s_locked = false; return false; } @@ -384,14 +383,14 @@ bool TalkgroupRulesLookup::load() /* Saves the table to the passed lookup table file. */ -bool TalkgroupRulesLookup::save() +bool TalkgroupRulesLookup::save(bool quiet) { // Make sure file is valid if (m_rulesFile.length() <= 0) { return false; } - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // New list for our new group voice rules yaml::Node groupVoiceList; @@ -415,9 +414,9 @@ bool TalkgroupRulesLookup::save() } try { - LogMessage(LOG_HOST, "Saving talkgroup rules file to %s", m_rulesFile.c_str()); + if (!quiet) + LogInfoEx(LOG_HOST, "Saving talkgroup rules file to %s", m_rulesFile.c_str()); yaml::Serialize(newRules, m_rulesFile.c_str()); - LogDebug(LOG_HOST, "Saved TGID config file to %s", m_rulesFile.c_str()); } catch (yaml::OperationException const& e) { LogError(LOG_HOST, "Cannot save the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 0b52b4a70..4d68de153 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -609,8 +609,9 @@ namespace lookups /** * @brief Saves loaded talkgroup rules. + * @param quiet Disable logging during save operation. */ - bool commit(); + bool commit(bool quiet = false); /** * @brief Flag indicating whether talkgroup ID access control is enabled or not. @@ -643,8 +644,8 @@ namespace lookups bool m_acl; bool m_stop; - static std::mutex m_mutex; //! Mutex used for change locking. - static bool m_locked; //! Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. + static std::mutex s_mutex; //!< Mutex used for change locking. + static bool s_locked; //!< Flag used for read locking (prevents find lookups), should be used when atomic operations (add/erase/etc) are being used. /** * @brief Loads the table from the passed lookup table file. @@ -653,9 +654,10 @@ namespace lookups bool load(); /** * @brief Saves the table to the passed lookup table file. + * @param quiet Disable logging during save operation. * @return True, if lookup table was saved, otherwise false. */ - bool save(); + bool save(bool quiet = false); public: /** diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index b3f6e2298..0a50e58ee 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -163,10 +163,17 @@ bool BaseNetwork::writeActLog(const char* message) char buffer[DATA_PACKET_LENGTH]; uint32_t len = ::strlen(message); +#if !defined(_WIN32) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif ::strncpy(buffer + 11U, message, len); - +#if !defined(_WIN32) +#pragma GCC diagnostic pop +#endif + return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, (uint8_t*)buffer, (uint32_t)len + 11U, - RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); + RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics); } /* Writes the local diagnostics log to the network. */ @@ -184,10 +191,17 @@ bool BaseNetwork::writeDiagLog(const char* message) char buffer[DATA_PACKET_LENGTH]; uint32_t len = ::strlen(message); +#if !defined(_WIN32) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif ::strncpy(buffer + 11U, message, len); +#if !defined(_WIN32) +#pragma GCC diagnostic pop +#endif return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG }, (uint8_t*)buffer, (uint32_t)len + 11U, - RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); + RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics); } /* Writes the local status to the network. */ @@ -209,10 +223,17 @@ bool BaseNetwork::writePeerStatus(json::object obj) char buffer[DATA_PACKET_LENGTH]; uint32_t len = ::strlen(json.c_str()); +#if !defined(_WIN32) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" +#endif ::strncpy(buffer + 11U, json.c_str(), len); +#if !defined(_WIN32) +#pragma GCC diagnostic pop +#endif return writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, (uint8_t*)buffer, (uint32_t)len + 11U, - RTP_END_OF_CALL_SEQ, 0U, false, m_useAlternatePortForDiagnostics); + RTP_END_OF_CALL_SEQ, 0U, m_useAlternatePortForDiagnostics); } /* Writes a group affiliation to the network. */ @@ -376,7 +397,7 @@ uint32_t BaseNetwork::getDMRStreamId(uint32_t slotNo) const /* Helper to send a data message to the master. */ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, - bool queueOnly, bool useAlternatePort, uint32_t peerId, uint32_t ssrc) + bool useAlternatePort, uint32_t peerId, uint32_t ssrc) { if (peerId == 0U) peerId = m_peerId; @@ -391,17 +412,11 @@ bool BaseNetwork::writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data uint16_t port = udp::Socket::port(m_addr) + 1U; if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { - if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); - else - m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, addr, addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); } } else { - if (!queueOnly) - return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen); - else - m_frameQueue->enqueueMessage(data, length, streamId, m_peerId, opcode, pktSeq, m_addr, m_addrLen); + return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, m_addr, m_addrLen); } return true; diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 79cdb8f3b..1a056772a 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -32,8 +32,8 @@ #include "common/p25/lc/LC.h" #include "common/p25/Audio.h" #include "common/nxdn/lc/RTCH.h" +#include "common/json/json.h" #include "common/network/FrameQueue.h" -#include "common/network/json/json.h" #include "common/network/udp/Socket.h" #include "common/RingBuffer.h" #include "common/Utils.h" @@ -63,13 +63,15 @@ #define TAG_REPEATER_GRANT "RPTG" #define TAG_REPEATER_KEY "RKEY" +#define TAG_INCALL_CTRL "ICC " + #define TAG_TRANSFER "TRNS" #define TAG_TRANSFER_ACT_LOG "TRNSLOG" #define TAG_TRANSFER_DIAG_LOG "TRNSDIAG" #define TAG_TRANSFER_STATUS "TRNSSTS" #define TAG_ANNOUNCE "ANNC" -#define TAG_PEER_LINK "PRLNK" +#define TAG_PEER_REPLICA "REPL" #define MAX_PEER_PING_TIME 60U // 60 seconds @@ -93,25 +95,27 @@ namespace network const uint32_t NXDN_PACKET_LENGTH = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer const uint32_t ANALOG_PACKET_LENGTH = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer + const uint32_t HA_PARAMS_ENTRY_LEN = 20U; + /** * @brief Network Peer Connection Status * @ingroup network_core */ enum NET_CONN_STATUS { // Common States - NET_STAT_WAITING_CONNECT, //! Waiting for Connection - NET_STAT_WAITING_LOGIN, //! Waiting for Login - NET_STAT_WAITING_AUTHORISATION, //! Waiting for Authorization - NET_STAT_WAITING_CONFIG, //! Waiting for Configuration - NET_STAT_RUNNING, //! Peer Running + NET_STAT_WAITING_CONNECT, //!< Waiting for Connection + NET_STAT_WAITING_LOGIN, //!< Waiting for Login + NET_STAT_WAITING_AUTHORISATION, //!< Waiting for Authorization + NET_STAT_WAITING_CONFIG, //!< Waiting for Configuration + NET_STAT_RUNNING, //!< Peer Running // Master States - NET_STAT_RPTL_RECEIVED, //! Login Received - NET_STAT_CHALLENGE_SENT, //! Authentication Challenge Sent + NET_STAT_RPTL_RECEIVED, //!< Login Received + NET_STAT_CHALLENGE_SENT, //!< Authentication Challenge Sent - NET_STAT_MST_RUNNING, //! Master Running + NET_STAT_MST_RUNNING, //!< Master Running - NET_STAT_INVALID = 0x7FFFFFF //! Invalid + NET_STAT_INVALID = 0x7FFFFFF //!< Invalid }; /** @@ -119,20 +123,21 @@ namespace network * @ingroup network_core */ enum NET_CONN_NAK_REASON { - NET_CONN_NAK_GENERAL_FAILURE, //! General Failure + NET_CONN_NAK_GENERAL_FAILURE, //!< General Failure - NET_CONN_NAK_MODE_NOT_ENABLED, //! Mode Not Enabled - NET_CONN_NAK_ILLEGAL_PACKET, //! Illegal Packet + NET_CONN_NAK_MODE_NOT_ENABLED, //!< Mode Not Enabled + NET_CONN_NAK_ILLEGAL_PACKET, //!< Illegal Packet - NET_CONN_NAK_FNE_UNAUTHORIZED, //! FNE Unauthorized - NET_CONN_NAK_BAD_CONN_STATE, //! Bad Connection State - NET_CONN_NAK_INVALID_CONFIG_DATA, //! Invalid Configuration Data - NET_CONN_NAK_PEER_RESET, //! Peer Reset - NET_CONN_NAK_PEER_ACL, //! Peer ACL + NET_CONN_NAK_FNE_UNAUTHORIZED, //!< FNE Unauthorized + NET_CONN_NAK_BAD_CONN_STATE, //!< Bad Connection State + NET_CONN_NAK_INVALID_CONFIG_DATA, //!< Invalid Configuration Data + NET_CONN_NAK_PEER_RESET, //!< Peer Reset + NET_CONN_NAK_PEER_ACL, //!< Peer ACL - NET_CONN_NAK_FNE_MAX_CONN, //! FNE Maximum Connections + NET_CONN_NAK_FNE_MAX_CONN, //!< FNE Maximum Connections + NET_CONN_NAK_FNE_DUPLICATE_CONN, //!< FNE Duplicate Connection - NET_CONN_NAK_INVALID = 0xFFFF //! Invalid + NET_CONN_NAK_INVALID = 0xFFFF //!< Invalid }; /** @@ -141,11 +146,219 @@ namespace network * @ingroup network_core */ enum CONTROL_BYTE { - NET_CTRL_GRANT_DEMAND = 0x80U, //! Grant Demand - NET_CTRL_GRANT_DENIAL = 0x40U, //! Grant Denial - NET_CTRL_SWITCH_OVER = 0x20U, //! Call Source RID Switch Over - NET_CTRL_GRANT_ENCRYPT = 0x08U, //! Grant Encrypt - NET_CTRL_U2U = 0x01U, //! Unit-to-Unit + NET_CTRL_GRANT_DEMAND = 0x80U, //!< Grant Demand + NET_CTRL_GRANT_DENIAL = 0x40U, //!< Grant Denial + NET_CTRL_SWITCH_OVER = 0x20U, //!< Call Source RID Switch Over + NET_CTRL_GRANT_ENCRYPT = 0x08U, //!< Grant Encrypt + NET_CTRL_U2U = 0x01U, //!< Unit-to-Unit + }; + + /** + * @brief RTP Stream Multiplex Validation Return Codes + * @ingroup network_core + */ + enum MULTIPLEX_RET_CODE { + MUX_VALID_SUCCESS = 0U, //!< Successful Validation + + MUX_LOST_FRAMES = 1U, //!< Lost Frames + MUX_OUT_OF_ORDER = 2U //!< Out-of-Order + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Handles dealing with maintaining RTP sequencing for multiple multiplexed RTP streams. + * @ingroup fne_network + */ + class HOST_SW_API RTPStreamMultiplex { + public: + auto operator=(RTPStreamMultiplex&) -> RTPStreamMultiplex& = delete; + auto operator=(RTPStreamMultiplex&&) -> RTPStreamMultiplex& = delete; + RTPStreamMultiplex(RTPStreamMultiplex&) = delete; + + /** + * @brief Initializes a new instance of the RTPStreamMultiplex class. + */ + RTPStreamMultiplex() : + m_mutex(), + m_streamSeqNos() + { + /* stub */ + } + /** + * @brief Finalizes a instance of the RTPStreamMultiplex class. + */ + ~RTPStreamMultiplex() + { + m_streamSeqNos.clear(); + } + + /** + * @brief Helper to verify the given RTP sequence for the given multiplexed RTP stream. + * @param streamId Stream ID. + * @param pktSeq Packet Sequence. + * @param func Network function. + * @param[out] lastRxSeq Last Received Sequence. + * @return MULTIPLEX_RET_CODE Return code. + */ + MULTIPLEX_RET_CODE verifyStream(uint64_t streamId, uint16_t pktSeq, uint8_t func, uint16_t* lastRxSeq) + { + MULTIPLEX_RET_CODE ret = MUX_VALID_SUCCESS; + if (pktSeq == RTP_END_OF_CALL_SEQ) { + // only reset packet sequences if we're a PROTOCOL or RPTC function + if ((func == NET_FUNC::PROTOCOL) || (func == NET_FUNC::RPTC)) { + erasePktSeq(streamId); // attempt to erase packet sequence for the stream + } + } else { + if (hasPktSeq(streamId)) { + *lastRxSeq = getPktSeq(streamId); + + if (*lastRxSeq == RTP_END_OF_CALL_SEQ) { + // reset the received sequence back to 0 + setPktSeq(streamId, 0U); + } + else { + if ((pktSeq >= *lastRxSeq) || (pktSeq == 0U)) { + // if the sequence isn't 0, and is greater then the last received sequence + 1 frame + // assume a packet was lost + if ((pktSeq != 0U) && pktSeq >= *lastRxSeq + 1U) { + ret = MUX_LOST_FRAMES; + } + + setPktSeq(streamId, pktSeq); + } + else { + if (pktSeq < *lastRxSeq) { + ret = MUX_OUT_OF_ORDER; + } + } + } + } + } + + return ret; + } + + /** + * @brief Helper to return the current count of multiplexed RTP streams. + * @returns size_t Count of stored streams. + */ + size_t streamCount() + { + return m_streamSeqNos.size(); + } + + /** + * @brief Helper to determine if the given multiplexed stream has a stored RTP sequence. + * @param streamId Stream ID. + * @returns bool True, if stream ID has a stored RTP sequence, otherwise false. + */ + bool hasPktSeq(uint64_t streamId) + { + bool ret = false; + std::lock_guard lock(m_mutex); + + // determine if the stream has a current sequence no and return + { + auto it = m_streamSeqNos.find(streamId); + if (it == m_streamSeqNos.end()) { + ret = false; + } + else { + ret = true; + } + } + + return ret; + } + + /** + * @brief Helper to get the stored RTP sequence for the given multiplexed stream. + * @param streamId Stream ID. + * @returns uint16_t Sequence number. + */ + uint16_t getPktSeq(uint64_t streamId) + { + std::lock_guard lock(m_mutex); + + // find the current sequence no and return + uint32_t pktSeq = 0U; + { + auto it = m_streamSeqNos.find(streamId); + if (it == m_streamSeqNos.end()) { + pktSeq = RTP_END_OF_CALL_SEQ; + } else { + pktSeq = m_streamSeqNos[streamId]; + } + } + + return pktSeq; + } + + /** + * @brief Helper to set the stored RTP sequence for the given multiplexed stream. + * @param streamId Stream ID. + * @param seq Sequence number. + */ + void setPktSeq(uint64_t streamId, uint16_t seq) + { + std::lock_guard lock(m_mutex); + auto it = m_streamSeqNos.find(streamId); + if (it == m_streamSeqNos.end()) { + m_streamSeqNos.insert({streamId, seq}); + } else { + m_streamSeqNos[streamId] = seq; + } + } + + /** + * @brief Helper to increment the stored RTP sequence for the given multiplexed stream. + * @param streamId Stream ID. + * @returns uint16_t Incremented packet sequence. + */ + uint16_t incPktSeq(uint64_t streamId) + { + std::lock_guard lock(m_mutex); + + uint32_t pktSeq = 0U; + auto it = m_streamSeqNos.find(streamId); + if (it == m_streamSeqNos.end()) { + m_streamSeqNos.insert({streamId, pktSeq}); + } else { + pktSeq = m_streamSeqNos[streamId]; + ++pktSeq; + + if (pktSeq > (RTP_END_OF_CALL_SEQ - 1U)) + pktSeq = 0U; + + m_streamSeqNos[streamId] = pktSeq; + } + + return pktSeq; + } + + /** + * @brief Helper to erase the stored RTP sequence for the given multiplexed stream. + * @param streamId Stream ID. + */ + void erasePktSeq(uint64_t streamId) + { + std::lock_guard lock(m_mutex); + + // find the sequence no and erase + { + auto entry = m_streamSeqNos.find(streamId); + if (entry != m_streamSeqNos.end()) { + m_streamSeqNos.erase(streamId); + } + } + } + + private: + std::recursive_mutex m_mutex; + std::unordered_map m_streamSeqNos; }; // --------------------------------------------------------------------------- @@ -183,6 +396,26 @@ namespace network /** * @brief Writes a grant request to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the group affiliation + * announcement message. The message is 24 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source ID | Destination ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination ID | Reserved |S| Reserverd | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Mode | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param mode Digital Mode. * @param srcId Source Radio ID. * @param dstId Destination Radio ID. @@ -202,6 +435,22 @@ namespace network /** * @brief Writes the local activity log to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the activity log message. + * The message is variable length bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Log Message . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Log Message ................................................. | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param message Textual string to send as activity log information. * @returns bool True, if message was sent, otherwise false. */ @@ -209,13 +458,100 @@ namespace network /** * @brief Writes the local diagnostic logs to the network. - * @param message Textual string to send as diagnostic log information. + * \code{.unparsed} + * Below is the representation of the data layout for the diagnostic log message. + * The message is variable length bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Log Message . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Log Message ................................................. | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @returns bool True, if message was sent, otherwise false. */ virtual bool writeDiagLog(const char* message); /** * @brief Writes the local status to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the peer status message. + * The message is variable length bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Variable | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length JSON Payload ......................................... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The JSON payload is variable length and looks like this: + * { + * "state": , + * "isTxCW": , + * "fixedMode": , + * "dmrTSCCEnable": , + * "dmrCC": , + * "p25CtrlEnable": , + * "p25CC": , + * "nxdnCtrlEnable": , + * "nxdnCC": , + * "tx": , + * "channelId": , + * "channelNo": , + * "lastDstId": , + * "lastSrcId": , + * "peerId": , + * "sysId": , + * "siteId": , + * "p25RfssId": , + * "p25NetId": , + * "p25NAC": , + * "vcChannels": [ + * { + * "channelId": , + * "channelNo": , + * "tx": , + * "lastDstId": , + * "lastSrcId": , + * } + * ], + * "modem": { + * "portType": "", + * "modemPort": "", + * "portSpeed": , + * "rxLevel": , + * "cwTxLevel": , + * "dmrTxLevel": , + * "p25TxLevel": , + * "nxdnTxLevel": , + * "rxDCOffset": , + * "txDCOffset": , + * "fdmaPremables": , + * "dmrRxDelay": , + * "p25CorrCount": , + * "rxFrequency": , + * "txFrequency": , + * "rxTuning": , + * "txTuning": , + * "rxFrequencyEffective": , + * "txFrequencyEffective": , + * "v24Connected": , + * "protoVer": + * } + * } + * \endcode * @param obj JSON object representing the local peer status. * @returns bool True, if peer status was sent, otherwise false. */ @@ -242,6 +578,16 @@ namespace network virtual bool announceGroupAffiliation(uint32_t srcId, uint32_t dstId); /** * @brief Writes a group affiliation removal to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the unit registration + * announcement message. The message is 3 bytes in length. + * + * Byte 0 1 2 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param srcId Source Radio ID. * @returns bool True, if group affiliation announcement was sent, otherwise false. */ @@ -265,6 +611,16 @@ namespace network virtual bool announceUnitRegistration(uint32_t srcId); /** * @brief Writes a unit deregistration to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the unit deregistration + * announcement message. The message is 3 bytes in length. + * + * Byte 0 1 2 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param srcId Source Radio ID. * @returns bool True, if unit deregistration announcement was sent, otherwise false. */ @@ -272,6 +628,22 @@ namespace network /** * @brief Writes a complete update of the peer affiliation list to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is variable bytes in length. + * + * Each affiliation update entry is 7 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Source ID | E: Dst Id | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Destination ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param affs Complete map of peer unit affiliations. * @returns bool True, if affiliation update announcement was sent, otherwise false. */ @@ -279,6 +651,20 @@ namespace network /** * @brief Writes a complete update of the peer's voice channel list to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is variable bytes in length. + * + * Each peer ID entry is 4 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param peers List of voice channel peers. * @returns bool True, if peer update announcement was sent, otherwise false. */ @@ -348,15 +734,13 @@ namespace network * @param length Length of buffer to write. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param queueOnly Flag indicating this message should be queued instead of send immediately. * @param useAlternatePort Flag indicating the message shuold be sent using the alternate port (mainly for activity and diagnostics). * @param peerId If non-zero, overrides the peer ID sent in the packet to the master. * @param ssrc If non-zero, overrides the RTP synchronization source ID sent in the packet to the master. * @returns bool True, if message was sent, otherwise false. */ bool writeMaster(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, - uint16_t pktSeq, uint32_t streamId, bool queueOnly = false, bool useAlternatePort = false, uint32_t peerId = 0U, - uint32_t ssrc = 0U); + uint16_t pktSeq, uint32_t streamId, bool useAlternatePort = false, uint32_t peerId = 0U, uint32_t ssrc = 0U); // Digital Mobile Radio /** @@ -592,6 +976,9 @@ namespace network * | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * + * S = Slot Number (clear Slot 1, set Slot 2) + * G = Group Flag + * * The data starting at offset 20 for 33 bytes of the raw DMR frame. * * DMR frame message has 2 trailing bytes: @@ -754,6 +1141,8 @@ namespace network * | Blk to Flw | Current Block | DUID | Frame Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * + * C = Confirmed PDU Flag + * * The data starting at offset 24 for variable number of bytes (DUID dependant) * is the P25 frame. * \endcode @@ -789,6 +1178,8 @@ namespace network * | | Frame Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * + * G = Group Flag + * * The data starting at offset 24 for 48 bytes if the raw NXDN frame. * \endcode * @param[out] length Length of network message buffer. diff --git a/src/common/network/FrameQueue.cpp b/src/common/network/FrameQueue.cpp index b5442fe1a..d25871386 100644 --- a/src/common/network/FrameQueue.cpp +++ b/src/common/network/FrameQueue.cpp @@ -172,17 +172,21 @@ bool FrameQueue::write(const uint8_t* message, uint32_t length, uint32_t streamI /* Cache message to frame queue. */ -void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, - OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) +void FrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId, + uint32_t peerId, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) { - enqueueMessage(message, length, streamId, peerId, peerId, opcode, rtpSeq, addr, addrLen); + enqueueMessage(queue, message, length, streamId, peerId, peerId, opcode, rtpSeq, addr, addrLen); } /* Cache message to frame queue. */ -void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, - uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) +void FrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId, + uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen) { + if (queue == nullptr) { + LogError(LOG_NET, "FrameQueue::enqueueMessage(), queue is null"); + return; + } if (message == nullptr) { LogError(LOG_NET, "FrameQueue::enqueueMessage(), message is null"); return; @@ -207,7 +211,7 @@ void FrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, uint32_ dgram->address = addr; dgram->addrLen = addrLen; - m_buffers.push_back(dgram); + queue->push(dgram); } /* Helper method to clear any tracked stream timestamps. */ diff --git a/src/common/network/FrameQueue.h b/src/common/network/FrameQueue.h index b4411ee0e..4db7a933b 100644 --- a/src/common/network/FrameQueue.h +++ b/src/common/network/FrameQueue.h @@ -90,6 +90,7 @@ namespace network /** * @brief Cache message to frame queue. + * @param[in] queue Queue of messages. * @param[in] message Message buffer to frame and queue. * @param length Length of message. * @param streamId Message stream ID. @@ -99,10 +100,11 @@ namespace network * @param addr IP address to write data to. * @param addrLen */ - void enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, - OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); + void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId, + uint32_t peerId, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); /** * @brief Cache message to frame queue. + * @param[in] queue Queue of messages. * @param[in] message Message buffer to frame and queue. * @param length Length of message. * @param streamId Message stream ID. @@ -113,8 +115,8 @@ namespace network * @param addr IP address to write data to. * @param addrLen */ - void enqueueMessage(const uint8_t* message, uint32_t length, uint32_t streamId, uint32_t peerId, - uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); + void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, uint32_t streamId, + uint32_t peerId, uint32_t ssrc, OpcodePair opcode, uint16_t rtpSeq, sockaddr_storage& addr, uint32_t addrLen); /** * @brief Helper method to clear any tracked stream timestamps. diff --git a/src/common/network/NetRPC.cpp b/src/common/network/NetRPC.cpp index f03824b20..6005eccc5 100644 --- a/src/common/network/NetRPC.cpp +++ b/src/common/network/NetRPC.cpp @@ -10,8 +10,8 @@ #include "Defines.h" #include "common/edac/CRC.h" #include "common/edac/SHA256.h" +#include "common/json/json.h" #include "common/network/RPCHeader.h" -#include "common/network/json/json.h" #include "common/Log.h" #include "common/Thread.h" #include "common/Utils.h" @@ -286,7 +286,7 @@ void NetRPC::defaultResponse(json::object& reply, std::string message, StatusTyp bool NetRPC::open() { if (m_debug) - LogMessage(LOG_NET, "Opening RPC network"); + LogInfoEx(LOG_NET, "Opening RPC network"); // generate AES256 key size_t size = m_password.size(); @@ -313,16 +313,56 @@ bool NetRPC::open() void NetRPC::close() { if (m_debug) - LogMessage(LOG_NET, "Closing RPC network"); + LogInfoEx(LOG_NET, "Closing RPC network"); m_socket->close(); } +/* Helper to register an RPC handler. */ + +bool NetRPC::registerHandler(uint16_t func, RPCType handler) +{ + // 32767 is the maximum possible function + if (func > RPC_MAX_FUNC) + return false; + + auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&](RPCHandlerMapPair x) { return x.first == func; }); + if (it != m_handlers.end()) { + LogError(LOG_HOST, "NetRPC::registerHandler() can't register RPC $%04X already registered. BUGBUG.", func); + return false; // handler is already registered + } + + if (m_debug) + LogDebugEx(LOG_HOST, "NetRPC::registerHandler()", "registering RPC $%04X", func); + + m_handlers[func] = handler; + return true; +} + +/* Helper to unregister an RPC handler. */ + +bool NetRPC::unregisterHandler(uint16_t func) +{ + // 32767 is the maximum possible function + if (func > RPC_MAX_FUNC) + return false; + + auto it = std::find_if(m_handlers.begin(), m_handlers.end(), [&](RPCHandlerMapPair x) { return x.first == func; }); + if (it != m_handlers.end()) { + if (m_debug) + LogDebugEx(LOG_HOST, "NetRPC::registerHandler()", "unregistering RPC $%04X", func); + + m_handlers.erase(func); + return true; + } + + return false; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- - /* Writes an RPC reply to the network. */ bool NetRPC::reply(uint16_t func, json::object& reply, sockaddr_storage& address, uint32_t addrLen) diff --git a/src/common/network/NetRPC.h b/src/common/network/NetRPC.h index 2b36d0e95..2401f327b 100644 --- a/src/common/network/NetRPC.h +++ b/src/common/network/NetRPC.h @@ -21,9 +21,9 @@ #endif // defined(_WIN32) #include "common/Defines.h" +#include "common/json/json.h" #include "common/network/udp/Socket.h" #include "common/network/RawFrameQueue.h" -#include "common/network/json/json.h" #include #include @@ -63,13 +63,17 @@ namespace network * @brief Status/Response Codes */ enum StatusType { - OK = 200, //! OK 200 + OK = 200, //!< OK 200 - BAD_REQUEST = 400, //! Bad Request 400 - INVALID_ARGS = 401, //! Invalid Arguments 401 - UNHANDLED_REQUEST = 402, //! Unhandled Request 402 + BAD_REQUEST = 400, //!< Bad Request 400 + INVALID_ARGS = 401, //!< Invalid Arguments 401 + UNHANDLED_REQUEST = 402, //!< Unhandled Request 402 } status; + auto operator=(NetRPC&) -> NetRPC& = delete; + auto operator=(NetRPC&&) -> NetRPC& = delete; + NetRPC(NetRPC&) = delete; + /** * @brief Initializes a new instance of the NetRPC class. * @param address Network Hostname/IP address to connect to. @@ -137,13 +141,15 @@ namespace network * @brief Helper to register an RPC handler. * @param func Function opcode. * @param handler Function handler. + * @returns bool True, if handler is registered, otherwise false. */ - void registerHandler(uint16_t func, RPCType handler) { m_handlers[func] = handler; } + bool registerHandler(uint16_t func, RPCType handler); /** * @brief Helper to unregister an RPC handler. * @param func Function opcode. + * @returns bool True, if handler is unregistered, otherwise false. */ - void unregisterHandler(uint16_t func) { m_handlers.erase(func); } + bool unregisterHandler(uint16_t func); private: std::string m_address; @@ -156,6 +162,7 @@ namespace network std::string m_password; + typedef std::pair RPCHandlerMapPair; std::map m_handlers; std::map m_handlerReplied; diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index fee310144..8814fdb62 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -9,8 +9,8 @@ */ #include "Defines.h" #include "common/edac/SHA256.h" -#include "common/network/json/json.h" #include "common/p25/kmm/KMMFactory.h" +#include "common/json/json.h" #include "common/Log.h" #include "common/Utils.h" #include "network/Network.h" @@ -25,7 +25,13 @@ using namespace network; // Constants // --------------------------------------------------------------------------- +#define DEFAULT_RETRY_TIME 10U // 10 seconds +#define DUPLICATE_CONN_RETRY_TIME 3600U // 60 minutes + #define MAX_RETRY_BEFORE_RECONNECT 4U +#define MAX_RETRY_HA_RECONNECT 2U +#define MAX_RETRY_DUP_RECONNECT 2U + #define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds // --------------------------------------------------------------------------- @@ -41,6 +47,10 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_pktLastSeq(0U), m_address(address), m_port(port), + m_configuredAddress(address), + m_configuredPort(port), + m_haIPs(), + m_currentHAIP(0U), m_password(password), m_enabled(false), m_dmrEnabled(dmr), @@ -52,12 +62,16 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_ridLookup(nullptr), m_tidLookup(nullptr), m_salt(nullptr), - m_retryTimer(1000U, 10U), + m_retryTimer(1000U, DEFAULT_RETRY_TIME), m_retryCount(0U), + m_maxRetryCount(MAX_RETRY_BEFORE_RECONNECT), + m_flaggedDuplicateConn(false), m_timeoutTimer(1000U, MAX_PEER_PING_TIME), + m_pingsReceived(0U), m_pktSeq(0U), m_loginStreamId(0U), m_metadata(nullptr), + m_mux(nullptr), m_remotePeerId(0U), m_promiscuousPeer(false), m_userHandleProtocol(false), @@ -84,6 +98,7 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_rxAnalogStreamId = 0U; m_metadata = new PeerMetadata(); + m_mux = new RTPStreamMultiplex(); } /* Finalizes a instance of the Network class. */ @@ -93,6 +108,7 @@ Network::~Network() delete[] m_salt; delete[] m_rxDMRStreamId; delete m_metadata; + delete m_mux; } /* Resets the DMR ring buffer for the given slot. */ @@ -187,19 +203,24 @@ void Network::clock(uint32_t ms) m_retryTimer.clock(ms); if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { if (m_enabled) { - if (m_retryCount > MAX_RETRY_BEFORE_RECONNECT) { - m_retryCount = 0U; + if (m_retryCount > m_maxRetryCount) { + if (m_flaggedDuplicateConn) { + LogError(LOG_NET, "PEER %u exceeded maximum duplicate connection retries, increasing delay between connection attempts", m_peerId); + } + LogError(LOG_NET, "PEER %u connection to the master has timed out, retrying connection, remotePeerId = %u", m_peerId, m_remotePeerId); close(); open(); + m_retryCount = 0U; m_retryTimer.start(); return; } bool ret = m_socket->open(m_addr.ss_family); if (ret) { + m_socket->recvBufSize(262144U); // 256K recv buffer ret = writeLogin(); if (!ret) { m_retryTimer.start(); @@ -279,11 +300,6 @@ void Network::clock(uint32_t ms) } m_pktSeq = rtpHeader.getSequence(); - - if (m_pktSeq == RTP_END_OF_CALL_SEQ) { - m_pktSeq = 0U; - m_pktLastSeq = 0U; - } // process incoming message function opcodes switch (fneHeader.getFunction()) { @@ -296,7 +312,7 @@ void Network::clock(uint32_t ms) break; } - // process incomfing message subfunction opcodes + // process incoming message subfunction opcodes switch (fneHeader.getSubFunction()) { case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame { @@ -311,6 +327,23 @@ void Network::clock(uint32_t ms) if (m_promiscuousPeer) { m_rxDMRStreamId[slotNo] = streamId; m_pktLastSeq = m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif } else { if (m_rxDMRStreamId[slotNo] == 0U) { @@ -325,18 +358,33 @@ void Network::clock(uint32_t ms) } else { if (m_rxDMRStreamId[slotNo] == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } - } + uint16_t lastRxSeq = 0U; - m_pktLastSeq = m_pktSeq; + MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u", + slotNo, streamId, m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u", + slotNo, streamId, m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "DMR Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxDMRStreamId[slotNo] = 0U; + } } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxDMRStreamId[slotNo] = 0U; - } + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (m_rxDMRStreamId[slotNo] != 0U && m_rxDMRStreamId[slotNo] != streamId) { + break; } } @@ -363,6 +411,23 @@ void Network::clock(uint32_t ms) if (m_promiscuousPeer) { m_rxP25StreamId = streamId; m_pktLastSeq = m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif } else { if (m_rxP25StreamId == 0U) { @@ -377,21 +442,37 @@ void Network::clock(uint32_t ms) } else { if (m_rxP25StreamId == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } - } + uint16_t lastRxSeq = 0U; - m_pktLastSeq = m_pktSeq; + MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "P25 stream %u possible lost frames; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "P25 stream %u out-of-order; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "P25 valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxP25StreamId = 0U; + } } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25StreamId = 0U; - } + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (m_rxP25StreamId != 0U && m_rxP25StreamId != streamId) { + break; } } + if (m_debug) Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length); if (length > 512) @@ -424,6 +505,23 @@ void Network::clock(uint32_t ms) if (m_promiscuousPeer) { m_rxNXDNStreamId = streamId; m_pktLastSeq = m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif } else { if (m_rxNXDNStreamId == 0U) { @@ -438,18 +536,33 @@ void Network::clock(uint32_t ms) } else { if (m_rxNXDNStreamId == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } - } + uint16_t lastRxSeq = 0U; - m_pktLastSeq = m_pktSeq; + MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "NXDN stream %u possible lost frames; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "NXDN stream %u out-of-order; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "NXDN valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxNXDNStreamId = 0U; + } } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxNXDNStreamId = 0U; - } + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (m_rxNXDNStreamId != 0U && m_rxNXDNStreamId != streamId) { + break; } } @@ -476,6 +589,23 @@ void Network::clock(uint32_t ms) if (m_promiscuousPeer) { m_rxAnalogStreamId = streamId; m_pktLastSeq = m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif } else { if (m_rxAnalogStreamId == 0U) { @@ -490,18 +620,33 @@ void Network::clock(uint32_t ms) } else { if (m_rxAnalogStreamId == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "Analog Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } - } + uint16_t lastRxSeq = 0U; - m_pktLastSeq = m_pktSeq; + MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "Analog stream %u possible lost frames; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "Analog stream %u out-of-order; got %u, expected %u", + streamId, m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "Analog valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxAnalogStreamId = 0U; + } } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxAnalogStreamId = 0U; - } + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (m_rxAnalogStreamId != 0U && m_rxAnalogStreamId != streamId) { + break; } } @@ -534,7 +679,7 @@ void Network::clock(uint32_t ms) case NET_FUNC::MASTER: // Master { - // process incomfing message subfunction opcodes + // process incoming message subfunction opcodes switch (fneHeader.getSubFunction()) { case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist { @@ -552,7 +697,7 @@ void Network::clock(uint32_t ms) offs += 4U; } - LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len); + LogInfoEx(LOG_NET, "Network Announced %u whitelisted RIDs", len); // save to file if enabled and we got RIDs if (m_saveLookup && len > 0) { @@ -578,7 +723,7 @@ void Network::clock(uint32_t ms) offs += 4U; } - LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len); + LogInfoEx(LOG_NET, "Network Announced %u blacklisted RIDs", len); // save to file if enabled and we got RIDs if (m_saveLookup && len > 0) { @@ -621,7 +766,7 @@ void Network::clock(uint32_t ms) m_tidLookup->eraseEntry(id, slot); } - LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table", + LogInfoEx(LOG_NET, "Activated%s%s TG %u TS %u in TGID table", (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot); m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred); } @@ -629,7 +774,7 @@ void Network::clock(uint32_t ms) offs += 5U; } - LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); + LogInfoEx(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); // save if saving from network is enabled if (m_saveLookup && len > 0) { @@ -655,14 +800,14 @@ void Network::clock(uint32_t ms) lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); if (!tid.isInvalid()) { - LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); + LogInfoEx(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); m_tidLookup->eraseEntry(id, slot); } offs += 5U; } - LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); + LogInfoEx(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); // save if saving from network is enabled if (m_saveLookup && len > 0) { @@ -673,6 +818,46 @@ void Network::clock(uint32_t ms) } break; + case NET_SUBFUNC::MASTER_HA_PARAMS: // HA Parameters + { + if (m_enabled) { + if (m_debug) + Utils::dump(1U, "Network::clock(), Network Rx, HA PARAMS", buffer.get(), length); + + m_haIPs.clear(); + m_currentHAIP = 0U; + m_maxRetryCount = MAX_RETRY_HA_RECONNECT; + + // always add the configured address to the HA IP list + m_haIPs.push_back(PeerHAIPEntry(m_configuredAddress, m_configuredPort)); + + uint32_t len = GET_UINT32(buffer, 6U); + if (len > 0U) { + len /= HA_PARAMS_ENTRY_LEN; + } + + uint8_t offs = 10U; + for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { + uint32_t ipAddr = GET_UINT32(buffer, offs + 4U); + uint16_t port = GET_UINT16(buffer, offs + 8U); + + std::string address = __IP_FROM_UINT(ipAddr); + + if (m_debug) + LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, %s:%u", address.c_str(), port); + + m_haIPs.push_back(PeerHAIPEntry(address, port)); + } + + if (m_haIPs.size() > 1U) { + m_currentHAIP = 1U; // because the first entry is our configured entry, set + // the current HA IP to the next available + LogInfoEx(LOG_NET, "Loaded %u HA IPs from master", m_haIPs.size() - 1U); + } + } + } + break; + default: Utils::dump("unknown master control opcode from the master", buffer.get(), length); break; @@ -682,7 +867,13 @@ void Network::clock(uint32_t ms) case NET_FUNC::INCALL_CTRL: // In-Call Control { - // process incomfing message subfunction opcodes + uint32_t ssrc = rtpHeader.getSSRC(); + if (!m_promiscuousPeer && ssrc != peerId) { + LogWarning(LOG_NET, "PEER %u, ignoring in-call control not destined for this peer SSRC %u", m_peerId, ssrc); + break; + } + + // process incoming message subfunction opcodes switch (fneHeader.getSubFunction()) { case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control { @@ -693,7 +884,7 @@ void Network::clock(uint32_t ms) // fire off DMR in-call callback if we have one if (m_dmrInCallCallback != nullptr) { - m_dmrInCallCallback(command, dstId, slot); + m_dmrInCallCallback(command, dstId, slot, peerId, ssrc, streamId); } } } @@ -706,7 +897,7 @@ void Network::clock(uint32_t ms) // fire off P25 in-call callback if we have one if (m_p25InCallCallback != nullptr) { - m_p25InCallCallback(command, dstId); + m_p25InCallCallback(command, dstId, peerId, ssrc, streamId); } } } @@ -719,7 +910,7 @@ void Network::clock(uint32_t ms) // fire off NXDN in-call callback if we have one if (m_nxdnInCallCallback != nullptr) { - m_nxdnInCallCallback(command, dstId); + m_nxdnInCallCallback(command, dstId, peerId, ssrc, streamId); } } } @@ -732,7 +923,7 @@ void Network::clock(uint32_t ms) // fire off analog in-call callback if we have one if (m_analogInCallCallback != nullptr) { - m_analogInCallCallback(command, dstId); + m_analogInCallCallback(command, dstId, peerId, ssrc, streamId); } } } @@ -765,7 +956,7 @@ void Network::clock(uint32_t ms) if (ks.keys().size() > 0U) { // fetch first key (a master response should never really send back more then one key) KeyItem ki = ks.keys()[0]; - LogMessage(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId, + LogInfoEx(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId, ks.algId(), ki.kId()); // fire off key response callback if we have one @@ -821,6 +1012,15 @@ void Network::clock(uint32_t ms) } break; + case NET_CONN_NAK_FNE_DUPLICATE_CONN: + LogWarning(LOG_NET, "PEER %u master NAK; duplicate connection to FNE, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + m_status = NET_STAT_WAITING_CONNECT; + m_remotePeerId = 0U; + m_flaggedDuplicateConn = true; + m_maxRetryCount = MAX_RETRY_DUP_RECONNECT; + m_retryTimer.start(); + return; + case NET_CONN_NAK_GENERAL_FAILURE: default: LogWarning(LOG_NET, "PEER %u master NAK; general failure, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); @@ -848,7 +1048,7 @@ void Network::clock(uint32_t ms) { switch (m_status) { case NET_STAT_WAITING_LOGIN: - LogMessage(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + LogInfoEx(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); ::memcpy(m_salt, buffer.get() + 6U, sizeof(uint32_t)); writeAuthorisation(); @@ -858,7 +1058,7 @@ void Network::clock(uint32_t ms) m_retryTimer.start(); break; case NET_STAT_WAITING_AUTHORISATION: - LogMessage(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + LogInfoEx(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); writeConfig(); @@ -867,7 +1067,7 @@ void Network::clock(uint32_t ms) m_retryTimer.start(); break; case NET_STAT_WAITING_CONFIG: - LogMessage(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + LogInfoEx(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); m_loginStreamId = 0U; m_remotePeerId = rtpHeader.getSSRC(); @@ -880,12 +1080,13 @@ void Network::clock(uint32_t ms) m_status = NET_STAT_RUNNING; m_timeoutTimer.start(); + m_retryTimer.setTimeout(DEFAULT_RETRY_TIME); m_retryTimer.start(); if (length > 6) { m_useAlternatePortForDiagnostics = (buffer[6U] & 0x80U) == 0x80U; if (m_useAlternatePortForDiagnostics) { - LogMessage(LOG_NET, "PEER %u RPTC ACK, master commanded alternate port for diagnostics and activity logging, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + LogInfoEx(LOG_NET, "PEER %u RPTC ACK, master commanded alternate port for diagnostics and activity logging, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); } else { // disable diagnostic and activity logging automatically if the master doesn't utilize the alternate port m_allowDiagnosticTransfer = false; @@ -935,6 +1136,13 @@ void Network::clock(uint32_t ms) uint64_t dt = (uint64_t)fabs((double)now - (double)serverNow); if (dt > MAX_SERVER_DIFF) LogWarning(LOG_NET, "PEER %u pong, time delay greater than %llums, now = %llu, server = %llu, dt = %llu", m_peerId, MAX_SERVER_DIFF, now, serverNow, dt); + + ++m_pingsReceived; + + // if we've been connected for at least 10 PING/PONG cycles and we're flagged duplicate connection, clear the flag + if (m_pingsReceived > 10U && m_flaggedDuplicateConn) { + m_flaggedDuplicateConn = false; + } } break; default: @@ -988,15 +1196,35 @@ bool Network::open() if (!m_enabled) return false; if (m_debug) - LogMessage(LOG_NET, "PEER %u opening network", m_peerId); + LogInfoEx(LOG_NET, "PEER %u opening network", m_peerId); m_status = NET_STAT_WAITING_CONNECT; + + // are we rotating IPs for HA reconnect? + if ((m_haIPs.size() - 1) > 1 && m_retryCount > 0U && !m_flaggedDuplicateConn && + m_maxRetryCount == MAX_RETRY_HA_RECONNECT) { + + if (m_currentHAIP > (m_haIPs.size() - 1)) { + m_currentHAIP = 0U; + } + + PeerHAIPEntry entry = m_haIPs[m_currentHAIP]; + m_currentHAIP++; + + LogInfoEx(LOG_NET, "PEER %u connection to the master has timed out, %s:%u is non-responsive, trying next HA %s:%u", m_peerId, + m_address.c_str(), m_port, entry.masterAddress.c_str(), entry.masterPort); + m_address = entry.masterAddress; + m_port = entry.masterPort; + } + m_timeoutTimer.start(); m_retryTimer.start(); m_retryCount = 0U; + m_pingsReceived = 0U; + if (udp::Socket::lookup(m_address, m_port, m_addr, m_addrLen) != 0) { - LogMessage(LOG_NET, "!!! Could not lookup the address of the master!"); + LogInfoEx(LOG_NET, "!!! Could not lookup the address of the master!"); return false; } @@ -1008,7 +1236,7 @@ bool Network::open() void Network::close() { if (m_debug) - LogMessage(LOG_NET, "PEER %u closing Network", m_peerId); + LogInfoEx(LOG_NET, "PEER %u closing Network", m_peerId); if (m_status == NET_STAT_RUNNING) { uint8_t buffer[1U]; @@ -1020,9 +1248,17 @@ void Network::close() m_socket->close(); m_retryTimer.stop(); + if (m_flaggedDuplicateConn) { + // if we were flagged a duplicate connection, increase the retry time to avoid rapid reconnect attempts + m_retryTimer.setTimeout(DUPLICATE_CONN_RETRY_TIME); + } + else { + m_retryTimer.setTimeout(DEFAULT_RETRY_TIME); + } m_timeoutTimer.stop(); m_status = NET_STAT_WAITING_CONNECT; + m_remotePeerId = 0U; } /* Sets flag enabling network communication. */ @@ -1036,6 +1272,38 @@ void Network::enable(bool enabled) // Protected Class Members // --------------------------------------------------------------------------- +/* Helper to verify the given RTP sequence for the given RTP stream. */ + +MULTIPLEX_RET_CODE Network::verifyStream(uint16_t* lastRxSeq) +{ + MULTIPLEX_RET_CODE ret = MUX_VALID_SUCCESS; + if (m_pktSeq == RTP_END_OF_CALL_SEQ) { + // reset the received sequence back to 0 + m_pktLastSeq = 0U; + } + else { + *lastRxSeq = m_pktLastSeq; + + if ((m_pktSeq >= m_pktLastSeq) || (m_pktSeq == 0U)) { + // if the sequence isn't 0, and is greater then the last received sequence + 1 frame + // assume a packet was lost + if ((m_pktSeq != 0U) && m_pktSeq > m_pktLastSeq + 1U) { + ret = MUX_LOST_FRAMES; + } + + m_pktLastSeq = m_pktSeq; + } + else { + if (m_pktSeq < m_pktLastSeq) { + ret = MUX_OUT_OF_ORDER; + } + } + } + + m_pktLastSeq = m_pktSeq; + return ret; +} + /* User overrideable handler that allows user code to process network packets not handled by this class. */ void Network::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, @@ -1052,6 +1320,11 @@ bool Network::writeLogin() return false; } + // reset retry timer default timeout + if (m_retryTimer.isRunning()) + m_retryTimer.stop(); + m_retryTimer.setTimeout(DEFAULT_RETRY_TIME); + uint8_t buffer[8U]; ::memcpy(buffer + 0U, TAG_REPEATER_LOGIN, 4U); SET_UINT32(m_peerId, buffer, 4U); // Peer ID diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 740bfbc04..7f14e917c 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -40,36 +40,87 @@ namespace network */ struct PeerMetadata { /** @name Identity and Frequency */ - std::string identity; //! Peer Identity - uint32_t rxFrequency; //! Peer Rx Frequency - uint32_t txFrequency; //! Peer Tx Frequency + std::string identity; //!< Peer Identity + uint32_t rxFrequency; //!< Peer Rx Frequency + uint32_t txFrequency; //!< Peer Tx Frequency /** @} */ /** @name System Info */ - uint32_t power; //! Peer Tx Power (W) - float latitude; //! Location Latitude (decmial notation) - float longitude; //! Location Longitude (decmial notation) - int height; //! Height (M) - std::string location; //! Textual Location + uint32_t power; //!< Peer Tx Power (W) + float latitude; //!< Location Latitude (decmial notation) + float longitude; //!< Location Longitude (decmial notation) + int height; //!< Height (M) + std::string location; //!< Textual Location /** @} */ /** @name Channel Data */ - float txOffsetMhz; //! Tx Offset (MHz) - float chBandwidthKhz; //! Channel Bandwidth (kHz) - uint8_t channelId; //! Channel ID - uint32_t channelNo; //! Channel Number + float txOffsetMhz; //!< Tx Offset (MHz) + float chBandwidthKhz; //!< Channel Bandwidth (kHz) + uint8_t channelId; //!< Channel ID + uint32_t channelNo; //!< Channel Number /** @} */ /** @name RCON */ - std::string restApiPassword; //! REST API Password - uint16_t restApiPort; //! REST API Port + std::string restApiPassword; //!< REST API Password + uint16_t restApiPort; //!< REST API Port /** @} */ /** @name Flags */ - bool isConventional; //! Flag indicating peer is a conventional peer. + bool isConventional; //!< Flag indicating peer is a conventional peer. /** @} */ }; + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents high availiability IP address data. + * @ingroup network_core + */ + struct PeerHAIPEntry { + public: + /** + * @brief Initializes a new instance of the PeerHAIPEntry class + */ + PeerHAIPEntry() : + masterAddress(), + masterPort(TRAFFIC_DEFAULT_PORT) + { + /* stub **/ + } + + /** + * @brief Initializes a new instance of the PeerHAIPEntry class + * @param address Master IP Address. + * @param port Master Port. + */ + PeerHAIPEntry(std::string address, uint16_t port) : + masterAddress(address), + masterPort(port) + { + /* stub */ + } + + /** + * @brief Equals operator. Copies this PeerHAIPEntry to another PeerHAIPEntry. + * @param data Instance of PeerHAIPEntry to copy. + */ + PeerHAIPEntry& operator=(const PeerHAIPEntry& data) + { + if (this != &data) { + masterAddress = data.masterAddress; + masterPort = data.masterPort; + } + + return *this; + } + + public: + std::string masterAddress; //!< Master IP Address. + uint16_t masterPort; //!< Master Port. + }; + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -193,6 +244,11 @@ namespace network */ void enable(bool enabled); + /** + * @brief Helper to clear the duplicate connection flag. + */ + void clearDuplicateConnFlag() { m_flaggedDuplicateConn = false; } + /** * @brief Helper to set the peer connected callback. * @param callback @@ -208,22 +264,22 @@ namespace network * @brief Helper to set the DMR In-Call Control callback. * @param callback */ - void setDMRICCCallback(std::function&& callback) { m_dmrInCallCallback = callback; } + void setDMRICCCallback(std::function&& callback) { m_dmrInCallCallback = callback; } /** * @brief Helper to set the P25 In-Call Control callback. * @param callback */ - void setP25ICCCallback(std::function&& callback) { m_p25InCallCallback = callback; } + void setP25ICCCallback(std::function&& callback) { m_p25InCallCallback = callback; } /** * @brief Helper to set the NXDN In-Call Control callback. * @param callback */ - void setNXDNICCCallback(std::function&& callback) { m_nxdnInCallCallback = callback; } + void setNXDNICCCallback(std::function&& callback) { m_nxdnInCallCallback = callback; } /** * @brief Helper to set the analog In-Call Control callback. * @param callback */ - void setAnalogICCCallback(std::function&& callback) { m_analogInCallCallback = callback; } + void setAnalogICCCallback(std::function&& callback) { m_analogInCallCallback = callback; } /** * @brief Helper to set the enc. key response callback. @@ -241,6 +297,12 @@ namespace network std::string m_address; uint16_t m_port; + std::string m_configuredAddress; + uint16_t m_configuredPort; + + std::vector m_haIPs; + uint8_t m_currentHAIP; + std::string m_password; bool m_enabled; @@ -260,8 +322,12 @@ namespace network Timer m_retryTimer; uint8_t m_retryCount; + uint8_t m_maxRetryCount; + bool m_flaggedDuplicateConn; Timer m_timeoutTimer; + uint32_t m_pingsReceived; + uint32_t* m_rxDMRStreamId; uint32_t m_rxP25StreamId; uint32_t m_rxNXDNStreamId; @@ -271,6 +337,7 @@ namespace network uint32_t m_loginStreamId; PeerMetadata* m_metadata; + RTPStreamMultiplex* m_mux; uint32_t m_remotePeerId; @@ -303,22 +370,26 @@ namespace network * @brief DMR In-Call Control Function Callback. * (This is called when the master sends a In-Call Control request.) */ - std::function m_dmrInCallCallback; + std::function m_dmrInCallCallback; /** * @brief P25 In-Call Control Function Callback. * (This is called once the master sends a In-Call Control request.) */ - std::function m_p25InCallCallback; + std::function m_p25InCallCallback; /** * @brief NXDN In-Call Control Function Callback. * (This is called once the master sends a In-Call Control request.) */ - std::function m_nxdnInCallCallback; + std::function m_nxdnInCallCallback; /** * @brief Analog In-Call Control Function Callback. * (This is called once the master sends a In-Call Control request.) */ - std::function m_analogInCallCallback; + std::function m_analogInCallCallback; /** * @brief Encryption Key Response Function Callback. @@ -326,6 +397,13 @@ namespace network */ std::function m_keyRespCallback; + /** + * @brief Helper to verify the given RTP sequence for the given RTP stream. + * @param[out] lastRxSeq Last Received Sequence. + * @return MULTIPLEX_RET_CODE Return code. + */ + MULTIPLEX_RET_CODE verifyStream(uint16_t* lastRxSeq); + /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. * @param peerId Peer ID. @@ -341,21 +419,104 @@ namespace network /** * @brief Writes login request to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is 8 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (RPTL) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @returns bool True, if login request was sent, otherwise false. */ bool writeLogin(); /** * @brief Writes network authentication challenge. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is 40 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (RPTK) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 30 bytes of SHA-256 ......................................... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @returns bool True, if authorization response was sent, otherwise false. */ bool writeAuthorisation(); /** * @brief Writes modem configuration to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is 40 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (RPTC) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Variable Length JSON Payload ................................ | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The JSON payload is variable length and looks like this: + * { + * "identity": "", + * "rxFrequency": , + * "txFrequency": , + * "info": + * { + * "latitude": , + * "longitude": , + * "height": , + * "location": "" + * }, + * "channel": + * { + * "txPower": , + * "txOffsetMhz": , + * "chBandwidthKhz": , + * "channelId": , + * "channelNo": , + * }, + * "software": "", + * } + * + * These are extra parameters used in the root of the above JSON. + * { + * "externalPeer": , + * "masterPeerId": , + * + * "conventionalPeer": , + * + * "sysView": , + * } + * \endcode * @returns bool True, if configuration response was sent, otherwise false. */ virtual bool writeConfig(); /** * @brief Writes a network stay-alive ping. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is 1 bytes in length. + * + * Byte 0 + * Bit 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+ + * \endcode * @returns bool True, if stay-alive ping was sent, otherwise false. */ bool writePing(); diff --git a/src/common/network/PacketBuffer.cpp b/src/common/network/PacketBuffer.cpp index 686a34b6c..48a4bb786 100644 --- a/src/common/network/PacketBuffer.cpp +++ b/src/common/network/PacketBuffer.cpp @@ -80,7 +80,6 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL // do we have all the blocks? if (fragments.size() == blockCnt + 1U) { - uint8_t* buffer = nullptr; fragments.lock(false); if (fragments[0] == nullptr) { LogError(LOG_NET, "%s, Packet Fragment, error missing block 0? Packet dropped.", m_name); @@ -106,8 +105,7 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL uint32_t compressedLen = fragments[0]->compressedSize; uint32_t len = fragments[0]->size; - buffer = new uint8_t[len + 1U]; - ::memset(buffer, 0x00U, len + 1U); + DECLARE_UINT8_ARRAY(buffer, len + 1U); if (fragments.size() == 1U) { ::memcpy(buffer, fragments[0U]->data, len); } else { @@ -136,7 +134,11 @@ bool PacketBuffer::decode(const uint8_t* data, uint8_t** message, uint32_t* outL } } else { - *message = buffer; + uint8_t* outBuf = new uint8_t[len + 1U]; + ::memset(outBuf, 0x00U, len + 1U); + ::memcpy(outBuf, buffer, len); + + *message = outBuf; if (outLength != nullptr) { *outLength = len; } diff --git a/src/common/network/PacketBuffer.h b/src/common/network/PacketBuffer.h index c26f2a217..4aa4e13de 100644 --- a/src/common/network/PacketBuffer.h +++ b/src/common/network/PacketBuffer.h @@ -39,9 +39,27 @@ namespace network /** * @brief Represents a fragmented packet buffer. * @ingroup network_core + * \code{.unparsed} + * Below is the representation of the fragment layout for a packet buffer message. The ultimate + * buffer is variable length up to a maximum of 534 bytes for a single fragment. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Uncompressed Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Compressed Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Block Number | Total Blocks | Payload ..................... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode */ class HOST_SW_API PacketBuffer { public: + auto operator=(PacketBuffer&) -> PacketBuffer& = delete; + auto operator=(PacketBuffer&&) -> PacketBuffer& = delete; + PacketBuffer(PacketBuffer&) = delete; + /** * @brief Initializes a new instance of the PacketBuffer class. * @param compression Flag indicating whether packet data should be compressed automatically. diff --git a/src/common/network/RPCHeader.h b/src/common/network/RPCHeader.h index 40caa92a9..ac231d85a 100644 --- a/src/common/network/RPCHeader.h +++ b/src/common/network/RPCHeader.h @@ -24,6 +24,7 @@ #define RPC_HEADER_LENGTH_BYTES 8 #define RPC_REPLY_FUNC 0x8000U +#define RPC_MAX_FUNC 0x7FFFU namespace network { diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 8719f8dcc..3b8422768 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -44,35 +44,36 @@ namespace network */ namespace NET_FUNC { enum ENUM : uint8_t { - ILLEGAL = 0xFFU, //! Illegal Function + ILLEGAL = 0xFFU, //!< Illegal Function - PROTOCOL = 0x00U, //! Digital Protocol Function + PROTOCOL = 0x00U, //!< Digital Protocol Function - MASTER = 0x01U, //! Network Master Function + MASTER = 0x01U, //!< Network Master Function - RPTL = 0x60U, //! Repeater Login - RPTK = 0x61U, //! Repeater Authorisation - RPTC = 0x62U, //! Repeater Configuration + RPTL = 0x60U, //!< Repeater Login + RPTK = 0x61U, //!< Repeater Authorisation + RPTC = 0x62U, //!< Repeater Configuration - RPT_DISC = 0x70U, //! Repeater Disconnect - MST_DISC = 0x71U, //! Master Disconnect + RPT_DISC = 0x70U, //!< Repeater Disconnect + MST_DISC = 0x71U, //!< Master Disconnect - PING = 0x74U, //! Ping - PONG = 0x75U, //! Pong + PING = 0x74U, //!< Ping + PONG = 0x75U, //!< Pong - GRANT_REQ = 0x7AU, //! Grant Request - INCALL_CTRL = 0x7BU, //! In-Call Control - KEY_REQ = 0x7CU, //! Encryption Key Request - KEY_RSP = 0x7DU, //! Encryption Key Response + GRANT_REQ = 0x7AU, //!< Grant Request + INCALL_CTRL = 0x7BU, //!< In-Call Control + KEY_REQ = 0x7CU, //!< Encryption Key Request + KEY_RSP = 0x7DU, //!< Encryption Key Response - ACK = 0x7EU, //! Packet Acknowledge - NAK = 0x7FU, //! Packet Negative Acknowledge + ACK = 0x7EU, //!< Packet Acknowledge + NAK = 0x7FU, //!< Packet Negative Acknowledge - TRANSFER = 0x90U, //! Network Transfer Function + TRANSFER = 0x90U, //!< Network Transfer Function - ANNOUNCE = 0x91U, //! Network Announce Function + ANNOUNCE = 0x91U, //!< Network Announce Function - PEER_LINK = 0x92U //! FNE Peer-Link Function + REPL = 0x92U, //!< FNE Replication Function + NET_TREE = 0x93U //!< FNE Network Tree Function }; }; @@ -82,34 +83,40 @@ namespace network */ namespace NET_SUBFUNC { enum ENUM : uint8_t { - NOP = 0xFFU, //! No Operation Sub-Function - - PROTOCOL_SUBFUNC_DMR = 0x00U, //! DMR - PROTOCOL_SUBFUNC_P25 = 0x01U, //! P25 - PROTOCOL_SUBFUNC_NXDN = 0x02U, //! NXDN - PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //! Analog - - MASTER_SUBFUNC_WL_RID = 0x00U, //! Whitelist RIDs - MASTER_SUBFUNC_BL_RID = 0x01U, //! Blacklist RIDs - MASTER_SUBFUNC_ACTIVE_TGS = 0x02U, //! Active TGIDs - MASTER_SUBFUNC_DEACTIVE_TGS = 0x03U, //! Deactive TGIDs - - TRANSFER_SUBFUNC_ACTIVITY = 0x01U, //! Activity Log Transfer - TRANSFER_SUBFUNC_DIAG = 0x02U, //! Diagnostic Log Transfer - TRANSFER_SUBFUNC_STATUS = 0x03U, //! Status Transfer - - ANNC_SUBFUNC_GRP_AFFIL = 0x00U, //! Announce Group Affiliation - ANNC_SUBFUNC_UNIT_REG = 0x01U, //! Announce Unit Registration - ANNC_SUBFUNC_UNIT_DEREG = 0x02U, //! Announce Unit Deregistration - ANNC_SUBFUNC_GRP_UNAFFIL = 0x03U, //! Announce Group Affiliation Removal - ANNC_SUBFUNC_AFFILS = 0x90U, //! Update All Affiliations - ANNC_SUBFUNC_SITE_VC = 0x9AU, //! Announce Site VCs - - PL_TALKGROUP_LIST = 0x00U, //! FNE Peer-Link Talkgroup Transfer - PL_RID_LIST = 0x01U, //! FNE Peer-Link Radio ID Transfer - PL_PEER_LIST = 0x02U, //! FNE Peer-Link Peer List Transfer - - PL_ACT_PEER_LIST = 0xA2U, //! FNE Peer-Link Active Peer List Transfer + NOP = 0xFFU, //!< No Operation Sub-Function + + PROTOCOL_SUBFUNC_DMR = 0x00U, //!< DMR + PROTOCOL_SUBFUNC_P25 = 0x01U, //!< P25 + PROTOCOL_SUBFUNC_NXDN = 0x02U, //!< NXDN + PROTOCOL_SUBFUNC_P25_P2 = 0x03U, //!< P25 Phase 2 + PROTOCOL_SUBFUNC_ANALOG = 0x0FU, //!< Analog + + MASTER_SUBFUNC_WL_RID = 0x00U, //!< Whitelist RIDs + MASTER_SUBFUNC_BL_RID = 0x01U, //!< Blacklist RIDs + MASTER_SUBFUNC_ACTIVE_TGS = 0x02U, //!< Active TGIDs + MASTER_SUBFUNC_DEACTIVE_TGS = 0x03U, //!< Deactive TGIDs + MASTER_HA_PARAMS = 0xA3U, //!< HA Parameters + + TRANSFER_SUBFUNC_ACTIVITY = 0x01U, //!< Activity Log Transfer + TRANSFER_SUBFUNC_DIAG = 0x02U, //!< Diagnostic Log Transfer + TRANSFER_SUBFUNC_STATUS = 0x03U, //!< Status Transfer + + ANNC_SUBFUNC_GRP_AFFIL = 0x00U, //!< Announce Group Affiliation + ANNC_SUBFUNC_UNIT_REG = 0x01U, //!< Announce Unit Registration + ANNC_SUBFUNC_UNIT_DEREG = 0x02U, //!< Announce Unit Deregistration + ANNC_SUBFUNC_GRP_UNAFFIL = 0x03U, //!< Announce Group Affiliation Removal + ANNC_SUBFUNC_AFFILS = 0x90U, //!< Update All Affiliations + ANNC_SUBFUNC_SITE_VC = 0x9AU, //!< Announce Site VCs + + REPL_TALKGROUP_LIST = 0x00U, //!< FNE Replication Talkgroup Transfer + REPL_RID_LIST = 0x01U, //!< FNE Replication Radio ID Transfer + REPL_PEER_LIST = 0x02U, //!< FNE Replication Peer List Transfer + + REPL_ACT_PEER_LIST = 0xA2U, //!< FNE Replication Active Peer List Transfer + REPL_HA_PARAMS = 0xA3U, //!< FNE Replication HA Parameters + + NET_TREE_LIST = 0x00U, //!< FNE Network Tree List + NET_TREE_DISC = 0x01U //!< FNE Network Tree Disconnect }; }; @@ -119,10 +126,10 @@ namespace network */ namespace NET_ICC { enum ENUM : uint8_t { - NOP = 0xFFU, //! No Operation Sub-Function + NOP = 0xFFU, //!< No Operation Sub-Function - BUSY_DENY = 0x00U, //! Busy Deny - REJECT_TRAFFIC = 0x01U, //! Reject Active Traffic + BUSY_DENY = 0x00U, //!< Busy Deny + REJECT_TRAFFIC = 0x01U, //!< Reject Active Traffic }; }; diff --git a/src/common/network/RawFrameQueue.cpp b/src/common/network/RawFrameQueue.cpp index efbd3ad5a..24488b488 100644 --- a/src/common/network/RawFrameQueue.cpp +++ b/src/common/network/RawFrameQueue.cpp @@ -23,8 +23,7 @@ using namespace network; // Static Class Members // --------------------------------------------------------------------------- -std::mutex RawFrameQueue::m_queueMutex; -bool RawFrameQueue::m_queueFlushing = false; +std::mutex RawFrameQueue::s_flushMtx; // --------------------------------------------------------------------------- // Public Class Members @@ -34,7 +33,6 @@ bool RawFrameQueue::m_queueFlushing = false; RawFrameQueue::RawFrameQueue(udp::Socket* socket, bool debug) : m_socket(socket), - m_buffers(), m_failedReadCnt(0U), m_debug(debug) { @@ -43,10 +41,7 @@ RawFrameQueue::RawFrameQueue(udp::Socket* socket, bool debug) : /* Finalizes a instance of the RawFrameQueue class. */ -RawFrameQueue::~RawFrameQueue() -{ - deleteBuffers(); -} +RawFrameQueue::~RawFrameQueue() = default; /* Read message from the received UDP packet. */ @@ -124,8 +119,12 @@ bool RawFrameQueue::write(const uint8_t* message, uint32_t length, sockaddr_stor /* Cache message to frame queue. */ -void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen) +void RawFrameQueue::enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen) { + if (queue == nullptr) { + LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), queue is null"); + return; + } if (message == nullptr) { LogError(LOG_NET, "RawFrameQueue::enqueueMessage(), message is null"); return; @@ -135,13 +134,6 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock return; } - // if the queue is flushing -- don't attempt to enqueue any messages - if (m_queueFlushing) { - LogWarning(LOG_NET, "RawFrameQueue::enqueueMessage() -- queue is flushing, waiting to enqueue message"); - while (m_queueFlushing) - Thread::sleep(2U); - } - // bryanb: this is really a developer warning not a end-user warning, there's nothing the end-users can do about // this message if (length > (DATA_PACKET_LENGTH - OVERSIZED_PACKET_WARN)) { @@ -161,69 +153,37 @@ void RawFrameQueue::enqueueMessage(const uint8_t* message, uint32_t length, sock dgram->address = addr; dgram->addrLen = addrLen; - m_buffers.push_back(dgram); + queue->push(dgram); } /* Flush the message queue. */ -bool RawFrameQueue::flushQueue() +bool RawFrameQueue::flushQueue(udp::BufferQueue* queue) { - bool ret = true; - - // scope is intentional - { - std::lock_guard lock(m_queueMutex); - m_queueFlushing = true; + if (queue == nullptr) { + LogError(LOG_NET, "RawFrameQueue::flushQueue(), queue is null"); + return false; + } - if (m_buffers.empty()) { - return false; - } + std::lock_guard lock(s_flushMtx); - // bryanb: this is the same as above -- but for some assinine reason prevents - // weirdness - if (m_buffers.size() == 0U) { - return false; - } + if (queue->empty()) { + return false; + } - // LogDebug(LOG_NET, "m_buffers len = %u", m_buffers.size()); + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (queue->size() == 0U) { + return false; + } - ret = true; - if (!m_socket->write(m_buffers)) { - // LogError(LOG_NET, "Failed writing data to the network"); - ret = false; - } + // LogDebug(LOG_NET, "queue len = %u", queue.size()); - m_queueFlushing = false; + bool ret = true; + if (!m_socket->write(queue)) { + // LogError(LOG_NET, "Failed writing data to the network"); + ret = false; } - deleteBuffers(); return ret; } - -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - -/* Helper to ensure buffers are deleted. */ - -void RawFrameQueue::deleteBuffers() -{ - std::lock_guard lock(m_queueMutex); - m_queueFlushing = true; - - for (auto& buffer : m_buffers) { - if (buffer != nullptr) { - // LogDebug(LOG_NET, "deleting buffer, addr %p len %u", buffer->buffer, buffer->length); - if (buffer->buffer != nullptr) { - delete[] buffer->buffer; - buffer->length = 0; - buffer->buffer = nullptr; - } - - delete buffer; - buffer = nullptr; - } - } - m_buffers.clear(); - m_queueFlushing = false; -} diff --git a/src/common/network/RawFrameQueue.h b/src/common/network/RawFrameQueue.h index 74e566bf4..4cc5468b8 100644 --- a/src/common/network/RawFrameQueue.h +++ b/src/common/network/RawFrameQueue.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -78,37 +78,31 @@ namespace network /** * @brief Cache message to frame queue. + * @param[in] queue Queue of messages. * @param[in] message Message buffer to frame and queue. * @param length Length of message. * @param addr IP address to write data to. * @param addrLen */ - void enqueueMessage(const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen); + void enqueueMessage(udp::BufferQueue* queue, const uint8_t* message, uint32_t length, sockaddr_storage& addr, uint32_t addrLen); /** * @brief Flush the message queue. + * @param[in] queue Queue of messages. */ - bool flushQueue(); + bool flushQueue(udp::BufferQueue* queue); protected: sockaddr_storage m_addr; uint32_t m_addrLen; udp::Socket* m_socket; - static std::mutex m_queueMutex; - static bool m_queueFlushing; - udp::BufferVector m_buffers; + static std::mutex s_flushMtx; uint32_t m_failedReadCnt; bool m_debug; - - private: - /** - * @brief Helper to ensure buffers are deleted. - */ - void deleteBuffers(); }; } // namespace network -#endif // __RAW_FRAME_QUEUE_H__ \ No newline at end of file +#endif // __RAW_FRAME_QUEUE_H__ diff --git a/src/common/network/rest/RequestDispatcher.h b/src/common/network/rest/RequestDispatcher.h deleted file mode 100644 index 6a3b20fb3..000000000 --- a/src/common/network/rest/RequestDispatcher.h +++ /dev/null @@ -1,335 +0,0 @@ -// 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. - * - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL - * - */ -/** - * @defgroup rest REST Services - * @brief Implementation for REST services. - * @ingroup network_core - * @defgroup http Embedded HTTP Core - * @brief Implementation for basic HTTP services. - * @ingroup rest - * - * @file RequestDispatcher.h - * @ingroup rest - */ -#if !defined(__REST__DISPATCHER_H__) -#define __REST__DISPATCHER_H__ - -#include "common/Defines.h" -#include "common/network/rest/http/HTTPPayload.h" -#include "common/Log.h" - -#include -#include -#include -#include -#include - -namespace network -{ - namespace rest - { - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Structure representing a REST API request match. - * @ingroup rest - */ - struct RequestMatch : std::smatch { - /** - * @brief Initializes a new instance of the RequestMatch structure. - * @param m String matcher. - * @param c Content. - */ - RequestMatch(const std::smatch& m, const std::string& c) : std::smatch(m), content(c) { /* stub */ } - - std::string content; - }; - - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Structure representing a request matcher. - * @ingroup rest - */ - template - struct RequestMatcher { - typedef std::function RequestHandlerType; - - /** - * @brief Initializes a new instance of the RequestMatcher structure. - * @param expression Matching expression. - */ - explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ } - - /** - * @brief Handler for GET requests. - * @param handler GET request handler. - * @return RequestMatcher* Instance of a RequestMatcher. - */ - RequestMatcher& get(RequestHandlerType handler) { - m_handlers[HTTP_GET] = handler; - return *this; - } - /** - * @brief Handler for POST requests. - * @param handler POST request handler. - * @return RequestMatcher* Instance of a RequestMatcher. - */ - RequestMatcher& post(RequestHandlerType handler) { - m_handlers[HTTP_POST] = handler; - return *this; - } - /** - * @brief Handler for PUT requests. - * @param handler PUT request handler. - * @return RequestMatcher* Instance of a RequestMatcher. - */ - RequestMatcher& put(RequestHandlerType handler) { - m_handlers[HTTP_PUT] = handler; - return *this; - } - /** - * @brief Handler for DELETE requests. - * @param handler DELETE request handler. - * @return RequestMatcher* Instance of a RequestMatcher. - */ - RequestMatcher& del(RequestHandlerType handler) { - m_handlers[HTTP_DELETE] = handler; - return *this; - } - /** - * @brief Handler for OPTIONS requests. - * @param handler OPTIONS request handler. - * @return RequestMatcher* Instance of a RequestMatcher. - */ - RequestMatcher& options(RequestHandlerType handler) { - m_handlers[HTTP_OPTIONS] = handler; - return *this; - } - - /** - * @brief Helper to determine if the request matcher is a regular expression. - * @returns bool True, if request matcher is a regular expression, otherwise false. - */ - bool regex() const { return m_isRegEx; } - /** - * @brief Helper to set the regular expression flag. - * @param regEx Flag indicating whether or not the request matcher is a regular expression. - */ - void setRegEx(bool regEx) { m_isRegEx = regEx; } - - /** - * @brief Helper to handle the actual request. - * @param request HTTP request. - * @param reply HTTP reply. - * @param what What matched. - */ - void handleRequest(const Request& request, Reply& reply, const std::smatch &what) { - // dispatching to matching based on handler - RequestMatch match(what, request.content); - auto& handler = m_handlers[request.method]; - if (handler) { - handler(request, reply, match); - } - } - - private: - std::string m_expression; - bool m_isRegEx; - std::map m_handlers; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements RESTful web request dispatching. - * @tparam Request HTTP request. - * @tparam Reply HTTP reply. - */ - template - class RequestDispatcher { - typedef RequestMatcher MatcherType; - public: - /** - * @brief Initializes a new instance of the RequestDispatcher class. - */ - RequestDispatcher() : m_basePath(), m_debug(false) { /* stub */ } - /** - * @brief Initializes a new instance of the RequestDispatcher class. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - RequestDispatcher(bool debug) : m_basePath(), m_debug(debug) { /* stub */ } - /** - * @brief Initializes a new instance of the RequestDispatcher class. - * @param basePath - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - RequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ } - - /** - * @brief Helper to match a request patch. - * @param expression Matching expression. - * @param regex Flag indicating whether or not this match is a regular expression. - * @returns MatcherType Instance of a request matcher. - */ - MatcherType& match(const std::string& expression, bool regex = false) - { - MatcherTypePtr& p = m_matchers[expression]; - if (!p) { - if (m_debug) { - ::LogDebug(LOG_REST, "creating RequestDispatcher, expression = %s", expression.c_str()); - } - p = std::make_shared(expression); - } else { - if (m_debug) { - ::LogDebug(LOG_REST, "fetching RequestDispatcher, expression = %s", expression.c_str()); - } - } - - p->setRegEx(regex); - return *p; - } - - /** - * @brief Helper to handle HTTP request. - * @param request HTTP request. - * @param reply HTTP reply. - */ - void handleRequest(const Request& request, Reply& reply) - { - for (const auto& matcher : m_matchers) { - std::smatch what; - if (!matcher.second->regex()) { - if (request.uri.find(matcher.first) != std::string::npos) { - if (m_debug) { - ::LogDebug(LOG_REST, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); - } - - //what = matcher.first; - - // ensure CORS headers are added - reply.headers.add("Access-Control-Allow-Origin", "*"); - reply.headers.add("Access-Control-Allow-Methods", "*"); - reply.headers.add("Access-Control-Allow-Headers", "*"); - - if (request.method == HTTP_OPTIONS) { - reply.status = http::HTTPPayload::OK; - } - - matcher.second->handleRequest(request, reply, what); - return; - } - } else { - if (std::regex_match(request.uri, what, std::regex(matcher.first))) { - if (m_debug) { - ::LogDebug(LOG_REST, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); - } - - matcher.second->handleRequest(request, reply, what); - return; - } - } - } - - ::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str()); - reply = http::HTTPPayload::statusPayload(http::HTTPPayload::BAD_REQUEST, "application/json"); - } - - private: - typedef std::shared_ptr MatcherTypePtr; - - std::string m_basePath; - std::map m_matchers; - - bool m_debug; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements a generic basic request dispatcher. - * @tparam Request HTTP request. - * @tparam Reply HTTP reply. - */ - template - class BasicRequestDispatcher { - public: - typedef std::function RequestHandlerType; - - /** - * @brief Initializes a new instance of the BasicRequestDispatcher class. - */ - BasicRequestDispatcher() { /* stub */ } - /** - * @brief Initializes a new instance of the BasicRequestDispatcher class. - * @param handler Instance of a RequestHandlerType for this dispatcher. - */ - BasicRequestDispatcher(RequestHandlerType handler) : m_handler(handler) { /* stub */ } - - /** - * @brief Helper to handle HTTP request. - * @param request HTTP request. - * @param reply HTTP reply. - */ - void handleRequest(const Request& request, Reply& reply) - { - if (m_handler) { - m_handler(request, reply); - } - } - - private: - RequestHandlerType m_handler; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements a generic debug request dispatcher. - * @tparam Request HTTP request. - * @tparam Reply HTTP reply. - */ - template - class DebugRequestDispatcher { - public: - /** - * @brief Initializes a new instance of the DebugRequestDispatcher class. - */ - DebugRequestDispatcher() { /* stub */ } - - /** - * @brief Helper to handle HTTP request. - * @param request HTTP request. - * @param reply HTTP reply. - */ - void handleRequest(const Request& request, Reply& reply) - { - for (auto header : request.headers.headers()) - ::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); - - ::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "content = %s", request.content.c_str()); - } - }; - - typedef RequestDispatcher DefaultRequestDispatcher; - } // namespace rest -} // namespace network - -#endif // __REST__DISPATCHER_H__ diff --git a/src/common/network/rest/http/ClientConnection.h b/src/common/network/rest/http/ClientConnection.h deleted file mode 100644 index 6b2e86dd9..000000000 --- a/src/common/network/rest/http/ClientConnection.h +++ /dev/null @@ -1,242 +0,0 @@ -// 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. - * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file ClientConnection.h - * @ingroup http - */ -#if !defined(__REST_HTTP__CLIENT_CONNECTION_H__) -#define __REST_HTTP__CLIENT_CONNECTION_H__ - -#include "common/Defines.h" -#include "common/network/rest/http/HTTPLexer.h" -#include "common/network/rest/http/HTTPPayload.h" -#include "common/Log.h" - -#include -#include -#include -#include - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class represents a single connection from a client. - * @tparam RequestHandlerType Type representing a request handler. - * @ingroup http - */ - template - class ClientConnection { - public: - auto operator=(ClientConnection&) -> ClientConnection& = delete; - auto operator=(ClientConnection&&) -> ClientConnection& = delete; - ClientConnection(ClientConnection&) = delete; - - /** - * @brief Initializes a new instance of the ClientConnection class. - * @param socket TCP socket for this connection. - * @param handler Request handler for this connection. - */ - explicit ClientConnection(asio::ip::tcp::socket socket, RequestHandlerType& handler) : - m_socket(std::move(socket)), - m_requestHandler(handler), - m_lexer(HTTPLexer(true)) - { - /* stub */ - } - - /** - * @brief Start the first asynchronous operation for the connection. - */ - void start() { read(); } - /** - * @brief Stop all asynchronous operations associated with the connection. - */ - void stop() - { - try - { - ensureNoLinger(); - if (m_socket.is_open()) { - m_socket.close(); - } - } - catch(const std::exception&) { /* ignore */ } - } - - /** - * @brief Helper to enable the SO_LINGER socket option during shutdown. - */ - void ensureNoLinger() - { - try - { - // enable SO_LINGER timeout 0 - asio::socket_base::linger linger(true, 0); - m_socket.set_option(linger); - } - catch(const asio::system_error& e) - { - asio::error_code ec = e.code(); - if (ec) { - ::LogError(LOG_REST, "ClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value()); - } - } - } - - /** - * @brief Perform an synchronous write operation. - * @param request HTTP request payload. - */ - void send(HTTPPayload request) - { - m_sizeToTransfer = m_bytesTransferred = 0U; - request.attachHostHeader(m_socket.remote_endpoint()); - write(request); - } - private: - /** - * @brief Perform an asynchronous read operation. - */ - void read() - { - m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { - if (!ec) { - HTTPLexer::ResultType result; - char* content; - - try - { - if (m_sizeToTransfer > 0U && (m_bytesTransferred + bytes_transferred) < m_sizeToTransfer) { - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - read(); - } - else { - if (m_sizeToTransfer > 0U) { - // final copy - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - m_sizeToTransfer = 0U; - bytes_transferred = m_bytesTransferred; - - // reset lexer and re-parse the full content - m_lexer.reset(); - std::tie(result, content) = m_lexer.parse(m_request, m_fullBuffer.data(), m_fullBuffer.data() + bytes_transferred); - } else { - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); - } - - // determine content length - std::string contentLength = m_request.headers.find("Content-Length"); - if (contentLength != "") { - size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); - - // setup a full read if necessary - if (length > bytes_transferred && m_sizeToTransfer == 0U) { - m_sizeToTransfer = length; - } - - if (m_sizeToTransfer > 0U) { - result = HTTPLexer::CONTINUE; - } else { - m_request.content = std::string(content, length); - } - } - - m_request.headers.add("RemoteHost", m_socket.remote_endpoint().address().to_string()); - - if (result == HTTPLexer::GOOD) { - m_sizeToTransfer = m_bytesTransferred = 0U; - m_requestHandler.handleRequest(m_request, m_reply); - } - else if (result == HTTPLexer::BAD) { - m_sizeToTransfer = m_bytesTransferred = 0U; - return; - } - else { - read(); - } - } - } - catch(const std::exception& e) { ::LogError(LOG_REST, "ClientConnection::read(), %s", ec.message().c_str()); } - } - else if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "ClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); - } - stop(); - } - }); - } - - /** - * @brief Perform an synchronous write operation. - * @param request HTTP request payload. - */ - void write(HTTPPayload request) - { - try - { - auto buffers = request.toBuffers(); - asio::write(m_socket, buffers); - } - catch(const asio::system_error& e) - { - asio::error_code ec = e.code(); - if (ec) { - ::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - - try - { - // initiate graceful connection closure - asio::error_code ignored_ec; - m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); - } - catch(const std::exception& e) { - ::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - } - } - } - } - - asio::ip::tcp::socket m_socket; - - RequestHandlerType& m_requestHandler; - - std::size_t m_sizeToTransfer; - std::size_t m_bytesTransferred; - std::array m_fullBuffer; - - std::array m_buffer; - - HTTPPayload m_request; - HTTPLexer m_lexer; - HTTPPayload m_reply; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__CLIENT_CONNECTION_H__ diff --git a/src/common/network/rest/http/HTTPClient.h b/src/common/network/rest/http/HTTPClient.h deleted file mode 100644 index 94c2a4f5b..000000000 --- a/src/common/network/rest/http/HTTPClient.h +++ /dev/null @@ -1,199 +0,0 @@ -// 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. - * - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPClient.h - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_CLIENT_H__) -#define __REST_HTTP__HTTP_CLIENT_H__ - -#include "common/Defines.h" -#include "common/network/rest/http/ClientConnection.h" -#include "common/network/rest/http/HTTPRequestHandler.h" -#include "common/Thread.h" - -#include -#include -#include -#include -#include -#include - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements top-level routines of the HTTP client. - * @tparam RequestHandlerType Type representing a request handler. - * @tparam ConnectionImpl Type representing the connection implementation. - * @ingroup http - */ - template class ConnectionImpl = ClientConnection> - class HTTPClient : private Thread { - public: - auto operator=(HTTPClient&) -> HTTPClient& = delete; - auto operator=(HTTPClient&&) -> HTTPClient& = delete; - HTTPClient(HTTPClient&) = delete; - - /** - * @brief Initializes a new instance of the HTTPClient class. - * @param address Hostname/IP Address. - * @param port Port. - */ - HTTPClient(const std::string& address, uint16_t port) : - m_address(address), - m_port(port), - m_connection(nullptr), - m_ioContext(), - m_socket(m_ioContext), - m_requestHandler() - { - /* stub */ - } - /** - * @brief Finalizes a instance of the HTTPClient class. - */ - ~HTTPClient() override - { - if (m_connection != nullptr) { - close(); - } - } - - /** - * @brief Helper to set the HTTP request handlers. - * @tparam Handler Type representing the request handler. - * @param handler Request handler. - */ - template - void setHandler(Handler&& handler) - { - m_requestHandler = RequestHandlerType(std::forward(handler)); - } - - /** - * @brief Send HTTP request to HTTP server. - * @param request HTTP request. - * @returns True, if request was completed, otherwise false. - */ - bool request(HTTPPayload& request) - { - if (m_completed) { - return false; - } - - asio::post(m_ioContext, [this, request]() { - std::lock_guard guard(m_lock); - { - if (m_connection != nullptr) { - m_connection->send(request); - } - } - }); - - return true; - } - - /** - * @brief Opens connection to the network. - */ - bool open() - { - if (m_completed) { - return false; - } - - return run(); - } - - /** - * @brief Closes connection to the network. - */ - void close() - { - if (m_completed) { - return; - } - - m_completed = true; - m_ioContext.stop(); - - wait(); - } - - private: - /** - * @brief Internal entry point for the ASIO IO context thread. - */ - void entry() override - { - if (m_completed) { - return; - } - - asio::ip::tcp::resolver resolver(m_ioContext); - auto endpoints = resolver.resolve(m_address, std::to_string(m_port)); - - try { - connect(endpoints); - - // the entry() call will block until all asynchronous operations - // have finished - m_ioContext.run(); - } - catch (std::exception&) { /* stub */ } - - if (m_connection != nullptr) { - m_connection->stop(); - } - } - - /** - * @brief Perform an asynchronous connect operation. - * @param endpoints TCP endpoint to connect to. - */ - void connect(asio::ip::basic_resolver_results& endpoints) - { - asio::connect(m_socket, endpoints); - - m_connection = std::make_unique(std::move(m_socket), m_requestHandler); - m_connection->start(); - } - - std::string m_address; - uint16_t m_port; - - typedef ConnectionImpl ConnectionType; - - std::unique_ptr m_connection; - - bool m_completed = false; - asio::io_context m_ioContext; - - asio::ip::tcp::socket m_socket; - - RequestHandlerType m_requestHandler; - - std::mutex m_lock; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_CLIENT_H__ diff --git a/src/common/network/rest/http/HTTPHeaders.h b/src/common/network/rest/http/HTTPHeaders.h deleted file mode 100644 index 409e00be6..000000000 --- a/src/common/network/rest/http/HTTPHeaders.h +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPClient.h - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_HEADERS_H__) -#define __REST_HTTP__HTTP_HEADERS_H__ - -#include "common/Defines.h" -#include "common/Log.h" -#include "common/Utils.h" - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- - - struct HTTPPayload; - - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Represents HTTP headers. - * @ingroup http - */ - struct HTTPHeaders { - /** - * @brief Structure representing an individual HTTP header. - * @ingroup http - */ - struct Header - { - /** - * @brief Header name. - */ - std::string name; - /** - * @brief Header value. - */ - std::string value; - - /** - * @brief Initializes a new instance of the Header class. - */ - Header() : name{}, value{} { /* stub */} - /** - * @brief Initializes a new instance of the Header class - * @param n Header name. - * @param v Header value. - */ - Header(std::string n, std::string v) : name{n}, value{v} { /* stub */ } - }; - - /** - * @brief Gets the list of HTTP headers. - * @returns std::vector
List of HTTP headers. - */ - std::vector
headers() const { return m_headers; } - /** - * @brief Returns true if the headers are empty. - * @returns bool True, if no HTTP headers are present, otherwise false. - */ - bool empty() const { return m_headers.empty(); } - /** - * @brief Returns the number of headers. - * @returns std::size_t Number of headers. - */ - std::size_t size() const { return m_headers.size(); } - /** - * @brief Clears the list of HTTP headers. - */ - void clearHeaders() { m_headers = std::vector
(); } - /** - * @brief Helper to add a HTTP header. - * @param name Header name. - * @param value Header value. - */ - void add(const std::string& name, const std::string& value) - { - //::LogDebugEx(LOG_REST, "HTTPHeaders::add()", "header = %s, value = %s", name.c_str(), value.c_str()); - for (auto& header : m_headers) { - if (::strtolower(header.name) == ::strtolower(name)) { - header.value = value; - return; - } - } - - m_headers.push_back(Header(name, value)); - //for (auto header : m_headers) - // ::LogDebugEx(LOG_REST, "HTTPHeaders::add()", "m_headers.header = %s, m_headers.value = %s", header.name.c_str(), header.value.c_str()); - } - /** - * @brief Helper to remove a HTTP header. - * @param headerName Header name. - */ - void remove(const std::string headerName) - { - auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) { - return ::strtolower(h.name) == ::strtolower(headerName); - }); - - if (header != m_headers.end()) { - m_headers.erase(header); - } - } - /** - * @brief Helper to find the named HTTP header. - * @param headerName Header name. - * @returns std::string Value of named header (if any). - */ - std::string find(const std::string headerName) const - { - auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) { - return ::strtolower(h.name) == ::strtolower(headerName); - }); - - if (header != m_headers.end()) { - return header->value; - } - else { - return ""; - } - } - - private: - friend struct HTTPPayload; - std::vector
m_headers; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_HEADERS_H__ diff --git a/src/common/network/rest/http/HTTPLexer.h b/src/common/network/rest/http/HTTPLexer.h deleted file mode 100644 index 736af7fc9..000000000 --- a/src/common/network/rest/http/HTTPLexer.h +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPLexer.h - * @ingroup http - * @file HTTPLexer.cpp - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_LEXER_H__) -#define __REST_HTTP__HTTP_LEXER_H__ - -#include "common/Defines.h" - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- - - struct HTTPPayload; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements the lexer for incoming payloads. - */ - class HTTPLexer { - public: - /** - * @brief Lexing result. - */ - enum ResultType { GOOD, BAD, INDETERMINATE, CONTINUE }; - - /** - * @brief Initializes a new instance of the HTTPLexer class. - * @param clientLexer Flag indicating this lexer is used for a HTTP client. - */ - HTTPLexer(bool clientLexer); - - /** - * @brief Reset to initial parser state. - */ - void reset(); - - /** - * @brief Parse some data. The enum return value is good when a complete request has - * been parsed, bad if the data is invalid, indeterminate when more data is - * required. The InputIterator return value indicates how much of the input - * has been consumed. - * @tparam InputIterator - * @param payload HTTP request payload. - * @param begin - * @param end - * @returns std::tuple - */ - template - std::tuple parse(HTTPPayload& payload, InputIterator begin, InputIterator end) - { - while (begin != end) { - ResultType result = consume(payload, *begin++); - if (result == GOOD || result == BAD) - return std::make_tuple(result, begin); - } - return std::make_tuple(INDETERMINATE, begin); - } - - /** - * @brief Returns flag indicating whether or not characters have been consumed from the payload. - * @returns True, if characters were consumed, otherwise false. - */ - uint32_t consumed() const { return m_consumed; } - - private: - /** - * @brief Handle the next character of input. - * @param payload HTTP request payload. - * @param input Character. - */ - ResultType consume(HTTPPayload& payload, char input); - - /** - * @brief Check if a byte is an HTTP character. - * @param c Character. - * @returns bool True, if character is an HTTP character, otherwise false. - */ - static bool isChar(int c); - /** - * @brief Check if a byte is an HTTP control character. - * @param c Character. - * @returns bool True, if character is an HTTP control character, otherwise false. - */ - static bool isControl(int c); - /** - * @brief Check if a byte is an HTTP special character. - * @param c Character. - * @returns bool True, if character is an HTTP special character, otherwise false. - */ - static bool isSpecial(int c); - /** - * @brief Check if a byte is an digit. - * @param c Character. - * @returns bool True, if character is a digit, otherwise false. - */ - static bool isDigit(int c); - - /** - * @brief Structure representing lexed HTTP headers. - */ - struct LexedHeader - { - /** - * @brief Header name. - */ - std::string name; - /** - * @brief Header value. - */ - std::string value; - - /** - * @brief Initializes a new instance of the LexedHeader class - */ - LexedHeader() { /* stub */ } - /** - * @brief Initializes a new instance of the LexedHeader class - * @param n Header name. - * @param v Header value. - */ - LexedHeader(const std::string& n, const std::string& v) : name(n), value(v) {} - }; - - std::vector m_headers; - uint16_t m_status; - bool m_clientLexer = false; - uint32_t m_consumed; - - /** - * @brief Lexer machine state. - */ - enum state - { - METHOD_START, //! HTTP Method Start - METHOD, //! HTTP Method - URI, //! HTTP URI - - HTTP_VERSION_H, //! HTTP Version: H - HTTP_VERSION_T_1, //! HTTP Version: T - HTTP_VERSION_T_2, //! HTTP Version: T - HTTP_VERSION_P, //! HTTP Version: P - HTTP_VERSION_SLASH, //! HTTP Version: / - HTTP_VERSION_MAJOR_START, //! HTTP Version Major Start - HTTP_VERSION_MAJOR, //! HTTP Version Major - HTTP_VERSION_MINOR_START, //! HTTP Version Minor Start - HTTP_VERSION_MINOR, //! HTTP Version Minor - - HTTP_STATUS_1, //! Status Number 1 - HTTP_STATUS_2, //! Status Number 2 - HTTP_STATUS_3, //! Status Number 3 - HTTP_STATUS_END, //! Status End - HTTP_STATUS_MESSAGE_START, //! Status Message Start - HTTP_STATUS_MESSAGE, //! Status Message End - - EXPECTING_NEWLINE_1, //! - - HEADER_LINE_START, //! Header Line Start - HEADER_LWS, //! - HEADER_NAME, //! Header Name - SPACE_BEFORE_HEADER_VALUE, //! - HEADER_VALUE, //! Header Value - - EXPECTING_NEWLINE_2, //! - EXPECTING_NEWLINE_3 //! - } m_state; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_LEXER_H__ diff --git a/src/common/network/rest/http/HTTPPayload.h b/src/common/network/rest/http/HTTPPayload.h deleted file mode 100644 index 91e686ce0..000000000 --- a/src/common/network/rest/http/HTTPPayload.h +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPPayload.h - * @ingroup http - * @file HTTPPayload.cpp - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_PAYLOAD_H__) -#define __REST_HTTP__HTTP_PAYLOAD_H__ - -#include "common/Defines.h" -#include "common/network/json/json.h" -#include "common/network/rest/http/HTTPHeaders.h" - -#include -#include - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Constants - // --------------------------------------------------------------------------- - - #define HTTP_GET "GET" - #define HTTP_POST "POST" - #define HTTP_PUT "PUT" - #define HTTP_DELETE "DELETE" - #define HTTP_OPTIONS "OPTIONS" - - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This struct implements a model of a payload to be sent to a - * HTTP client/server. - * @ingroup http - */ - struct HTTPPayload { - /** - * @brief HTTP Status/Response Codes - */ - enum StatusType { - OK = 200, //! HTTP OK 200 - CREATED = 201, //! HTTP Created 201 - ACCEPTED = 202, //! HTTP Accepted 202 - NO_CONTENT = 204, //! HTTP No Content 204 - - MULTIPLE_CHOICES = 300, //! HTTP Multiple Choices 300 - MOVED_PERMANENTLY = 301, //! HTTP Moved Permenantly 301 - MOVED_TEMPORARILY = 302, //! HTTP Moved Temporarily 302 - NOT_MODIFIED = 304, //! HTTP Not Modified 304 - - BAD_REQUEST = 400, //! HTTP Bad Request 400 - UNAUTHORIZED = 401, //! HTTP Unauthorized 401 - FORBIDDEN = 403, //! HTTP Forbidden 403 - NOT_FOUND = 404, //! HTTP Not Found 404 - - INTERNAL_SERVER_ERROR = 500, //! HTTP Internal Server Error 500 - NOT_IMPLEMENTED = 501, //! HTTP Not Implemented 501 - BAD_GATEWAY = 502, //! HTTP Bad Gateway 502 - SERVICE_UNAVAILABLE = 503 //! HTTP Service Unavailable 503 - } status; - - HTTPHeaders headers; - std::string content; - size_t contentLength; - - std::string method; - std::string uri; - - int httpVersionMajor; - int httpVersionMinor; - - bool isClientPayload = false; - - /** - * @brief Convert the payload into a vector of buffers. The buffers do not own the - * underlying memory blocks, therefore the payload object must remain valid and - * not be changed until the write operation has completed. - * @returns std::vector List of buffers representing the HTTP payload. - */ - std::vector toBuffers(); - - /** - * @brief Prepares payload for transmission by finalizing status and content type. - * @param obj - * @param status HTTP status. - */ - void payload(json::object& obj, StatusType status = OK); - /** - * @brief Prepares payload for transmission by finalizing status and content type. - * @param content - * @param status HTTP status. - * @param contentType HTTP content type. - */ - void payload(std::string& content, StatusType status = OK, const std::string& contentType = "text/html"); - - /** - * @brief Get a request payload. - * @param method HTTP method. - * @param uri HTTP uri. - */ - static HTTPPayload requestPayload(std::string method, std::string uri); - /** - * @brief Get a status payload. - * @param status HTTP status. - * @param contentType HTTP content type. - */ - static HTTPPayload statusPayload(StatusType status, const std::string& contentType = "text/html"); - - /** - * @brief Helper to attach a host TCP stream reader. - * @param remoteEndpoint Endpoint. - */ - void attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint); - - private: - /** - * @brief Internal helper to ensure the headers are of a default for the given content type. - * @param contentType HTTP content type. - */ - void ensureDefaultHeaders(const std::string& contentType = "text/html"); - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_PAYLOAD_H__ diff --git a/src/common/network/rest/http/HTTPRequestHandler.h b/src/common/network/rest/http/HTTPRequestHandler.h deleted file mode 100644 index 41bfbfe78..000000000 --- a/src/common/network/rest/http/HTTPRequestHandler.h +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPRequestHandler.h - * @ingroup http - * @file HTTPRequestHandler.cpp - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_REQUEST_HANDLER_H__) -#define __REST_HTTP__HTTP_REQUEST_HANDLER_H__ - -#include "common/Defines.h" - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- - - struct HTTPPayload; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements the common handler for all incoming requests. - * @ingroup http - */ - class HTTPRequestHandler { - public: - auto operator=(HTTPRequestHandler&) -> HTTPRequestHandler& = delete; - HTTPRequestHandler(HTTPRequestHandler&) = delete; - - /** - * @brief Initializes a new instance of the HTTPRequestHandler class. - * @param docRoot Path to the document root to serve. - */ - explicit HTTPRequestHandler(const std::string& docRoot); - /** - * @brief - */ - HTTPRequestHandler(HTTPRequestHandler&&) = default; - - /** - * @brief - */ - HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; - - /** - * @brief Handle a request and produce a reply. - * @param req HTTP request. - * @param reply HTTP reply. - */ - void handleRequest(const HTTPPayload& req, HTTPPayload& reply); - - private: - /** - * @brief Internal helper to decode an incoming URL. - * @param[in] in Incoming URL string. - * @param[out] out Decoded URL string. - * @returns bool True, if URL decoded, otherwise false. - */ - static bool urlDecode(const std::string& in, std::string& out); - - std::string m_docRoot; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__ diff --git a/src/common/network/rest/http/HTTPServer.h b/src/common/network/rest/http/HTTPServer.h deleted file mode 100644 index 25f25130b..000000000 --- a/src/common/network/rest/http/HTTPServer.h +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPServer.h - * @ingroup http - */ -#if !defined(__REST_HTTP__HTTP_SERVER_H__) -#define __REST_HTTP__HTTP_SERVER_H__ - -#include "common/Defines.h" -#include "common/network/rest/http/ServerConnection.h" -#include "common/network/rest/http/ServerConnectionManager.h" -#include "common/network/rest/http/HTTPRequestHandler.h" - -#include -#include -#include -#include -#include - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements top-level routines of the HTTP server. - * @tparam RequestHandlerType Type representing a request handler. - * @tparam ConnectionImpl Type representing the connection implementation. - * @ingroup http - */ - template class ConnectionImpl = ServerConnection> - class HTTPServer { - public: - auto operator=(HTTPServer&) -> HTTPServer& = delete; - auto operator=(HTTPServer&&) -> HTTPServer& = delete; - HTTPServer(HTTPServer&) = delete; - - /** - * @brief Initializes a new instance of the HTTPServer class. - * @param address Hostname/IP Address. - * @param port Port. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - explicit HTTPServer(const std::string& address, uint16_t port, bool debug) : - m_ioService(), - m_acceptor(m_ioService), - m_connectionManager(), - m_socket(m_ioService), - m_requestHandler(), - m_debug(debug) - { - // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) - asio::ip::address ipAddress = asio::ip::address::from_string(address); - m_endpoint = asio::ip::tcp::endpoint(ipAddress, port); - } - - /** - * @brief Helper to set the HTTP request handlers. - * @tparam Handler Type representing the request handler. - * @param handler Request handler. - */ - template - void setHandler(Handler&& handler) - { - m_requestHandler = RequestHandlerType(std::forward(handler)); - } - - /** - * @brief Open TCP acceptor. - */ - void open() - { - // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) - m_acceptor.open(m_endpoint.protocol()); - m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); - m_acceptor.set_option(asio::socket_base::keep_alive(true)); - m_acceptor.bind(m_endpoint); - m_acceptor.listen(); - - accept(); - } - - /** - * @brief Run the servers ASIO IO service loop. - */ - void run() - { - // the run() call will block until all asynchronous operations - // have finished; while the server is running, there is always at least one - // asynchronous operation outstanding: the asynchronous accept call waiting - // for new incoming connections - m_ioService.run(); - } - - /** - * @brief Helper to stop running ASIO IO services. - */ - void stop() - { - // the server is stopped by cancelling all outstanding asynchronous - // operations; once all operations have finished the m_ioService::run() - // call will exit - m_acceptor.close(); - m_connectionManager.stopAll(); - } - - private: - /** - * @brief Perform an asynchronous accept operation. - */ - void accept() - { - m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { - // check whether the server was stopped by a signal before this - // completion handler had a chance to run - if (!m_acceptor.is_open()) { - return; - } - - if (!ec) { - m_connectionManager.start(std::make_shared(std::move(m_socket), m_connectionManager, m_requestHandler, false, m_debug)); - } - - accept(); - }); - } - - typedef ConnectionImpl ConnectionType; - typedef std::shared_ptr ConnectionTypePtr; - - asio::io_service m_ioService; - asio::ip::tcp::acceptor m_acceptor; - - asio::ip::tcp::endpoint m_endpoint; - - ServerConnectionManager m_connectionManager; - - asio::ip::tcp::socket m_socket; - - RequestHandlerType m_requestHandler; - bool m_debug; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__HTTP_SERVER_H__ diff --git a/src/common/network/rest/http/SecureClientConnection.h b/src/common/network/rest/http/SecureClientConnection.h deleted file mode 100644 index 3ce83a850..000000000 --- a/src/common/network/rest/http/SecureClientConnection.h +++ /dev/null @@ -1,267 +0,0 @@ -// 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 / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file SecureClientConnection.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SECURE_CLIENT_CONNECTION_H__) -#define __REST_HTTP__SECURE_CLIENT_CONNECTION_H__ - -#if defined(ENABLE_SSL) - -#include "common/Defines.h" -#include "common/network/rest/http/HTTPLexer.h" -#include "common/network/rest/http/HTTPPayload.h" -#include "common/Log.h" - -#include -#include -#include -#include - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class represents a single connection from a client. - * @tparam RequestHandlerType Type representing a request handler. - * @ingroup http - */ - template - class SecureClientConnection { - public: - auto operator=(SecureClientConnection&) -> SecureClientConnection& = delete; - auto operator=(SecureClientConnection&&) -> SecureClientConnection& = delete; - SecureClientConnection(SecureClientConnection&) = delete; - - /** - * @brief Initializes a new instance of the SecureClientConnection class. - * @param socket TCP socket for this connection. - * @param context SSL context for this connection. - * @param handler Request handler for this connection. - */ - explicit SecureClientConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, RequestHandlerType& handler) : - m_socket(std::move(socket), context), - m_requestHandler(handler), - m_lexer(HTTPLexer(true)) - { - m_socket.set_verify_mode(asio::ssl::verify_none); - m_socket.set_verify_callback(std::bind(&SecureClientConnection::verify_certificate, this, std::placeholders::_1, std::placeholders::_2)); - } - - /** - * @brief Start the first asynchronous operation for the connection. - */ - void start() - { - m_socket.handshake(asio::ssl::stream_base::client); - read(); - } - /** - * @brief Stop all asynchronous operations associated with the connection. - */ - void stop() - { - try - { - ensureNoLinger(); - if (m_socket.lowest_layer().is_open()) { - m_socket.lowest_layer().close(); - } - } - catch(const std::exception&) { /* ignore */ } - } - - /** - * @brief Helper to enable the SO_LINGER socket option during shutdown. - */ - void ensureNoLinger() - { - try - { - // enable SO_LINGER timeout 0 - asio::socket_base::linger linger(true, 0); - m_socket.lowest_layer().set_option(linger); - } - catch(const asio::system_error& e) - { - asio::error_code ec = e.code(); - if (ec) { - ::LogError(LOG_REST, "SecureClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value()); - } - } - } - - /** - * @brief Perform an synchronous write operation. - * @param request HTTP request. - */ - void send(HTTPPayload request) - { - request.attachHostHeader(m_socket.lowest_layer().remote_endpoint()); - write(request); - } - private: - /** - * @brief Perform an SSL certificate verification. - * @param preverified Flag indicating the SSL certificate was preverified. - * @param context SSL verification context. - * @returns True, if SSL certificate is valid, otherwise false. - */ - bool verify_certificate(bool preverified, asio::ssl::verify_context& context) - { - return true; // ignore always valid - } - - /** - * @brief Perform an asynchronous read operation. - */ - void read() - { - m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { - if (!ec) { - HTTPLexer::ResultType result; - char* content; - - try - { - if (m_sizeToTransfer > 0U && (m_bytesTransferred + bytes_transferred) < m_sizeToTransfer) { - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - read(); - } - else { - if (m_sizeToTransfer > 0U) { - // final copy - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - m_sizeToTransfer = 0U; - bytes_transferred = m_bytesTransferred; - - // reset lexer and re-parse the full content - m_lexer.reset(); - std::tie(result, content) = m_lexer.parse(m_request, m_fullBuffer.data(), m_fullBuffer.data() + bytes_transferred); - } else { - ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); - m_bytesTransferred += bytes_transferred; - - std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); - } - - // determine content length - std::string contentLength = m_request.headers.find("Content-Length"); - if (contentLength != "") { - size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); - - // setup a full read if necessary - if (length > bytes_transferred && m_sizeToTransfer == 0U) { - m_sizeToTransfer = length; - } - - if (m_sizeToTransfer > 0U) { - result = HTTPLexer::CONTINUE; - } else { - m_request.content = std::string(content, length); - } - } - - m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string()); - if (result == HTTPLexer::GOOD) { - m_sizeToTransfer = m_bytesTransferred = 0U; - m_requestHandler.handleRequest(m_request, m_reply); - } - else if (result == HTTPLexer::BAD) { - m_sizeToTransfer = m_bytesTransferred = 0U; - return; - } - else { - read(); - } - } - } - catch(const std::exception& e) { ::LogError(LOG_REST, "SecureClientConnection::read(), %s", ec.message().c_str()); } - } - else if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "SecureClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); - } - stop(); - } - }); - } - - /** - * @brief Perform an synchronous write operation. - * @param request HTTP request. - */ - void write(HTTPPayload request) - { - try - { - m_socket.handshake(asio::ssl::stream_base::client); - - auto buffers = request.toBuffers(); - asio::write(m_socket, buffers); - } - catch(const asio::system_error& e) - { - asio::error_code ec = e.code(); - if (ec) { - ::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - - try - { - // initiate graceful connection closure - asio::error_code ignored_ec; - m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); - } - catch(const std::exception& e) { - ::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - } - } - } - } - - asio::ssl::stream m_socket; - - RequestHandlerType& m_requestHandler; - - std::size_t m_sizeToTransfer; - std::size_t m_bytesTransferred; - std::array m_fullBuffer; - - std::array m_buffer; - - HTTPPayload m_request; - HTTPLexer m_lexer; - HTTPPayload m_reply; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // ENABLE_SSL - -#endif // __REST_HTTP__SECURE_CLIENT_CONNECTION_H__ diff --git a/src/common/network/rest/http/SecureHTTPClient.h b/src/common/network/rest/http/SecureHTTPClient.h deleted file mode 100644 index 009e78f9b..000000000 --- a/src/common/network/rest/http/SecureHTTPClient.h +++ /dev/null @@ -1,209 +0,0 @@ -// 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 / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* -* Copyright (C) 2024 Bryan Biedenkapp, N2PLL -* -*/ -/** - * @file SecureHTTPClient.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SECURE_HTTP_CLIENT_H__) -#define __REST_HTTP__SECURE_HTTP_CLIENT_H__ - -#if defined(ENABLE_SSL) - -#include "common/Defines.h" -#include "common/network/rest/http/SecureClientConnection.h" -#include "common/network/rest/http/HTTPRequestHandler.h" -#include "common/Thread.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements top-level routines of the secure HTTP client. - * @tparam RequestHandlerType Type representing a request handler. - * @tparam ConnectionImpl Type representing the connection implementation. - * @ingroup http - */ - template class ConnectionImpl = SecureClientConnection> - class SecureHTTPClient : private Thread { - public: - auto operator=(SecureHTTPClient&) -> SecureHTTPClient& = delete; - auto operator=(SecureHTTPClient&&) -> SecureHTTPClient& = delete; - SecureHTTPClient(SecureHTTPClient&) = delete; - - /** - * @brief Initializes a new instance of the SecureHTTPClient class. - * @param address Hostname/IP Address. - * @param port Port. - */ - SecureHTTPClient(const std::string& address, uint16_t port) : - m_address(address), - m_port(port), - m_connection(nullptr), - m_ioContext(), - m_context(asio::ssl::context::tlsv12), - m_socket(m_ioContext), - m_requestHandler() - { - /* stub */ - } - /** - * @brief Finalizes a instance of the SecureHTTPClient class. - */ - ~SecureHTTPClient() override - { - if (m_connection != nullptr) { - close(); - } - } - - /** - * @brief Helper to set the HTTP request handlers. - * @tparam Handler Type representing the request handler. - * @param handler Request handler. - */ - template - void setHandler(Handler&& handler) - { - m_requestHandler = RequestHandlerType(std::forward(handler)); - } - - /** - * @brief Send HTTP request to HTTP server. - * @param request HTTP request. - * @returns True, if request was completed, otherwise false. - */ - bool request(HTTPPayload& request) - { - if (m_completed) { - return false; - } - - asio::post(m_ioContext, [this, request]() { - std::lock_guard guard(m_lock); - { - if (m_connection != nullptr) { - m_connection->send(request); - } - } - }); - - return true; - } - - /** - * @brief Opens connection to the network. - */ - bool open() - { - if (m_completed) { - return false; - } - - return run(); - } - - /** - * @brief Closes connection to the network. - */ - void close() - { - if (m_completed) { - return; - } - - m_completed = true; - m_ioContext.stop(); - - wait(); - } - - private: - /** - * @brief Internal entry point for the ASIO IO context thread. - */ - void entry() override - { - if (m_completed) { - return; - } - - asio::ip::tcp::resolver resolver(m_ioContext); - auto endpoints = resolver.resolve(m_address, std::to_string(m_port)); - - try { - connect(endpoints); - - // the entry() call will block until all asynchronous operations - // have finished - m_ioContext.run(); - } - catch (std::exception&) { /* stub */ } - - if (m_connection != nullptr) { - m_connection->stop(); - } - } - - /** - * @brief Perform an asynchronous connect operation. - * @param endpoints TCP endpoint to connect to. - */ - void connect(asio::ip::basic_resolver_results& endpoints) - { - asio::connect(m_socket, endpoints); - - m_connection = std::make_unique(std::move(m_socket), m_context, m_requestHandler); - m_connection->start(); - } - - std::string m_address; - uint16_t m_port; - - typedef ConnectionImpl ConnectionType; - - std::unique_ptr m_connection; - - bool m_completed = false; - asio::io_context m_ioContext; - - asio::ssl::context m_context; - asio::ip::tcp::socket m_socket; - - RequestHandlerType m_requestHandler; - - std::mutex m_lock; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // ENABLE_SSL - -#endif // __REST_HTTP__SECURE_HTTP_CLIENT_H__ diff --git a/src/common/network/rest/http/SecureHTTPServer.h b/src/common/network/rest/http/SecureHTTPServer.h deleted file mode 100644 index c19d5ab3b..000000000 --- a/src/common/network/rest/http/SecureHTTPServer.h +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file HTTPServer.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SECURE_HTTP_SERVER_H__) -#define __REST_HTTP__SECURE_HTTP_SERVER_H__ - -#if defined(ENABLE_SSL) - -#include "common/Defines.h" -#include "common/network/rest/http/SecureServerConnection.h" -#include "common/network/rest/http/ServerConnectionManager.h" -#include "common/network/rest/http/HTTPRequestHandler.h" - -#include -#include -#include -#include -#include - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements top-level routines of the secure HTTP server. - * @tparam RequestHandlerType Type representing a request handler. - * @tparam ConnectionImpl Type representing the connection implementation. - * @ingroup http - */ - template class ConnectionImpl = SecureServerConnection> - class SecureHTTPServer { - public: - auto operator=(SecureHTTPServer&) -> SecureHTTPServer& = delete; - auto operator=(SecureHTTPServer&&) -> SecureHTTPServer& = delete; - SecureHTTPServer(SecureHTTPServer&) = delete; - - /** - * @brief Initializes a new instance of the SecureHTTPServer class. - * @param address Hostname/IP Address. - * @param port Port. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - explicit SecureHTTPServer(const std::string& address, uint16_t port, bool debug) : - m_ioService(), - m_acceptor(m_ioService), - m_connectionManager(), - m_context(asio::ssl::context::tlsv12), - m_socket(m_ioService), - m_requestHandler(), - m_debug(debug) - { - asio::ip::address ipAddress = asio::ip::address::from_string(address); - m_endpoint = asio::ip::tcp::endpoint(ipAddress, port); - } - - /** - * @brief Helper to set the SSL certificate and private key. - * @param keyFile SSL certificate private key. - * @param certFile SSL certificate. - */ - bool setCertAndKey(const std::string& keyFile, const std::string& certFile) - { - try - { - m_context.use_certificate_chain_file(certFile); - m_context.use_private_key_file(keyFile, asio::ssl::context::pem); - return true; - } - catch(const std::exception& e) { - ::LogError(LOG_REST, "%s", e.what()); - return false; - } - } - - /** - * @brief Helper to set the HTTP request handlers. - * @tparam Handler Type representing the request handler. - * @param handler Request handler. - */ - template - void setHandler(Handler&& handler) - { - m_requestHandler = RequestHandlerType(std::forward(handler)); - } - - /** - * @brief Open TCP acceptor. - */ - void open() - { - // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) - m_acceptor.open(m_endpoint.protocol()); - m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); - m_acceptor.set_option(asio::socket_base::keep_alive(true)); - m_acceptor.bind(m_endpoint); - m_acceptor.listen(); - - accept(); - } - - /** - * @brief Run the servers ASIO IO service loop. - */ - void run() - { - // the run() call will block until all asynchronous operations - // have finished; while the server is running, there is always at least one - // asynchronous operation outstanding: the asynchronous accept call waiting - // for new incoming connections - m_ioService.run(); - } - - /** - * @brief Helper to stop running ASIO IO services. - */ - void stop() - { - // the server is stopped by cancelling all outstanding asynchronous - // operations; once all operations have finished the m_ioService::run() - // call will exit - m_acceptor.close(); - m_connectionManager.stopAll(); - } - - private: - /** - * @brief Perform an asynchronous accept operation. - */ - void accept() - { - m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { - // check whether the server was stopped by a signal before this - // completion handler had a chance to run - if (!m_acceptor.is_open()) { - return; - } - - if (!ec) { - m_connectionManager.start(std::make_shared(std::move(m_socket), m_context, m_connectionManager, m_requestHandler, false, m_debug)); - } - - accept(); - }); - } - - typedef ConnectionImpl ConnectionType; - typedef std::shared_ptr ConnectionTypePtr; - - asio::io_service m_ioService; - asio::ip::tcp::acceptor m_acceptor; - - asio::ip::tcp::endpoint m_endpoint; - - ServerConnectionManager m_connectionManager; - - asio::ssl::context m_context; - asio::ip::tcp::socket m_socket; - - std::string m_certFile; - std::string m_keyFile; - - RequestHandlerType m_requestHandler; - bool m_debug; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // ENABLE_SSL - -#endif // __REST_HTTP__SECURE_HTTP_SERVER_H__ diff --git a/src/common/network/rest/http/SecureServerConnection.h b/src/common/network/rest/http/SecureServerConnection.h deleted file mode 100644 index 38c22bc6e..000000000 --- a/src/common/network/rest/http/SecureServerConnection.h +++ /dev/null @@ -1,287 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file SecureServerConnection.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SECURE_SERVER_CONNECTION_H__) -#define __REST_HTTP__SECURE_SERVER_CONNECTION_H__ - -#if defined(ENABLE_SSL) - -#include "common/Defines.h" -#include "common/network/rest/http/HTTPLexer.h" -#include "common/network/rest/http/HTTPPayload.h" -#include "common/Log.h" - -#include -#include -#include -#include - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- - - template class ServerConnectionManager; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class represents a single connection from a client. - * @tparam RequestHandlerType Type representing a request handler. - * @ingroup http - */ - template - class SecureServerConnection : public std::enable_shared_from_this> { - typedef SecureServerConnection selfType; - typedef std::shared_ptr selfTypePtr; - typedef ServerConnectionManager ConnectionManagerType; - public: - auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete; - auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete; - SecureServerConnection(SecureServerConnection&) = delete; - - /** - * @brief Initializes a new instance of the SecureServerConnection class. - * @param socket TCP socket for this connection. - * @param context SSL context. - * @param manager Connection manager for this connection. - * @param handler Request handler for this connection. - * @param persistent Flag indicating whether or not the connection is persistent. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - explicit SecureServerConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, ConnectionManagerType& manager, RequestHandlerType& handler, - bool persistent = false, bool debug = false) : - m_socket(std::move(socket), context), - m_connectionManager(manager), - m_requestHandler(handler), - m_lexer(HTTPLexer(false)), - m_continue(false), - m_contResult(HTTPLexer::INDETERMINATE), - m_persistent(persistent), - m_debug(debug) - { - /* stub */ - } - - /** - * @brief Start the first asynchronous operation for the connection. - */ - void start() { handshake(); } - /** - * @brief Stop all asynchronous operations associated with the connection. - */ - void stop() - { - try - { - if (m_socket.lowest_layer().is_open()) { - m_socket.lowest_layer().close(); - } - } - catch(const std::exception&) { /* ignore */ } - } - - private: - /** - * @brief Perform an asynchronous SSL handshake. - */ - void handshake() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } - - m_socket.async_handshake(asio::ssl::stream_base::server, [this](asio::error_code ec) { - if (!ec) { - read(); - } - }); - } - - /** - * @brief Perform an asynchronous read operation. - */ - void read() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } - - m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t recvLength) { - if (!ec) { - HTTPLexer::ResultType result = HTTPLexer::GOOD; - char* content; - - // catch exceptions here so we don't blatently crash the system - try - { - if (!m_continue) { - std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + recvLength); - - m_request.content = std::string(); - std::string contentLength = m_request.headers.find("Content-Length"); - if (contentLength != "" && (::strlen(content) != 0)) { - size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); - m_request.contentLength = length; - m_request.content = std::string(content, length); - } - - m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string()); - - uint32_t consumed = m_lexer.consumed(); - if (result == HTTPLexer::GOOD && consumed == recvLength && - ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { - if (m_debug) { - LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); - } - - result = HTTPLexer::INDETERMINATE; - m_continue = true; - } - } else { - if (m_debug) { - LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); - } - - if (m_contResult == HTTPLexer::INDETERMINATE) { - m_request.content = std::string(m_buffer.data(), recvLength); - } else { - m_request.content.append(std::string(m_buffer.data(), recvLength)); - } - - if (m_request.contentLength != 0 && recvLength < m_request.contentLength) { - m_contResult = result = HTTPLexer::CONTINUE; - m_continue = true; - } - } - - if (result == HTTPLexer::GOOD) { - if (m_debug) { - Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); - } - - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - m_requestHandler.handleRequest(m_request, m_reply); - - if (m_debug) { - Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); - } - - write(); - } - else if (result == HTTPLexer::BAD) { - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); - write(); - } - else { - read(); - } - } - catch(const std::exception& e) { - ::LogError(LOG_REST, "SecureServerConnection::read(), %s", ec.message().c_str()); - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - } - } - else if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "SecureServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); - } - m_connectionManager.stop(this->shared_from_this()); - m_continue = false; - } - }); - } - - /** - * @brief Perform an asynchronous write operation. - */ - void write() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } else { - m_reply.headers.add("Connection", "keep-alive"); - } - - auto buffers = m_reply.toBuffers(); - asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) { - if (m_persistent) { - m_lexer.reset(); - m_reply.headers = HTTPHeaders(); - m_reply.status = HTTPPayload::OK; - m_reply.content = ""; - m_request = HTTPPayload(); - read(); - } - else { - if (!ec) { - try - { - // initiate graceful connection closure - asio::error_code ignored_ec; - m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); - } - catch(const std::exception& e) { ::LogError(LOG_REST, "%s", ec.message().c_str()); } - } - - if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "SecureServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - } - m_connectionManager.stop(this->shared_from_this()); - } - } - }); - } - - asio::ssl::stream m_socket; - - ConnectionManagerType& m_connectionManager; - RequestHandlerType& m_requestHandler; - - std::array m_buffer; - - HTTPPayload m_request; - HTTPLexer m_lexer; - HTTPPayload m_reply; - - bool m_continue; - HTTPLexer::ResultType m_contResult; - - bool m_persistent; - bool m_debug; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // ENABLE_SSL - -#endif // __REST_HTTP__SECURE_SERVER_CONNECTION_H__ diff --git a/src/common/network/rest/http/ServerConnection.h b/src/common/network/rest/http/ServerConnection.h deleted file mode 100644 index c7b932a32..000000000 --- a/src/common/network/rest/http/ServerConnection.h +++ /dev/null @@ -1,267 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file ServerConnection.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SERVER_CONNECTION_H__) -#define __REST_HTTP__SERVER_CONNECTION_H__ - -#include "common/Defines.h" -#include "common/network/rest/http/HTTPLexer.h" -#include "common/network/rest/http/HTTPPayload.h" -#include "common/Log.h" -#include "common/Utils.h" - -#include -#include -#include -#include - -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Prototypes - // --------------------------------------------------------------------------- - - template class ServerConnectionManager; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class represents a single connection from a client. - * @tparam RequestHandlerType Type representing a request handler. - * @ingroup http - */ - template - class ServerConnection : public std::enable_shared_from_this> { - typedef ServerConnection selfType; - typedef std::shared_ptr selfTypePtr; - typedef ServerConnectionManager ConnectionManagerType; - public: - auto operator=(ServerConnection&) -> ServerConnection& = delete; - auto operator=(ServerConnection&&) -> ServerConnection& = delete; - ServerConnection(ServerConnection&) = delete; - - /** - * @brief Initializes a new instance of the ServerConnection class. - * @param socket TCP socket for this connection. - * @param manager Connection manager for this connection. - * @param handler Request handler for this connection. - * @param persistent Flag indicating whether or not the connection is persistent. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - explicit ServerConnection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler, - bool persistent = false, bool debug = false) : - m_socket(std::move(socket)), - m_connectionManager(manager), - m_requestHandler(handler), - m_lexer(HTTPLexer(false)), - m_continue(false), - m_contResult(HTTPLexer::INDETERMINATE), - m_persistent(persistent), - m_debug(debug) - { - /* stub */ - } - - /** - * @brief Start the first asynchronous operation for the connection. - */ - void start() { read(); } - /** - * @brief Stop all asynchronous operations associated with the connection. - */ - void stop() - { - try - { - if (m_socket.is_open()) { - m_socket.close(); - } - } - catch(const std::exception&) { /* ignore */ } - } - - private: - /** - * @brief Perform an asynchronous read operation. - */ - void read() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } - - m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t recvLength) { - if (!ec) { - HTTPLexer::ResultType result = HTTPLexer::GOOD; - char* content; - - // catch exceptions here so we don't blatently crash the system - try - { - if (!m_continue) { - std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + recvLength); - - m_request.content = std::string(); - std::string contentLength = m_request.headers.find("Content-Length"); - if (contentLength != "" && (::strlen(content) != 0)) { - size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); - m_request.contentLength = length; - m_request.content = std::string(content, length); - } - - m_request.headers.add("RemoteHost", m_socket.remote_endpoint().address().to_string()); - - uint32_t consumed = m_lexer.consumed(); - if (result == HTTPLexer::GOOD && consumed == recvLength && - ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { - if (m_debug) { - LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); - Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); - } - - m_contResult = result = HTTPLexer::INDETERMINATE; - m_continue = true; - } - } else { - if (m_debug) { - LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); - Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); - } - - if (m_contResult == HTTPLexer::INDETERMINATE) { - m_request.content = std::string(m_buffer.data(), recvLength); - } else { - m_request.content.append(std::string(m_buffer.data(), recvLength)); - } - - if (m_request.contentLength != 0 && recvLength < m_request.contentLength) { - m_contResult = result = HTTPLexer::CONTINUE; - m_continue = true; - } - } - - if (result == HTTPLexer::GOOD) { - if (m_debug) { - Utils::dump(1U, "ServerConnection::read(), HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); - } - - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - m_requestHandler.handleRequest(m_request, m_reply); - - if (m_debug) { - Utils::dump(1U, "ServerConnection::read(), HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); - } - - write(); - } - else if (result == HTTPLexer::BAD) { - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); - write(); - } - else { - read(); - } - } - catch(const std::exception& e) { - ::LogError(LOG_REST, "ServerConnection::read(), %s", ec.message().c_str()); - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - } - } - else if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "ServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); - } - m_connectionManager.stop(this->shared_from_this()); - m_continue = false; - m_contResult = HTTPLexer::INDETERMINATE; - } - }); - } - - /** - * @brief Perform an asynchronous write operation. - */ - void write() - { - if (!m_persistent) { - auto self(this->shared_from_this()); - } else { - m_reply.headers.add("Connection", "keep-alive"); - } - - auto buffers = m_reply.toBuffers(); - asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) { - if (m_persistent) { - m_lexer.reset(); - m_reply.headers = HTTPHeaders(); - m_reply.status = HTTPPayload::OK; - m_reply.content = ""; - m_request = HTTPPayload(); - read(); - } - else { - if (!ec) { - try - { - // initiate graceful connection closure - asio::error_code ignored_ec; - m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); - } - catch(const std::exception& e) { ::LogError(LOG_REST, "ServerConnection::write(), %s", ec.message().c_str()); } - } - - if (ec != asio::error::operation_aborted) { - if (ec) { - ::LogError(LOG_REST, "ServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); - } - m_connectionManager.stop(this->shared_from_this()); - } - } - }); - } - - asio::ip::tcp::socket m_socket; - - ConnectionManagerType& m_connectionManager; - RequestHandlerType& m_requestHandler; - - std::array m_buffer; - - HTTPPayload m_request; - HTTPLexer m_lexer; - HTTPPayload m_reply; - - bool m_continue; - HTTPLexer::ResultType m_contResult; - - bool m_persistent; - bool m_debug; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__SERVER_CONNECTION_H__ diff --git a/src/common/network/rest/http/ServerConnectionManager.h b/src/common/network/rest/http/ServerConnectionManager.h deleted file mode 100644 index dc4e48409..000000000 --- a/src/common/network/rest/http/ServerConnectionManager.h +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file ServerConnection.h - * @ingroup http - */ -#if !defined(__REST_HTTP__SERVER_CONNECTION_MANAGER_H__) -#define __REST_HTTP__SERVER_CONNECTION_MANAGER_H__ - -#include "common/Defines.h" - -#include -#include - -namespace network -{ - namespace rest - { - namespace http - { - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Manages open connections so that they may be cleanly stopped when the server - * needs to shut down. - * @tparam ConnectionPtr - * @ingroup http - */ - template - class ServerConnectionManager { - public: - auto operator=(ServerConnectionManager&) -> ServerConnectionManager& = delete; - auto operator=(ServerConnectionManager&&) -> ServerConnectionManager& = delete; - ServerConnectionManager(ServerConnectionManager&) = delete; - - /** - * @brief Initializes a new instance of the ServerConnectionManager class. - */ - ServerConnectionManager() = default; - - /** - * @brief Add the specified connection to the manager and start it. - * @param c - */ - void start(ConnectionPtr c) - { - std::lock_guard guard(m_lock); - { - m_connections.insert(c); - } - c->start(); - } - - /** - * @brief Stop the specified connection. - * @param c - */ - void stop(ConnectionPtr c) - { - std::lock_guard guard(m_lock); - { - m_connections.erase(c); - } - c->stop(); - } - - /** - * @brief Stop all connections. - */ - void stopAll() - { - for (auto c : m_connections) - c->stop(); - - std::lock_guard guard(m_lock); - m_connections.clear(); - } - - private: - std::set m_connections; - std::mutex m_lock; - }; - } // namespace http - } // namespace rest -} // namespace network - -#endif // __REST_HTTP__SERVER_CONNECTION_MANAGER_H__ diff --git a/src/common/network/sip/RequestDispatcher.h b/src/common/network/sip/RequestDispatcher.h deleted file mode 100644 index 522b5c803..000000000 --- a/src/common/network/sip/RequestDispatcher.h +++ /dev/null @@ -1,219 +0,0 @@ -// 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. - * - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL - * - */ -/** - * @defgroup sip Session Initiation Protocol Services - * @brief Implementation for Session Initiation Protocol services. - * @ingroup network_core - * - * @file RequestDispatcher.h - * @ingroup sip - */ -#if !defined(__SIP__DISPATCHER_H__) -#define __SIP__DISPATCHER_H__ - -#include "common/Defines.h" -#include "common/network/sip/SIPPayload.h" -#include "common/Log.h" - -#include -#include -#include -#include -#include - -namespace network -{ - namespace sip - { - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Structure representing a request handler. - * @ingroup rest - */ - template - struct RequestHandler { - typedef std::function RequestHandlerType; - - /** - * @brief Initializes a new instance of the RequestHandler structure. - */ - explicit RequestHandler() { /* stub */} - - /** - * @brief Handler for INVITE requests. - * @param handler INVITE request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& invite(RequestHandlerType handler) { - m_handlers[SIP_INVITE] = handler; - return *this; - } - /** - * @brief Handler for ACK requests. - * @param handler ACK request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& ack(RequestHandlerType handler) { - m_handlers[SIP_ACK] = handler; - return *this; - } - /** - * @brief Handler for BYE requests. - * @param handler BYE request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& bye(RequestHandlerType handler) { - m_handlers[SIP_BYE] = handler; - return *this; - } - /** - * @brief Handler for CANCEL requests. - * @param handler CANCEL request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& cancel(RequestHandlerType handler) { - m_handlers[SIP_CANCEL] = handler; - return *this; - } - /** - * @brief Handler for REGISTER requests. - * @param handler REGISTER request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& registerReq(RequestHandlerType handler) { - m_handlers[SIP_REGISTER] = handler; - return *this; - } - /** - * @brief Handler for OPTIONS requests. - * @param handler OPTIONS request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& options(RequestHandlerType handler) { - m_handlers[SIP_OPTIONS] = handler; - return *this; - } - /** - * @brief Handler for MESSAGE requests. - * @param handler MESSAGE request handler. - * @return RequestHandler* Instance of a RequestHandler. - */ - RequestHandler& message(RequestHandlerType handler) { - m_handlers[SIP_MESSAGE] = handler; - return *this; - } - - /** - * @brief Helper to handle the actual request. - * @param request HTTP request. - * @param reply HTTP reply. - */ - void handleRequest(const Request& request, Reply& reply) { - // dispatching to handler - auto& handler = m_handlers[request.method]; - if (handler) { - handler(request, reply); - } - } - - private: - std::map m_handlers; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements SIP request dispatching. - * @tparam Request SIP request. - * @tparam Reply SIP reply. - */ - template - class SIPRequestDispatcher { - typedef RequestHandler HandlerType; - public: - /** - * @brief Initializes a new instance of the SIPRequestDispatcher class. - */ - SIPRequestDispatcher() : m_handlers(), m_debug(false) { /* stub */ } - /** - * @brief Initializes a new instance of the SIPRequestDispatcher class. - * @param debug Flag indicating whether or not verbose logging should be enabled. - */ - SIPRequestDispatcher(bool debug) : m_handlers(), m_debug(debug) { /* stub */ } - - /** - * @brief Helper to set a request handler. - * @returns HandlerType Instance of a request handler. - */ - HandlerType& handler() - { - HandlerTypePtr& p = m_handlers; - return *p; - } - - /** - * @brief Helper to handle SIP request. - * @param request SIP request. - * @param reply SIP reply. - */ - void handleRequest(const Request& request, Reply& reply) - { - m_handlers.handleRequest(request, reply); - return; - } - - private: - typedef std::shared_ptr HandlerTypePtr; - HandlerType m_handlers; - - bool m_debug; - }; - - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This class implements a generic debug request dispatcher. - * @tparam Request SIP request. - * @tparam Reply SIP reply. - */ - template - class SIPDebugRequestDispatcher { - public: - /** - * @brief Initializes a new instance of the SIPDebugRequestDispatcher class. - */ - SIPDebugRequestDispatcher() { /* stub */ } - - /** - * @brief Helper to handle SIP request. - * @param request SIP request. - * @param reply SIP reply. - */ - void handleRequest(const Request& request, Reply& reply) - { - for (auto header : request.headers.headers()) - ::LogDebugEx(LOG_SIP, "SIPDebugRequestDispatcher::handleRequest()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); - - ::LogDebugEx(LOG_SIP, "SIPDebugRequestDispatcher::handleRequest()", "content = %s", request.content.c_str()); - } - }; - - typedef SIPRequestDispatcher DefaultSIPRequestDispatcher; - } // namespace sip -} // namespace network - -#endif // __SIP__DISPATCHER_H__ diff --git a/src/common/network/sip/SIPLexer.cpp b/src/common/network/sip/SIPLexer.cpp deleted file mode 100644 index ff408329c..000000000 --- a/src/common/network/sip/SIPLexer.cpp +++ /dev/null @@ -1,402 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL - * - */ -#include "Defines.h" -#include "network/sip/SIPLexer.h" -#include "network/sip/SIPPayload.h" -#include "Log.h" - -using namespace network::sip; - -#include - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Initializes a new instance of the SIPLexer class. */ - -SIPLexer::SIPLexer(bool clientLexer) : - m_headers(), - m_clientLexer(clientLexer), - m_consumed(0U), - m_state(METHOD_START) -{ - if (m_clientLexer) { - m_state = SIP_VERSION_S; - } -} - -/* Reset to initial parser state. */ - -void SIPLexer::reset() -{ - m_state = METHOD_START; - if (m_clientLexer) { - m_state = SIP_VERSION_S; - } - - m_headers = std::vector(); -} - -// --------------------------------------------------------------------------- -// Private Class Members -// --------------------------------------------------------------------------- - -/* Handle the next character of input. */ - -SIPLexer::ResultType SIPLexer::consume(SIPPayload& req, char input) -{ - m_consumed++; - switch (m_state) - { - /* - ** HTTP Method - */ - - case METHOD_START: - if (!isChar(input) || isControl(input) || isSpecial(input)) - return BAD; - else { - m_state = METHOD; - req.method.push_back(input); - return INDETERMINATE; - } - case METHOD: - if (input == ' ') { - m_state = URI; - return INDETERMINATE; - } - else if (!isChar(input) || isControl(input) || isSpecial(input)) { - return BAD; - } - else { - req.method.push_back(input); - return INDETERMINATE; - } - - /* - ** URI - */ - - case URI: - if (input == ' ') { - m_state = SIP_VERSION_S; - return INDETERMINATE; - } - else if (isControl(input)) { - return BAD; - } - else { - req.uri.push_back(input); - return INDETERMINATE; - } - - /* - ** SIP/2.0 - ** SIP/2.0 200 OK - */ - case SIP_VERSION_S: - if (input == 'S') { - m_state = SIP_VERSION_I; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_I: - if (input == 'I') { - m_state = SIP_VERSION_P; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_P: - if (input == 'P') { - m_state = SIP_VERSION_SLASH; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_SLASH: - if (input == '/') { - req.sipVersionMajor = 0; - req.sipVersionMinor = 0; - m_state = SIP_VERSION_MAJOR_START; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_MAJOR_START: - if (isDigit(input)) { - req.sipVersionMajor = req.sipVersionMajor * 10 + input - '0'; - m_state = SIP_VERSION_MAJOR; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_MAJOR: - if (input == '.') { - m_state = SIP_VERSION_MINOR_START; - return INDETERMINATE; - } - else if (isDigit(input)) { - req.sipVersionMajor = req.sipVersionMajor * 10 + input - '0'; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_MINOR_START: - if (isDigit(input)) { - req.sipVersionMinor = req.sipVersionMinor * 10 + input - '0'; - m_state = SIP_VERSION_MINOR; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_VERSION_MINOR: - if (input == '\r') { - m_state = EXPECTING_NEWLINE_1; - if (m_clientLexer) { - return BAD; - } - else { - return INDETERMINATE; - } - } - else if (input == ' ') { - if (m_clientLexer) { - m_state = SIP_STATUS_1; - return INDETERMINATE; - } - else { - return BAD; - } - } - else if (isDigit(input)) { - req.sipVersionMinor = req.sipVersionMinor * 10 + input - '0'; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_STATUS_1: - if (isDigit(input)) { - m_state = SIP_STATUS_2; - m_status = m_status * 10 + input - '0'; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_STATUS_2: - if (isDigit(input)) { - m_state = SIP_STATUS_3; - m_status = m_status * 10 + input - '0'; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_STATUS_3: - if (isDigit(input)) { - m_state = SIP_STATUS_END; - m_status = m_status * 10 + input - '0'; - req.status = (SIPPayload::StatusType)m_status; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_STATUS_END: - if (input == ' ') { - m_state = SIP_STATUS_MESSAGE; - return INDETERMINATE; - } - else { - return BAD; - } - case SIP_STATUS_MESSAGE_START: - if (!isChar(input) || isControl(input) || isSpecial(input)) { - return BAD; - } - else { - m_state = SIP_STATUS_MESSAGE; - return INDETERMINATE; - } - case SIP_STATUS_MESSAGE: - if (input == '\r') { - m_state = EXPECTING_NEWLINE_1; - return INDETERMINATE; - } - else if (input == ' ') { - m_state = SIP_STATUS_MESSAGE; - return INDETERMINATE; - } - else if (!isChar(input) || isControl(input) || isSpecial(input)) { - return BAD; - } - else { - return INDETERMINATE; - } - - case EXPECTING_NEWLINE_1: - if (input == '\n') { - m_state = HEADER_LINE_START; - return INDETERMINATE; - } - else { - return BAD; - } - - /* - ** Headers - */ - - case HEADER_LINE_START: - if (input == '\r') { - m_state = EXPECTING_NEWLINE_3; - return INDETERMINATE; - } - else if (!req.headers.empty() && (input == ' ' || input == '\t')) { - m_state = HEADER_LWS; - return INDETERMINATE; - } - else if (!isChar(input) || isControl(input) || isSpecial(input)) { - return BAD; - } - else { - m_headers.push_back(LexedHeader()); - m_headers.back().name.push_back(std::tolower(input)); - m_state = HEADER_NAME; - return INDETERMINATE; - } - - case HEADER_LWS: - if (input == '\r') { - m_state = EXPECTING_NEWLINE_2; - return INDETERMINATE; - } - else if (input == ' ' || input == '\t') { - return INDETERMINATE; - } - else if (isControl(input)) { - return BAD; - } - else { - m_state = HEADER_VALUE; - m_headers.back().value.push_back(input); - return INDETERMINATE; - } - - case HEADER_NAME: - if (input == ':') { - m_state = SPACE_BEFORE_HEADER_VALUE; - return INDETERMINATE; - } - else if (!isChar(input) || isControl(input) || isSpecial(input)) { - return BAD; - } - else - { - m_headers.back().name.push_back(std::tolower(input)); - return INDETERMINATE; - } - - case SPACE_BEFORE_HEADER_VALUE: - if (input == ' ') { - m_state = HEADER_VALUE; - return INDETERMINATE; - } - else { - return BAD; - } - - case HEADER_VALUE: - if (input == '\r') { - m_state = EXPECTING_NEWLINE_2; - return INDETERMINATE; - } - else if (isControl(input)) { - return BAD; - } - else { - m_headers.back().value.push_back(input); - return INDETERMINATE; - } - - case EXPECTING_NEWLINE_2: - if (input == '\n') { - m_state = HEADER_LINE_START; - return INDETERMINATE; - } - else { - return BAD; - } - - case EXPECTING_NEWLINE_3: - if (input == '\n') { - for (auto header : m_headers) { - //::LogDebugEx(LOG_SIP, "SIPLexer::consume()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); - req.headers.add(header.name, header.value); - } - - return GOOD; - } else { - return BAD; - } - - default: - return BAD; - } -} - -/* Check if a byte is an SIP character. */ - -bool SIPLexer::isChar(int c) -{ - return c >= 0 && c <= 127; -} - -/* Check if a byte is an SIP control character. */ - -bool SIPLexer::isControl(int c) -{ - return (c >= 0 && c <= 31) || (c == 127); -} - -/* Check if a byte is an SIP special character. */ - -bool SIPLexer::isSpecial(int c) -{ - switch (c) - { - case '(': case ')': case '<': case '>': case '@': - case ',': case ';': case ':': case '\\': case '"': - case '/': case '[': case ']': case '?': case '=': - case '{': case '}': case ' ': case '\t': - return true; - default: - return false; - } -} - -/* Check if a byte is an digit. */ - -bool SIPLexer::isDigit(int c) -{ - return c >= '0' && c <= '9'; -} diff --git a/src/common/network/sip/SIPPayload.cpp b/src/common/network/sip/SIPPayload.cpp deleted file mode 100644 index 29e20bdcc..000000000 --- a/src/common/network/sip/SIPPayload.cpp +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL - * - */ -#include "Defines.h" -#include "network/sip/SIPPayload.h" -#include "Log.h" -#include "Utils.h" - -using namespace network::sip; - -#include - -namespace status_strings { - const std::string trying = "SIP/2.0 100 Trying\r\n"; - const std::string ringing = "SIP/2.0 180 Ringing\r\n"; - const std::string ok = "SIP/2.0 200 OK\r\n"; - const std::string accepted = "SIP/2.0 202 Accepted\r\n"; - const std::string no_notify = "SIP/2.0 204 No Notification\r\n"; - const std::string multiple_choices = "SIP/2.0 300 Multiple Choices\r\n"; - const std::string moved_permanently = "SIP/2.0 301 Moved Permanently\r\n"; - const std::string moved_temporarily = "SIP/2.0 302 Moved Temporarily\r\n"; - const std::string bad_request = "SIP/2.0 400 Bad Request\r\n"; - const std::string unauthorized = "SIP/2.0 401 Unauthorized\r\n"; - const std::string forbidden = "SIP/2.0 403 Forbidden\r\n"; - const std::string not_found = "SIP/2.0 404 Not Found\r\n"; - const std::string internal_server_error = "SIP/2.0 500 Internal Server Error\r\n"; - const std::string not_implemented = "SIP/2.0 501 Not Implemented\r\n"; - const std::string bad_gateway = "SIP/2.0 502 Bad Gateway\r\n"; - const std::string service_unavailable = "SIP/2.0 503 Service Unavailable\r\n"; - const std::string busy_everywhere = "SIP/2.0 600 Busy Everywhere\r\n"; - const std::string decline = "SIP/2.0 603 Decline\r\n"; - - asio::const_buffer toBuffer(SIPPayload::StatusType status) - { - switch (status) - { - case SIPPayload::TRYING: - return asio::buffer(trying); - case SIPPayload::RINGING: - return asio::buffer(ringing); - case SIPPayload::OK: - return asio::buffer(ok); - case SIPPayload::ACCEPTED: - return asio::buffer(accepted); - case SIPPayload::NO_NOTIFY: - return asio::buffer(no_notify); - case SIPPayload::MULTIPLE_CHOICES: - return asio::buffer(multiple_choices); - case SIPPayload::MOVED_PERMANENTLY: - return asio::buffer(moved_permanently); - case SIPPayload::MOVED_TEMPORARILY: - return asio::buffer(moved_temporarily); - case SIPPayload::BAD_REQUEST: - return asio::buffer(bad_request); - case SIPPayload::UNAUTHORIZED: - return asio::buffer(unauthorized); - case SIPPayload::FORBIDDEN: - return asio::buffer(forbidden); - case SIPPayload::NOT_FOUND: - return asio::buffer(not_found); - case SIPPayload::INTERNAL_SERVER_ERROR: - return asio::buffer(internal_server_error); - case SIPPayload::NOT_IMPLEMENTED: - return asio::buffer(not_implemented); - case SIPPayload::BAD_GATEWAY: - return asio::buffer(bad_gateway); - case SIPPayload::SERVICE_UNAVAILABLE: - return asio::buffer(service_unavailable); - case SIPPayload::BUSY_EVERYWHERE: - return asio::buffer(busy_everywhere); - case SIPPayload::DECLINE: - return asio::buffer(decline); - default: - return asio::buffer(internal_server_error); - } - } -} // namespace status_strings - -namespace misc_strings { - const char name_value_separator[] = { ':', ' ' }; - const char request_method_separator[] = { ' ' }; - const char crlf[] = { '\r', '\n' }; - - const char sip_default_version[] = { 'S', 'I', 'P', '/', '2', '.', '0' }; -} // namespace misc_strings - -// --------------------------------------------------------------------------- -// Public Class Members -// --------------------------------------------------------------------------- - -/* Convert the reply into a vector of buffers. The buffers do not own the underlying memory blocks, therefore the reply object must remain valid and not be changed until the write operation has completed. */ - -std::vector SIPPayload::toBuffers() -{ - std::vector buffers; - if (isClientPayload) { - // copy method and erase zero terminator - method.erase(std::find(method.begin(), method.end(), '\0'), method.end()); - - // copy URI and erase zero terminator - uri.erase(std::find(uri.begin(), uri.end(), '\0'), uri.end()); -#if DEBUG_SIP_PAYLOAD - ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "method = %s, uri = %s", method.c_str(), uri.c_str()); -#endif - buffers.push_back(asio::buffer(method)); - buffers.push_back(asio::buffer(misc_strings::request_method_separator)); - buffers.push_back(asio::buffer(uri)); - buffers.push_back(asio::buffer(misc_strings::request_method_separator)); - buffers.push_back(asio::buffer(misc_strings::sip_default_version)); - buffers.push_back(asio::buffer(misc_strings::crlf)); - } - else { - buffers.push_back(status_strings::toBuffer(status)); - } - - for (std::size_t i = 0; i < headers.size(); ++i) { - SIPHeaders::Header& h = headers.m_headers[i]; -#if DEBUG_SIP_PAYLOAD - ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "header = %s, value = %s", h.name.c_str(), h.value.c_str()); -#endif - - buffers.push_back(asio::buffer(h.name)); - buffers.push_back(asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(asio::buffer(h.value)); - buffers.push_back(asio::buffer(misc_strings::crlf)); - } - - buffers.push_back(asio::buffer(misc_strings::crlf)); - if (content.size() > 0) - buffers.push_back(asio::buffer(content)); - -#if DEBUG_SIP_PAYLOAD - ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str()); - for (auto buffer : buffers) - Utils::dump("SIPPayload::toBuffers(), buffer[]", (uint8_t*)buffer.data(), buffer.size()); -#endif - - return buffers; -} - -/* Prepares payload for transmission by finalizing status and content type. */ - -void SIPPayload::payload(json::object& obj, SIPPayload::StatusType s) -{ - json::value v = json::value(obj); - std::string json = std::string(v.serialize()); - payload(json, s, "application/json"); -} - -/* Prepares payload for transmission by finalizing status and content type. */ - -void SIPPayload::payload(std::string& c, SIPPayload::StatusType s, const std::string& contentType) -{ - content = c; - status = s; - ensureDefaultHeaders(contentType); -} - -// --------------------------------------------------------------------------- -// Static Members -// --------------------------------------------------------------------------- - -/* Get a status payload. */ - -SIPPayload SIPPayload::requestPayload(std::string method, std::string uri) -{ - SIPPayload rep; - rep.isClientPayload = true; - rep.method = ::strtoupper(method); - rep.uri = std::string(uri); - return rep; -} - -/* Get a status payload. */ - -SIPPayload SIPPayload::statusPayload(SIPPayload::StatusType status, const std::string& contentType) -{ - SIPPayload rep; - rep.isClientPayload = false; - rep.status = status; - rep.ensureDefaultHeaders(contentType); - - return rep; -} - - -/* Helper to attach a host TCP stream reader. */ - -void SIPPayload::attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint) -{ - headers.add("Host", std::string(remoteEndpoint.address().to_string() + ":" + std::to_string(remoteEndpoint.port()))); -} - -// --------------------------------------------------------------------------- -// Private Members -// --------------------------------------------------------------------------- - -/* Internal helper to ensure the headers are of a default for the given content type. */ - -void SIPPayload::ensureDefaultHeaders(const std::string& contentType) -{ - if (!isClientPayload) { - headers.add("Content-Type", std::string(contentType)); - headers.add("Content-Length", std::to_string(content.size())); - headers.add("Server", std::string(("DVM/" __VER__))); - } - else { - headers.add("User-Agent", std::string(("DVM/" __VER__))); - headers.add("Accept", "*/*"); - headers.add("Content-Type", std::string(contentType)); - headers.add("Content-Length", std::to_string(content.size())); - } -} diff --git a/src/common/network/sip/SIPPayload.h b/src/common/network/sip/SIPPayload.h deleted file mode 100644 index 85978e571..000000000 --- a/src/common/network/sip/SIPPayload.h +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -/* - * Digital Voice Modem - Common Library - * BSL-1.0 Open Source. Use is subject to license terms. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL - * - */ -/** - * @file SIPPayload.h - * @ingroup sip - * @file SIPPayload.cpp - * @ingroup sip - */ -#if !defined(__SIP__SIP_PAYLOAD_H__) -#define __SIP__SIP_PAYLOAD_H__ - -#include "common/Defines.h" -#include "common/network/json/json.h" -#include "common/network/sip/SIPHeaders.h" - -#include -#include - -#include - -namespace network -{ - namespace sip - { - // --------------------------------------------------------------------------- - // Constants - // --------------------------------------------------------------------------- - - #define SIP_INVITE "INVITE" - #define SIP_ACK "ACK" - #define SIP_BYE "BYE" - #define SIP_CANCEL "CANCEL" - #define SIP_REGISTER "REGISTER" - #define SIP_OPTIONS "OPTIONS" - #define SIP_MESSAGE "MESSAGE" - - // --------------------------------------------------------------------------- - // Structure Declaration - // --------------------------------------------------------------------------- - - /** - * @brief This struct implements a model of a payload to be sent to a - * SIP client/server. - * @ingroup sip - */ - struct SIPPayload { - /** - * @brief SIP Status/Response Codes - */ - enum StatusType { - TRYING = 100, //! SIP Trying 100 - RINGING = 180, //! SIP Ringing 180 - - OK = 200, //! SIP OK 200 - ACCEPTED = 202, //! SIP Accepted 202 - NO_NOTIFY = 204, //! SIP No Notification 204 - - MULTIPLE_CHOICES = 300, //! SIP Multiple Choices 300 - MOVED_PERMANENTLY = 301, //! SIP Moved Permenantly 301 - MOVED_TEMPORARILY = 302, //! SIP Moved Temporarily 302 - - BAD_REQUEST = 400, //! SIP Bad Request 400 - UNAUTHORIZED = 401, //! SIP Unauthorized 401 - FORBIDDEN = 403, //! SIP Forbidden 403 - NOT_FOUND = 404, //! SIP Not Found 404 - - INTERNAL_SERVER_ERROR = 500, //! SIP Internal Server Error 500 - NOT_IMPLEMENTED = 501, //! SIP Not Implemented 501 - BAD_GATEWAY = 502, //! SIP Bad Gateway 502 - SERVICE_UNAVAILABLE = 503, //! SIP Service Unavailable 503 - - BUSY_EVERYWHERE = 600, //! SIP Busy Everywhere 600 - DECLINE = 603, //! SIP Decline 603 - } status; - - SIPHeaders headers; - std::string content; - size_t contentLength; - - std::string method; - std::string uri; - - int sipVersionMajor; - int sipVersionMinor; - - bool isClientPayload = false; - - /** - * @brief Convert the payload into a vector of buffers. The buffers do not own the - * underlying memory blocks, therefore the payload object must remain valid and - * not be changed until the write operation has completed. - * @returns std::vector List of buffers representing the SIP payload. - */ - std::vector toBuffers(); - - /** - * @brief Prepares payload for transmission by finalizing status and content type. - * @param obj - * @param status SIP status. - */ - void payload(json::object& obj, StatusType status = OK); - /** - * @brief Prepares payload for transmission by finalizing status and content type. - * @param content - * @param status SIP status. - * @param contentType SIP content type. - */ - void payload(std::string& content, StatusType status = OK, const std::string& contentType = "application/sdp"); - - /** - * @brief Get a request payload. - * @param method SIP method. - * @param uri SIP uri. - */ - static SIPPayload requestPayload(std::string method, std::string uri); - /** - * @brief Get a status payload. - * @param status SIP status. - * @param contentType SIP content type. - */ - static SIPPayload statusPayload(StatusType status, const std::string& contentType = "application/sdp"); - - /** - * @brief Helper to attach a host TCP stream reader. - * @param remoteEndpoint Endpoint. - */ - void attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint); - - private: - /** - * @brief Internal helper to ensure the headers are of a default for the given content type. - * @param contentType SIP content type. - */ - void ensureDefaultHeaders(const std::string& contentType = "application/sdp"); - }; - } // namespace sip -} // namespace network - -#endif // __SIP__SIP_PAYLOAD_H__ diff --git a/src/common/network/tcp/SecureTcpClient.cpp b/src/common/network/tcp/SecureTcpClient.cpp index 0b3d4b0f8..a928ecb2a 100644 --- a/src/common/network/tcp/SecureTcpClient.cpp +++ b/src/common/network/tcp/SecureTcpClient.cpp @@ -18,6 +18,6 @@ using namespace network::tcp; // Static Class Members // --------------------------------------------------------------------------- -std::string SecureTcpClient::m_sslHostname = std::string(); +std::string SecureTcpClient::s_sslHostname = std::string(); #endif // ENABLE_SSL \ No newline at end of file diff --git a/src/common/network/tcp/SecureTcpClient.h b/src/common/network/tcp/SecureTcpClient.h index 657ce8c8d..f8a654c8b 100644 --- a/src/common/network/tcp/SecureTcpClient.h +++ b/src/common/network/tcp/SecureTcpClient.h @@ -244,11 +244,11 @@ namespace network * @brief Sets the hostname for the SSL certificate. * @param hostname Hostname. */ - static void setHostname(std::string hostname) { m_sslHostname = hostname; } + static void setHostname(std::string hostname) { s_sslHostname = hostname; } private: sockaddr_storage m_sockaddr; - static std::string m_sslHostname; + static std::string s_sslHostname; SSL* m_pSSL; SSL_CTX* m_pSSLCtx; @@ -284,7 +284,7 @@ namespace network } SSL_set_hostflags(m_pSSL, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (!SSL_set1_host(m_pSSL, SecureTcpClient::m_sslHostname.c_str())) { + if (!SSL_set1_host(m_pSSL, SecureTcpClient::s_sslHostname.c_str())) { LogError(LOG_NET, "Failed to set SSL hostname, %s err: %d", ERR_error_string(ERR_get_error(), NULL), errno); throw std::runtime_error("Failed to set SSL hostname"); } diff --git a/src/common/network/tcp/SecureTcpListener.h b/src/common/network/tcp/SecureTcpListener.h index 978c4bcd6..6d7bdab7f 100644 --- a/src/common/network/tcp/SecureTcpListener.h +++ b/src/common/network/tcp/SecureTcpListener.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Common Library -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index 8ec1f2cf9..748f1226a 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2006-2016,2020 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -161,6 +161,86 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t return true; } +/* Sets the socket receive buffer size. */ + +bool Socket::recvBufSize(ssize_t bufSize) +{ + int optVal = -1; + socklen_t optValLen = sizeof(optVal); + + // resize buffer + if (::setsockopt(m_fd, SOL_SOCKET, SO_RCVBUF, (char*)& bufSize, sizeof(bufSize)) == -1) { +#if defined(_WIN32) + LogError(LOG_NET, "Cannot resize the receive buffer size, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot resize the receive buffer size, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 + return false; + } + + // get the buffer size after alteration + if (::getsockopt(m_fd, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, &optValLen) == -1) { +#if defined(_WIN32) + LogError(LOG_NET, "Cannot get the receive buffer size, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot get the receive buffer size, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 + return false; + } + + /* + ** bryanb: this check may seem strange, but on Linux the kernel doubles the + ** requested buffer size for its own overhead, so we just need to ensure + ** that the returned buffer size is at least what we requested + */ + if (optVal >= bufSize) + return true; + else + LogWarning(LOG_NET, "Could not resize socket recv buffer, %u != %u. This is suboptimal and may result in lost packets.", optVal, bufSize); + + return false; +} + +/* Sets the socket send buffer size. */ + +bool Socket::sendBufSize(ssize_t bufSize) +{ + int optVal = -1; + socklen_t optValLen = sizeof(optVal); + + // resize buffer + if (::setsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, (char*)&bufSize, sizeof(bufSize)) == -1) { +#if defined(_WIN32) + LogError(LOG_NET, "Cannot resize the send buffer size, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot resize the send buffer size, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 + return false; + } + + // get the buffer size after alteration + if (::getsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optValLen) == -1) { +#if defined(_WIN32) + LogError(LOG_NET, "Cannot get the send buffer size, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot get the send buffer size, err: %d (%s)", errno, strerror(errno)); +#endif // _WIN32 + return false; + } + + /* + ** bryanb: this check may seem strange, but on Linux the kernel doubles the + ** requested buffer size for its own overhead, so we just need to ensure + ** that the returned buffer size is at least what we requested + */ + if (optVal >= bufSize) + return true; + else + LogWarning(LOG_NET, "Could not resize socket send buffer, %u != %u. This is suboptimal and may result in lost packets.", optVal, bufSize); + + return false; +} + /* Closes the UDP socket connection. */ void Socket::close() @@ -227,7 +307,7 @@ ssize_t Socket::read(uint8_t* buffer, uint32_t length, sockaddr_storage& address #endif // defined(_WIN32) if (len == -1 && errno == ENOTSOCK) { - LogMessage(LOG_NET, "Re-opening UDP port on %u", m_localPort); + LogInfoEx(LOG_NET, "Re-opening UDP port on %u", m_localPort); close(); open(); } @@ -403,29 +483,30 @@ bool Socket::write(const uint8_t* buffer, uint32_t length, const sockaddr_storag /* Write data to the UDP socket. */ -bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept +bool Socket::write(BufferQueue* buffers, ssize_t* lenWritten) noexcept { - bool result = false; - if (m_fd < 0) { + if (buffers == nullptr) { if (lenWritten != nullptr) { *lenWritten = -1; } - LogError(LOG_NET, "tried to write datagram with no file descriptor? this shouldn't happen BUGBUG"); + LogError(LOG_NET, "tried to write datagram with buffers? this shouldn't happen BUGBUG"); return false; } - if (buffers.empty()) { + size_t currentQueueSize = buffers->size(); + + bool result = false; + if (m_fd < 0) { if (lenWritten != nullptr) { *lenWritten = -1; } + LogError(LOG_NET, "tried to write datagram with no file descriptor? this shouldn't happen BUGBUG"); return false; } - // bryanb: this is the same as above -- but for some assinine reason prevents - // weirdness - if (buffers.size() == 0U) { + if (buffers->empty()) { if (lenWritten != nullptr) { *lenWritten = -1; } @@ -433,11 +514,9 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept return false; } - // LogDebug(LOG_NET, "buffers len = %u", buffers.size()); - - if (buffers.size() > UINT16_MAX) { - LogError(LOG_NET, "Trying to send too many buffers?"); - + // bryanb: this is the same as above -- but for some assinine reason prevents + // weirdness + if (currentQueueSize == 0U) { if (lenWritten != nullptr) { *lenWritten = -1; } @@ -445,7 +524,9 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept return false; } - // LogDebug(LOG_NET, "Sending message(s) (to %s:%u) addrLen %u", Socket::address(address).c_str(), Socket::port(address), addrLen); + // LogDebugEx(LOG_NET, "Socket::write()", "buffers len = %u", currentQueueSize); + if (currentQueueSize > UINT16_MAX) + currentQueueSize = UINT16_MAX; // only send up to this many buffers // are we crypto wrapped? if (m_isCryptoWrapped) { @@ -460,113 +541,121 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept } } - int sent = 0; + int sent = 0, msgs = 0; + struct sockaddr_storage* addresses[MAX_BUFFER_COUNT]; struct mmsghdr headers[MAX_BUFFER_COUNT]; struct iovec chunks[MAX_BUFFER_COUNT]; // create mmsghdrs from input buffers and send them at once - int size = buffers.size(); - for (size_t i = 0; i < buffers.size(); ++i) { - if (buffers[i] == nullptr) { - --size; + for (size_t i = 0U; i < currentQueueSize; ++i) { + UDPDatagram* packet = buffers->front(); + buffers->pop(); + if (packet == nullptr) { continue; } - uint32_t length = buffers[i]->length; - if (buffers[i]->buffer == nullptr) { + uint32_t length = packet->length; + if (packet->buffer == nullptr) { LogError(LOG_NET, "discarding buffered message with len = %u, but deleted buffer?", length); - --size; + delete packet; continue; } - try { - // are we crypto wrapped? - if (m_isCryptoWrapped && m_presharedKey != nullptr) { - uint32_t cryptedLen = length * sizeof(uint8_t); - uint8_t* cryptoBuffer = buffers[i]->buffer; + if (m_af != packet->address.ss_family) { + LogError(LOG_NET, "Socket::write() mismatched network address family? this isn't normal, aborting"); + if (packet->buffer != nullptr) + delete[] packet->buffer; + delete packet; + continue; + } - // do we need to pad the original buffer to be block aligned? - if (cryptedLen % crypto::AES::BLOCK_BYTES_LEN != 0) { - uint32_t alignment = crypto::AES::BLOCK_BYTES_LEN - (cryptedLen % crypto::AES::BLOCK_BYTES_LEN); - cryptedLen += alignment; + uint8_t* iov_buffer = new uint8_t[packet->length]; + size_t iov_length = packet->length; + sockaddr_storage address; + ::memcpy(&address, &packet->address, sizeof(sockaddr_storage)); + uint32_t addrLen = packet->addrLen; - // reallocate buffer and copy - cryptoBuffer = new uint8_t[cryptedLen]; - ::memset(cryptoBuffer, 0x00U, cryptedLen); - ::memcpy(cryptoBuffer, buffers.at(i)->buffer, length); - } + ::memcpy(iov_buffer, packet->buffer, packet->length); - // encrypt - uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); - delete[] cryptoBuffer; + // cleanup buffered packet + if (packet != nullptr) { + if (packet->buffer != nullptr) { + delete[] packet->buffer; + packet->length = 0; + } - if (crypted == nullptr) { - --size; - continue; - } + delete packet; + } - // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); + // are we crypto wrapped? + if (m_isCryptoWrapped && m_presharedKey != nullptr) { + uint32_t cryptedLen = length * sizeof(uint8_t); + uint8_t* cryptoBuffer = new uint8_t[iov_length]; + ::memcpy(cryptoBuffer, iov_buffer, iov_length); - // finalize - DECLARE_UINT8_ARRAY(out, cryptedLen + 2U); - ::memcpy(out + 2U, crypted, cryptedLen); - SET_UINT16(AES_WRAPPED_PCKT_MAGIC, out, 0U); + // do we need to pad the original buffer to be block aligned? + if (cryptedLen % crypto::AES::BLOCK_BYTES_LEN != 0) { + uint32_t alignment = crypto::AES::BLOCK_BYTES_LEN - (cryptedLen % crypto::AES::BLOCK_BYTES_LEN); + cryptedLen += alignment; + + // reallocate buffer and copy + cryptoBuffer = new uint8_t[cryptedLen]; + ::memset(cryptoBuffer, 0x00U, cryptedLen); + ::memcpy(cryptoBuffer, iov_buffer, length); + } - // cleanup buffers and replace with new - delete[] crypted; - //delete buffers[i]->buffer; + // encrypt + uint8_t* crypted = m_aes->encryptECB(cryptoBuffer, cryptedLen, m_presharedKey); + delete[] cryptoBuffer; - // this should never happen... - if (buffers[i] == nullptr) { - --size; - continue; + if (crypted == nullptr) { + if (iov_buffer != nullptr) { + delete[] iov_buffer; + iov_buffer = nullptr; } - - buffers[i]->buffer = new uint8_t[cryptedLen + 2U]; - ::memcpy(buffers[i]->buffer, out, cryptedLen + 2U); - buffers[i]->length = cryptedLen + 2U; + continue; } - chunks[i].iov_len = buffers.at(i)->length; - chunks[i].iov_base = buffers.at(i)->buffer; - sent += buffers.at(i)->length; + // Utils::dump(1U, "Socket::write(), crypted", crypted, cryptedLen); - headers[i].msg_hdr.msg_name = (void*)&buffers.at(i)->address; - headers[i].msg_hdr.msg_namelen = buffers.at(i)->addrLen; - headers[i].msg_hdr.msg_iov = &chunks[i]; - headers[i].msg_hdr.msg_iovlen = 1; - headers[i].msg_hdr.msg_control = 0; - headers[i].msg_hdr.msg_controllen = 0; - } - catch (...) { - --size; - } - } + // finalize + DECLARE_UINT8_ARRAY(out, cryptedLen + 2U); + ::memcpy(out + 2U, crypted, cryptedLen); + SET_UINT16(AES_WRAPPED_PCKT_MAGIC, out, 0U); - bool skip = false; - for (auto& buffer : buffers) { - if (buffer == nullptr) { - LogError(LOG_NET, "Socket::write() missing network buffer data? this isn't normal, aborting"); - skip = true; - break; + // cleanup buffers and replace with new + delete[] crypted; + crypted = nullptr; + delete[] iov_buffer; + iov_buffer = nullptr; + iov_buffer = new uint8_t[cryptedLen + 2U]; + ::memcpy(iov_buffer, out, cryptedLen + 2U); + iov_length = cryptedLen + 2U; } - if (m_af != buffer->address.ss_family) { - LogError(LOG_NET, "Socket::write() mismatched network address family? this isn't normal, aborting"); - skip = true; - break; + // skip if no IOV buffer + if (iov_buffer == nullptr) { + continue; } - } - if (skip) { - if (lenWritten != nullptr) { - *lenWritten = -1; - } + addresses[i] = new sockaddr_storage; + ::memcpy(addresses[i], &address, sizeof(sockaddr_storage)); - return false; + chunks[i].iov_len = iov_length; + chunks[i].iov_base = iov_buffer; + sent += iov_length; + + headers[i].msg_hdr.msg_name = (void*)addresses[i]; + headers[i].msg_hdr.msg_namelen = addrLen; + headers[i].msg_hdr.msg_iov = &chunks[i]; + headers[i].msg_hdr.msg_iovlen = 1; + headers[i].msg_hdr.msg_control = 0; + headers[i].msg_hdr.msg_controllen = 0; + + ++msgs; } - if (sendmmsg(m_fd, headers, size, 0) < 0) { + if (sendmmsg(m_fd, headers, msgs, 0) < 0) { #if defined(_WIN32) LogError(LOG_NET, "Error returned from sendmmsg, err: %lu", ::GetLastError()); #else @@ -594,6 +683,20 @@ bool Socket::write(BufferVector& buffers, ssize_t* lenWritten) noexcept } } + // cleanup buffers + for (size_t i = 0U; i < currentQueueSize; i++) { + if (addresses[i] != nullptr) { + delete addresses[i]; + addresses[i] = nullptr; + } + + if (chunks[i].iov_base != nullptr) { + uint8_t* iov_buffer = (uint8_t*)chunks[i].iov_base; + delete[] iov_buffer; + chunks[i].iov_base = nullptr; + } + } + return result; } diff --git a/src/common/network/udp/Socket.h b/src/common/network/udp/Socket.h index 739f8820d..b094c4bf5 100644 --- a/src/common/network/udp/Socket.h +++ b/src/common/network/udp/Socket.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2006-2016,2020 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -25,7 +25,7 @@ #include "common/AESCrypto.h" #include -#include +#include #if defined(_WIN32) #pragma comment(lib, "Ws2_32.lib") @@ -51,8 +51,8 @@ * @ingroup udp_socket */ enum IPMATCHTYPE { - IMT_ADDRESS_AND_PORT, //! Address and Port - IMT_ADDRESS_ONLY //! Address Only + IMT_ADDRESS_AND_PORT, //!< Address and Port + IMT_ADDRESS_ONLY //!< Address Only }; #if defined(_WIN32) @@ -160,15 +160,15 @@ namespace network * @ingroup udp_socket */ struct UDPDatagram { - uint8_t* buffer; //! Message Buffer - size_t length; //! Length of Message Buffer + uint8_t* buffer; //!< Message Buffer + size_t length; //!< Length of Message Buffer - sockaddr_storage address; //! Address and Port - uint32_t addrLen; //! Length of address structure + sockaddr_storage address; //!< Address and Port + uint32_t addrLen; //!< Length of address structure }; - /** @brief Vector of buffers that contain a full frames */ - typedef std::vector BufferVector; + /** @brief Queue of buffers that contain a UDP datagram. */ + typedef std::queue BufferQueue; // --------------------------------------------------------------------------- // Class Declaration @@ -222,6 +222,19 @@ namespace network */ bool open(const uint32_t af, const std::string& address, const uint16_t port) noexcept; + /** + * @brief Sets the socket receive buffer size. + * @param bufSize Buffer size to set. + * @returns bool True, if buffer size set, otherwise false. + */ + bool recvBufSize(ssize_t bufSize); + /** + * @brief Sets the socket send buffer size. + * @param bufSize Buffer size to set. + * @returns bool True, if buffer size set, otherwise false. + */ + bool sendBufSize(ssize_t bufSize); + /** * @brief Closes the UDP socket connection. */ @@ -248,11 +261,11 @@ namespace network virtual bool write(const uint8_t* buffer, uint32_t length, const sockaddr_storage& address, uint32_t addrLen, ssize_t* lenWritten = nullptr) noexcept; /** * @brief Write data to the UDP socket. - * @param[in] buffers Vector of buffers to write to socket. + * @param[in] buffers Queue of buffers to write to socket. * @param[out] lenWritten Total number of bytes written. * @returns bool True, if messages were sent otherwise, false. */ - virtual bool write(BufferVector& buffers, ssize_t* lenWritten = nullptr) noexcept; + virtual bool write(BufferQueue* buffers, ssize_t* lenWritten = nullptr) noexcept; /** * @brief Sets the preshared encryption key. diff --git a/src/common/network/viface/VIFace.cpp b/src/common/network/viface/VIFace.cpp index 530b2b133..69034d931 100644 --- a/src/common/network/viface/VIFace.cpp +++ b/src/common/network/viface/VIFace.cpp @@ -49,7 +49,7 @@ using namespace network::viface; // Static Class Members // --------------------------------------------------------------------------- -uint32_t VIFace::m_idSeq = 0U; +uint32_t VIFace::s_idSeq = 0U; // --------------------------------------------------------------------------- // Global Functions @@ -336,8 +336,8 @@ VIFace::VIFace(std::string name, bool tap, int id) : // set id if (id < 0) { - m_id = m_idSeq; - m_idSeq++; + m_id = s_idSeq; + s_idSeq++; } else { m_id = id; } diff --git a/src/common/network/viface/VIFace.h b/src/common/network/viface/VIFace.h index ff304465d..bfbef0fb6 100644 --- a/src/common/network/viface/VIFace.h +++ b/src/common/network/viface/VIFace.h @@ -39,8 +39,8 @@ namespace network */ struct viface_queues { - int rxFd; //! Receive Packet File Descriptor - int txFd; //! Transmit Packet File Descriptor + int rxFd; //!< Receive Packet File Descriptor + int txFd; //!< Transmit Packet File Descriptor }; // --------------------------------------------------------------------------- @@ -54,6 +54,10 @@ namespace network */ class HOST_SW_API VIFace { public: + auto operator=(VIFace&) -> VIFace& = delete; + auto operator=(VIFace&&) -> VIFace& = delete; + VIFace(VIFace&) = delete; + /** * @brief Initializes a new instance of the VIFace class. * @param name Name of the virtual interface. The placeholder %d can be used and a number will be assigned to it. @@ -233,7 +237,7 @@ namespace network uint32_t m_mtu; uint32_t m_id; - static uint32_t m_idSeq; + static uint32_t s_idSeq; /** * @brief Internal helper that performs a kernel IOCTL to get the IPv4 address by request. diff --git a/src/common/nxdn/NXDNDefines.h b/src/common/nxdn/NXDNDefines.h index a9e4c3632..255ebc675 100644 --- a/src/common/nxdn/NXDNDefines.h +++ b/src/common/nxdn/NXDNDefines.h @@ -133,10 +133,10 @@ namespace nxdn namespace RFChannelType { /** @brief Link Information Channel - RF Channel Type */ enum E : uint8_t { - RCCH = 0U, //! Control Channel - RTCH = 1U, //! Traffic Channel - RDCH = 2U, //! Data Channel - RTCH_C = 3U //! Composite Control/Traffic Channel + RCCH = 0U, //!< Control Channel + RTCH = 1U, //!< Traffic Channel + RDCH = 2U, //!< Data Channel + RTCH_C = 3U //!< Composite Control/Traffic Channel }; } @@ -145,15 +145,15 @@ namespace nxdn /** @brief Link Information Channel - Functional Channel Type */ enum E : uint8_t { // Common Access Channel - CAC_OUTBOUND = 0U, //! Common Access Channel - Outbound - CAC_INBOUND_LONG = 1U, //! Common Access Channel - Inbound Long - CAC_INBOUND_SHORT = 3U, //! Common Access Channel - Inbound Short + CAC_OUTBOUND = 0U, //!< Common Access Channel - Outbound + CAC_INBOUND_LONG = 1U, //!< Common Access Channel - Inbound Long + CAC_INBOUND_SHORT = 3U, //!< Common Access Channel - Inbound Short // Slow Associated Control Channel / User Specific Channel - USC_SACCH_NS = 0U, //! Slow Access Control Channel - Non-Superframe - USC_UDCH = 1U, //! - USC_SACCH_SS = 2U, //! Slow Access Control Channel - Superframe - USC_SACCH_SS_IDLE = 3U //! Slow Access Control Channel - Sueprframe/Idle + USC_SACCH_NS = 0U, //!< Slow Access Control Channel - Non-Superframe + USC_UDCH = 1U, //!< + USC_SACCH_SS = 2U, //!< Slow Access Control Channel - Superframe + USC_SACCH_SS_IDLE = 3U //!< Slow Access Control Channel - Sueprframe/Idle }; } @@ -161,14 +161,14 @@ namespace nxdn namespace ChOption { /** @brief Link Information Channel - Channel Options */ enum E : uint8_t { - DATA_NORMAL = 0U, //! Normal RCCH Data (Mandatory Data) - DATA_IDLE = 1U, //! Idle RCCH Data (Data that does not need to be Rx) - DATA_COMMON = 2U, //! Common RCCH Data (Optional Data) - - STEAL_NONE = 3U, //! No Stealing - STEAL_FACCH1_2 = 2U, //! 2 VCHs second half of FACCH1 - STEAL_FACCH1_1 = 1U, //! 2 VCHs first half of FACCH1 - STEAL_FACCH = 0U //! FACCH + DATA_NORMAL = 0U, //!< Normal RCCH Data (Mandatory Data) + DATA_IDLE = 1U, //!< Idle RCCH Data (Data that does not need to be Rx) + DATA_COMMON = 2U, //!< Common RCCH Data (Optional Data) + + STEAL_NONE = 3U, //!< No Stealing + STEAL_FACCH1_2 = 2U, //!< 2 VCHs second half of FACCH1 + STEAL_FACCH1_1 = 1U, //!< 2 VCHs first half of FACCH1 + STEAL_FACCH = 0U //!< FACCH }; } @@ -176,31 +176,31 @@ namespace nxdn namespace ChStructure { /** @brief Common Access Channel - Structure */ enum E : uint8_t { - SR_RCCH_SINGLE = 0x00U, //! Single Non-Header RCCH Message - SR_RCCH_DUAL = 0x01U, //! Dual Non-Header RCCH Message - SR_RCCH_HEAD_SINGLE = 0x02U, //! Single Header RCCH Message - SR_RCCH_HEAD_DUAL = 0x03U, //! Dual Header RCCH Message + SR_RCCH_SINGLE = 0x00U, //!< Single Non-Header RCCH Message + SR_RCCH_DUAL = 0x01U, //!< Dual Non-Header RCCH Message + SR_RCCH_HEAD_SINGLE = 0x02U, //!< Single Header RCCH Message + SR_RCCH_HEAD_DUAL = 0x03U, //!< Dual Header RCCH Message - SR_SINGLE = 0U, //! SACCH Single (same as SR_4_4, kept for clarity) + SR_SINGLE = 0U, //!< SACCH Single (same as SR_4_4, kept for clarity) - SR_4_4 = 0U, //! 4/4 SACCH Single/Last - SR_3_4 = 1U, //! 3/4 SACCH - SR_2_4 = 2U, //! 2/4 SACCH - SR_1_4 = 3U //! 1/4 SACCH Header + SR_4_4 = 0U, //!< 4/4 SACCH Single/Last + SR_3_4 = 1U, //!< 3/4 SACCH + SR_2_4 = 2U, //!< 2/4 SACCH + SR_1_4 = 3U //!< 1/4 SACCH Header }; } /** @name Encryption Algorithms */ - const uint8_t CIPHER_TYPE_NONE = 0x00U; //! Unencrypted + const uint8_t CIPHER_TYPE_NONE = 0x00U; //!< Unencrypted /** @} */ /** @brief Location Category */ namespace LocationCategory { /** @brief Location Category */ enum E : uint8_t { - GLOBAL = 0x00U, //! Global - LOCAL = 0x01U, //! Local - REGIONAL = 0x02U //! Regional + GLOBAL = 0x00U, //!< Global + LOCAL = 0x01U, //!< Local + REGIONAL = 0x02U //!< Regional }; } @@ -209,9 +209,9 @@ namespace nxdn /** @brief Data Response Class */ enum : uint8_t { - ACK = 0x00U, //! Acknowledge + ACK = 0x00U, //!< Acknowledge ACK_S = 0x01U, //! - NACK = 0x03U //! Negative Acknowledge + NACK = 0x03U //!< Negative Acknowledge }; } @@ -219,42 +219,42 @@ namespace nxdn namespace CauseResponse { /** @brief Cause Responses */ enum : uint8_t { - RSRC_NOT_AVAIL_NETWORK = 0x51U, //! Network Resource Not Available - RSRC_NOT_AVAIL_TEMP = 0x52U, //! Resource Temporarily Not Available - RSRC_NOT_AVAIL_QUEUED = 0x53U, //! Resource Queued Not Available - SVC_UNAVAILABLE = 0x60U, //! Service Unavailable - PROC_ERROR = 0x70U, //! Procedure Error - Lack of packet data - PROC_ERROR_UNDEF = 0x71U, //! Procedure Error - Invalid packet data - - MM_REG_ACCEPTED = 0x01U, //! Registration Accepted - MM_LOC_ACPT_GRP_FAIL = 0x04U, //! Location Accepted / Group Failed - MM_LOC_ACPT_GRP_REFUSE = 0x05U, //! Location Accepted / Group Refused - MM_REG_FAILED = 0x06U, //! Registration Failed - MM_REG_REFUSED = 0x08U, //! Registration Refused - - VD_ACCEPTED = 0x10U, //! Voice Accepted - VD_GRP_NOT_PERM = 0x11U, //! Voice Group Not Permitted - VD_REQ_UNIT_NOT_PERM = 0x12U, //! Requesting Unit Not Permitted - VD_TGT_UNIT_NOT_PERM = 0x13U, //! Target Unit Not Permitted - VD_REQ_UNIT_NOT_REG = 0x1CU, //! Requesting Unit Not Registered - VD_QUE_CHN_RESOURCE_NOT_AVAIL = 0x30U, //! Channel resources unavailable - VD_QUE_TGT_UNIT_BUSY = 0x38U, //! Target Unit Busy - VD_QUE_GRP_BUSY = 0x39U, //! Group Busy - - SS_ACK_R = 0x01U, //! Data Response - ACK Rx Success - SS_ACK_S = 0x02U, //! Data Response - ACK Tx Success - SS_NACK = 0x08U, //! Data Response - NACK (Request Full Retry) - SS_ACCEPTED = 0x10U, //! Data Accepted - SS_GRP_NOT_PERM = 0x11U, //! Data Group Not Permitted - SS_REQ_UNIT_NOT_PERM = 0x12U, //! Requesting Unit Not Permitted - SS_TGT_UNIT_NOT_PERM = 0x13U, //! Target Unit Not Permitted - SS_REQ_UNIT_NOT_REG = 0x1CU, //! Requesting Unit Not Registered - - DREQ_USER = 0x10U, //! Disconnect by User Request - DREQ_OTHER = 0x1FU, //! Other Disconnect Request - - DISC_USER = 0x10U, //! Disconnect by User - DISC_OTHER = 0x1FU //! Other Disconnect + RSRC_NOT_AVAIL_NETWORK = 0x51U, //!< Network Resource Not Available + RSRC_NOT_AVAIL_TEMP = 0x52U, //!< Resource Temporarily Not Available + RSRC_NOT_AVAIL_QUEUED = 0x53U, //!< Resource Queued Not Available + SVC_UNAVAILABLE = 0x60U, //!< Service Unavailable + PROC_ERROR = 0x70U, //!< Procedure Error - Lack of packet data + PROC_ERROR_UNDEF = 0x71U, //!< Procedure Error - Invalid packet data + + MM_REG_ACCEPTED = 0x01U, //!< Registration Accepted + MM_LOC_ACPT_GRP_FAIL = 0x04U, //!< Location Accepted / Group Failed + MM_LOC_ACPT_GRP_REFUSE = 0x05U, //!< Location Accepted / Group Refused + MM_REG_FAILED = 0x06U, //!< Registration Failed + MM_REG_REFUSED = 0x08U, //!< Registration Refused + + VD_ACCEPTED = 0x10U, //!< Voice Accepted + VD_GRP_NOT_PERM = 0x11U, //!< Voice Group Not Permitted + VD_REQ_UNIT_NOT_PERM = 0x12U, //!< Requesting Unit Not Permitted + VD_TGT_UNIT_NOT_PERM = 0x13U, //!< Target Unit Not Permitted + VD_REQ_UNIT_NOT_REG = 0x1CU, //!< Requesting Unit Not Registered + VD_QUE_CHN_RESOURCE_NOT_AVAIL = 0x30U, //!< Channel resources unavailable + VD_QUE_TGT_UNIT_BUSY = 0x38U, //!< Target Unit Busy + VD_QUE_GRP_BUSY = 0x39U, //!< Group Busy + + SS_ACK_R = 0x01U, //!< Data Response - ACK Rx Success + SS_ACK_S = 0x02U, //!< Data Response - ACK Tx Success + SS_NACK = 0x08U, //!< Data Response - NACK (Request Full Retry) + SS_ACCEPTED = 0x10U, //!< Data Accepted + SS_GRP_NOT_PERM = 0x11U, //!< Data Group Not Permitted + SS_REQ_UNIT_NOT_PERM = 0x12U, //!< Requesting Unit Not Permitted + SS_TGT_UNIT_NOT_PERM = 0x13U, //!< Target Unit Not Permitted + SS_REQ_UNIT_NOT_REG = 0x1CU, //!< Requesting Unit Not Registered + + DREQ_USER = 0x10U, //!< Disconnect by User Request + DREQ_OTHER = 0x1FU, //!< Other Disconnect Request + + DISC_USER = 0x10U, //!< Disconnect by User + DISC_OTHER = 0x1FU //!< Other Disconnect }; } @@ -262,14 +262,14 @@ namespace nxdn namespace SiteInformation1 { /** @brief Site Information 1 (SIF1) */ enum : uint8_t { - DATA_CALL_SVC = 0x01U, //! Data Call Service - VOICE_CALL_SVC = 0x02U, //! Voice Call Service - COMPOSITE_CONTROL = 0x04U, //! Composite Control Channel - AUTH_SVC = 0x08U, //! Authentication Service - GRP_REG_SVC = 0x10U, //! Group Registration Service - LOC_REG_SVC = 0x20U, //! Location Registration Service - MULTI_SYSTEM_SVC = 0x40U, //! Multi-System Service - MULTI_SITE_SVC = 0x80U //! Multi-Site Service + DATA_CALL_SVC = 0x01U, //!< Data Call Service + VOICE_CALL_SVC = 0x02U, //!< Voice Call Service + COMPOSITE_CONTROL = 0x04U, //!< Composite Control Channel + AUTH_SVC = 0x08U, //!< Authentication Service + GRP_REG_SVC = 0x10U, //!< Group Registration Service + LOC_REG_SVC = 0x20U, //!< Location Registration Service + MULTI_SYSTEM_SVC = 0x40U, //!< Multi-System Service + MULTI_SITE_SVC = 0x80U //!< Multi-Site Service }; } @@ -277,10 +277,10 @@ namespace nxdn namespace SiteInformation2 { /** @brief Site Information 2 (SIF2) */ enum : uint8_t { - IP_NETWORK = 0x10U, //! IP Networked - PSTN_NETWORK = 0x20U, //! PSTN Networked - STATUS_CALL_REM_CTRL = 0x40U, //! Status Call & Remote Control Service - SHORT_DATA_CALL_SVC = 0x80U //! Short Data Call Service + IP_NETWORK = 0x10U, //!< IP Networked + PSTN_NETWORK = 0x20U, //!< PSTN Networked + STATUS_CALL_REM_CTRL = 0x40U, //!< Status Call & Remote Control Service + SHORT_DATA_CALL_SVC = 0x80U //!< Short Data Call Service }; } @@ -288,10 +288,10 @@ namespace nxdn namespace RemoteControlCommand { /** @brief Remote Control Commands */ enum : uint8_t { - STUN = 0x00U, //! Stun - REVIVE = 0x01U, //! Revive - KILL = 0x02U, //! Kill - REMOTE_MONITOR = 0x04U //! Remote Monitor + STUN = 0x00U, //!< Stun + REVIVE = 0x01U, //!< Revive + KILL = 0x02U, //!< Kill + REMOTE_MONITOR = 0x04U //!< Remote Monitor }; } @@ -299,9 +299,9 @@ namespace nxdn namespace ChAccessStep { /** @brief Channel Access - Step */ enum E : uint8_t { - SYS_DEFINED = 0x00U, //! System Defined - ONEDOT25K = 0x02U, //! 1.25khz Step - THREEDOT125K = 0x03U //! 3.125khz Step + SYS_DEFINED = 0x00U, //!< System Defined + ONEDOT25K = 0x02U, //!< 1.25khz Step + THREEDOT125K = 0x03U //!< 3.125khz Step }; } @@ -309,11 +309,11 @@ namespace nxdn namespace ChAccessBase { /** @brief Channel Access - Base */ enum E : uint8_t { - FREQ_100 = 0x01U, //! 100mhz Base - FREQ_330 = 0x02U, //! 330mhz Base - FREQ_400 = 0x03U, //! 400mhz Base - FREQ_750 = 0x04U, //! 750mhz Base - FREQ_SYS_DEFINED = 0x07U //! System Defined + FREQ_100 = 0x01U, //!< 100mhz Base + FREQ_330 = 0x02U, //!< 330mhz Base + FREQ_400 = 0x03U, //!< 400mhz Base + FREQ_750 = 0x04U, //!< 750mhz Base + FREQ_SYS_DEFINED = 0x07U //!< System Defined }; } @@ -321,12 +321,12 @@ namespace nxdn namespace CallType { /** @brief Call Types */ enum : uint8_t { - BROADCAST = 0x00U, //! Broadcast - CONFERENCE = 0x01U, //! Conference - UNSPECIFIED = 0x02U, //! Unspecified - INDIVIDUAL = 0x04U, //! Individual - INTERCONNECT = 0x06U, //! Interconnect - SPEED_DIAL = 0x07U //! Speed Dial + BROADCAST = 0x00U, //!< Broadcast + CONFERENCE = 0x01U, //!< Conference + UNSPECIFIED = 0x02U, //!< Unspecified + INDIVIDUAL = 0x04U, //!< Individual + INTERCONNECT = 0x06U, //!< Interconnect + SPEED_DIAL = 0x07U //!< Speed Dial }; } @@ -334,9 +334,9 @@ namespace nxdn namespace TransmissionMode { /** @brief Transmission Mode */ enum : uint8_t { - MODE_4800 = 0x00U, //! 4800-baud - MODE_9600 = 0x02U, //! 9600-baud - MODE_9600_EFR = 0x03U //! 9600-baud; should never be used on data calls + MODE_4800 = 0x00U, //!< 4800-baud + MODE_9600 = 0x02U, //!< 9600-baud + MODE_9600_EFR = 0x03U //!< 9600-baud; should never be used on data calls }; } @@ -345,39 +345,39 @@ namespace nxdn /** @brief Message Types */ enum : uint8_t { // Common Message Types - IDLE = 0x10U, //! IDLE - Idle - DISC = 0x11U, //! DISC - Disconnect - DST_ID_INFO = 0x17U, //! DST_ID_INFO - Digital Station ID - SRV_INFO = 0x19U, //! SRV_INFO - Service Information - CCH_INFO = 0x1AU, //! CCH_INFO - Control Channel Information - ADJ_SITE_INFO = 0x1BU, //! ADJ_SITE_INFO - Adjacent Site Information - REM_CON_REQ = 0x34U, //! REM_CON_REQ - Remote Control Request - REM_CON_RESP = 0x35U, //! REM_CON_RESP - Remote Control Response + IDLE = 0x10U, //!< IDLE - Idle + DISC = 0x11U, //!< DISC - Disconnect + DST_ID_INFO = 0x17U, //!< DST_ID_INFO - Digital Station ID + SRV_INFO = 0x19U, //!< SRV_INFO - Service Information + CCH_INFO = 0x1AU, //!< CCH_INFO - Control Channel Information + ADJ_SITE_INFO = 0x1BU, //!< ADJ_SITE_INFO - Adjacent Site Information + REM_CON_REQ = 0x34U, //!< REM_CON_REQ - Remote Control Request + REM_CON_RESP = 0x35U, //!< REM_CON_RESP - Remote Control Response // Traffic Channel Message Types - RTCH_VCALL = 0x01U, //! VCALL - Voice Call - RTCH_VCALL_IV = 0x03U, //! VCALL_IV - Voice Call Initialization Vector - RTCH_TX_REL_EX = 0x07U, //! TX_REL_EX - Transmission Release Extension - RTCH_TX_REL = 0x08U, //! TX_REL - Transmission Release - RTCH_DCALL_HDR = 0x09U, //! DCALL - Data Call (Header) - RTCH_DCALL_DATA = 0x0BU, //! DCALL - Data Call (User Data Format) - RTCH_DCALL_ACK = 0x0CU, //! DCALL_ACK - Data Call Acknowledge - RTCH_HEAD_DLY = 0x0FU, //! HEAD_DLY - Header Delay - RTCH_SDCALL_REQ_HDR = 0x38U, //! SDCALL_REQ - Short Data Call Request (Header) - RTCH_SDCALL_REQ_DATA = 0x39U, //! SDCALL_REQ - Short Data Call Request (User Data Format) - RTCH_SDCALL_IV = 0x3AU, //! SDCALL_IV - Short Data Call Initialization Vector - RTCH_SDCALL_RESP = 0x3BU, //! SDCALL_RESP - Short Data Call Response + RTCH_VCALL = 0x01U, //!< VCALL - Voice Call + RTCH_VCALL_IV = 0x03U, //!< VCALL_IV - Voice Call Initialization Vector + RTCH_TX_REL_EX = 0x07U, //!< TX_REL_EX - Transmission Release Extension + RTCH_TX_REL = 0x08U, //!< TX_REL - Transmission Release + RTCH_DCALL_HDR = 0x09U, //!< DCALL - Data Call (Header) + RTCH_DCALL_DATA = 0x0BU, //!< DCALL - Data Call (User Data Format) + RTCH_DCALL_ACK = 0x0CU, //!< DCALL_ACK - Data Call Acknowledge + RTCH_HEAD_DLY = 0x0FU, //!< HEAD_DLY - Header Delay + RTCH_SDCALL_REQ_HDR = 0x38U, //!< SDCALL_REQ - Short Data Call Request (Header) + RTCH_SDCALL_REQ_DATA = 0x39U, //!< SDCALL_REQ - Short Data Call Request (User Data Format) + RTCH_SDCALL_IV = 0x3AU, //!< SDCALL_IV - Short Data Call Initialization Vector + RTCH_SDCALL_RESP = 0x3BU, //!< SDCALL_RESP - Short Data Call Response // Control Channel Message Types - RCCH_VCALL_CONN = 0x03U, //! VCALL_CONN - Voice Call Connection Request (ISP) / Voice Call Connection Response (OSP) - RCCH_VCALL_ASSGN = 0x04U, //! VCALL_ASSGN - Voice Call Assignment - RCCH_DCALL_ASSGN = 0x14U, //! DCALL_ASSGN - Data Call Assignment - RCCH_SITE_INFO = 0x18U, //! SITE_INFO - Site Information - RCCH_REG = 0x20U, //! REG - Registration Request (ISP) / Registration Response (OSP) - RCCH_REG_C = 0x22U, //! REG_C - Registration Clear Request (ISP) / Registration Clear Response (OSP) - RCCH_REG_COMM = 0x23U, //! REG_COMM - Registration Command - RCCH_GRP_REG = 0x24U, //! GRP_REG - Group Registration Request (ISP) / Group Registration Response (OSP) - RCCH_PROP_FORM = 0x3FU //! PROP_FORM - Proprietary Form + RCCH_VCALL_CONN = 0x03U, //!< VCALL_CONN - Voice Call Connection Request (ISP) / Voice Call Connection Response (OSP) + RCCH_VCALL_ASSGN = 0x04U, //!< VCALL_ASSGN - Voice Call Assignment + RCCH_DCALL_ASSGN = 0x14U, //!< DCALL_ASSGN - Data Call Assignment + RCCH_SITE_INFO = 0x18U, //!< SITE_INFO - Site Information + RCCH_REG = 0x20U, //!< REG - Registration Request (ISP) / Registration Response (OSP) + RCCH_REG_C = 0x22U, //!< REG_C - Registration Clear Request (ISP) / Registration Clear Response (OSP) + RCCH_REG_COMM = 0x23U, //!< REG_COMM - Registration Command + RCCH_GRP_REG = 0x24U, //!< GRP_REG - Group Registration Request (ISP) / Group Registration Response (OSP) + RCCH_PROP_FORM = 0x3FU //!< PROP_FORM - Proprietary Form }; } diff --git a/src/common/nxdn/acl/AccessControl.cpp b/src/common/nxdn/acl/AccessControl.cpp index 6eaccc71c..495441648 100644 --- a/src/common/nxdn/acl/AccessControl.cpp +++ b/src/common/nxdn/acl/AccessControl.cpp @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2016 Simon Rune, G7RZU * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL diff --git a/src/common/nxdn/lc/RCCH.cpp b/src/common/nxdn/lc/RCCH.cpp index b5a696711..38727e3be 100644 --- a/src/common/nxdn/lc/RCCH.cpp +++ b/src/common/nxdn/lc/RCCH.cpp @@ -22,10 +22,10 @@ using namespace nxdn::lc; // Static Class Members // --------------------------------------------------------------------------- -bool RCCH::m_verbose = false; +bool RCCH::s_verbose = false; -uint8_t* RCCH::m_siteCallsign = nullptr; -SiteData RCCH::m_siteData = SiteData(); +uint8_t* RCCH::s_siteCallsign = nullptr; +SiteData RCCH::s_siteData = SiteData(); // --------------------------------------------------------------------------- // Public Class Members @@ -51,9 +51,9 @@ RCCH::RCCH() : m_transmissionMode(TransmissionMode::MODE_4800), m_siteIdenEntry() { - if (m_siteCallsign == nullptr) { - m_siteCallsign = new uint8_t[CALLSIGN_LENGTH_BYTES]; - ::memset(m_siteCallsign, 0x00U, CALLSIGN_LENGTH_BYTES); + if (s_siteCallsign == nullptr) { + s_siteCallsign = new uint8_t[CALLSIGN_LENGTH_BYTES]; + ::memset(s_siteCallsign, 0x00U, CALLSIGN_LENGTH_BYTES); } } @@ -82,19 +82,19 @@ std::string RCCH::toString(bool isp) void RCCH::setCallsign(std::string callsign) { - if (m_siteCallsign == nullptr) { - m_siteCallsign = new uint8_t[CALLSIGN_LENGTH_BYTES]; - ::memset(m_siteCallsign, 0x00U, CALLSIGN_LENGTH_BYTES); + if (s_siteCallsign == nullptr) { + s_siteCallsign = new uint8_t[CALLSIGN_LENGTH_BYTES]; + ::memset(s_siteCallsign, 0x00U, CALLSIGN_LENGTH_BYTES); } uint32_t idLength = callsign.length(); if (idLength > 0) { - ::memset(m_siteCallsign, 0x20U, CALLSIGN_LENGTH_BYTES); + ::memset(s_siteCallsign, 0x20U, CALLSIGN_LENGTH_BYTES); if (idLength > CALLSIGN_LENGTH_BYTES) idLength = CALLSIGN_LENGTH_BYTES; for (uint32_t i = 0; i < idLength; i++) - m_siteCallsign[i] = callsign[i]; + s_siteCallsign[i] = callsign[i]; } } @@ -114,7 +114,7 @@ void RCCH::decode(const uint8_t* data, uint8_t* rcch, uint32_t length, uint32_t WRITE_BIT(rcch, i, b); } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "NXDN, RCCH::decode(), Decoded RCCH Data", rcch, NXDN_RCCH_LC_LENGTH_BYTES); } @@ -137,7 +137,7 @@ void RCCH::encode(uint8_t* data, const uint8_t* rcch, uint32_t length, uint32_t data[0U] = m_messageType & 0x3FU; // Message Type } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "NXDN, RCCH::encode(), Encoded RCCH Data", data, NXDN_RCCH_LC_LENGTH_BYTES); } } diff --git a/src/common/nxdn/lc/RCCH.h b/src/common/nxdn/lc/RCCH.h index c56cde628..b497397ae 100644 --- a/src/common/nxdn/lc/RCCH.h +++ b/src/common/nxdn/lc/RCCH.h @@ -71,14 +71,14 @@ namespace nxdn /** * @brief Gets the flag indicating verbose log output. - * @returns bool True, if the TSBK is verbose logging, otherwise false. + * @returns bool True, if the RCCH is verbose logging, otherwise false. */ - static bool getVerbose() { return m_verbose; } + static bool getVerbose() { return s_verbose; } /** * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. */ - static void setVerbose(bool verbose) { m_verbose = verbose; } + static void setVerbose(bool verbose) { s_verbose = verbose; } /** @name Local Site data */ /** @@ -91,12 +91,12 @@ namespace nxdn * @brief Gets the local site data. * @returns SiteData Currently set site data for the RCCH class. */ - static SiteData getSiteData() { return m_siteData; } + static SiteData getSiteData() { return s_siteData; } /** * @brief Sets the local site data. * @param siteData Site data to set for the RCCH class. */ - static void setSiteData(SiteData siteData) { m_siteData = siteData; } + static void setSiteData(SiteData siteData) { s_siteData = siteData; } /** @} */ public: @@ -183,11 +183,11 @@ namespace nxdn /** @} */ protected: - static bool m_verbose; + static bool s_verbose; // Local Site data - static uint8_t* m_siteCallsign; - static SiteData m_siteData; + static uint8_t* s_siteCallsign; + static SiteData s_siteData; /** * @brief Internal helper to decode a RCCH link control message. diff --git a/src/common/nxdn/lc/RTCH.cpp b/src/common/nxdn/lc/RTCH.cpp index a476dc62a..14486cc26 100644 --- a/src/common/nxdn/lc/RTCH.cpp +++ b/src/common/nxdn/lc/RTCH.cpp @@ -24,7 +24,7 @@ using namespace nxdn::lc; // Static Class Members // --------------------------------------------------------------------------- -bool RTCH::m_verbose = false; +bool RTCH::s_verbose = false; // --------------------------------------------------------------------------- // Public Class Members @@ -113,7 +113,7 @@ void RTCH::decode(const uint8_t* data, uint32_t length, uint32_t offset) WRITE_BIT(rtch, i, b); } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "NXDN, RTCH::decode(), Decoded RTCH Data", rtch, NXDN_RTCH_LC_LENGTH_BYTES); } @@ -136,7 +136,7 @@ void RTCH::encode(uint8_t* data, uint32_t length, uint32_t offset) WRITE_BIT(data, offset, b); } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "NXDN, RTCH::encode(), Encoded RTCH Data", data, length); } } diff --git a/src/common/nxdn/lc/RTCH.h b/src/common/nxdn/lc/RTCH.h index abfd87c11..84de99cd3 100644 --- a/src/common/nxdn/lc/RTCH.h +++ b/src/common/nxdn/lc/RTCH.h @@ -82,7 +82,7 @@ namespace nxdn * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. */ - static void setVerbose(bool verbose) { m_verbose = verbose; } + static void setVerbose(bool verbose) { s_verbose = verbose; } public: /** @name Common Data */ @@ -177,7 +177,7 @@ namespace nxdn DECLARE_PROPERTY(uint8_t, causeRsp, CauseResponse); private: - static bool m_verbose; + static bool s_verbose; // Encryption data uint8_t* m_mi; diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp index 21929537f..64075afcc 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DCALL_HDR.cpp @@ -69,8 +69,8 @@ void MESSAGE_TYPE_DCALL_HDR::encode(uint8_t* data, uint32_t length, uint32_t off rcch[6U] = (m_dstId >> 0U) & 0xFFU; // ... rcch[7U] = m_causeRsp; // Cause (VD) - rcch[9U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[10U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[9U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[10U] = (s_siteData.locId() >> 0) & 0xFFU; // ... RCCH::encode(data, rcch, length, offset); } diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp index f1d9ff545..6d48beee0 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_DST_ID_INFO.cpp @@ -50,9 +50,9 @@ void MESSAGE_TYPE_DST_ID_INFO::encode(uint8_t* data, uint32_t length, uint32_t o ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); rcch[1U] = 0xC0U + CALLSIGN_LENGTH_BYTES; // Station ID Option - Start / End / Character Count - rcch[2U] = (m_siteCallsign[0]); // Character 0 + rcch[2U] = (s_siteCallsign[0]); // Character 0 for (uint8_t i = 1; i < CALLSIGN_LENGTH_BYTES; i++) { - rcch[i + 2U] = m_siteCallsign[i]; // Character 1 - 7 + rcch[i + 2U] = s_siteCallsign[i]; // Character 1 - 7 } RCCH::encode(data, rcch, length, offset); diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp index 628491077..46e390ad8 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_GRP_REG.cpp @@ -58,8 +58,8 @@ void MESSAGE_TYPE_GRP_REG::encode(uint8_t* data, uint32_t length, uint32_t offse rcch[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address rcch[5U] = (m_dstId >> 0U) & 0xFFU; // ... rcch[6U] = m_causeRsp; // Cause (MM) - rcch[8U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[9U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[8U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[9U] = (s_siteData.locId() >> 0) & 0xFFU; // ... RCCH::encode(data, rcch, length, offset); } diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp index 3b3002f90..f5c5f23d0 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG.cpp @@ -60,9 +60,9 @@ void MESSAGE_TYPE_REG::encode(uint8_t* data, uint32_t length, uint32_t offset) ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); rcch[1U] = (m_regOption << 3) + // Registration Option - ((m_siteData.locId() >> 22U) & 0x03U); // Location ID + ((s_siteData.locId() >> 22U) & 0x03U); // Location ID - uint16_t systemCode = (m_siteData.locId() >> 12U) << 7U; + uint16_t systemCode = (s_siteData.locId() >> 12U) << 7U; rcch[2U] = (systemCode >> 8U) & 0x03U; // ... rcch[3U] = systemCode & 0xFFU; // ... diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp index d90eeffcb..8da27c816 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_C.cpp @@ -53,8 +53,8 @@ void MESSAGE_TYPE_REG_C::encode(uint8_t* data, uint32_t length, uint32_t offset) uint8_t rcch[NXDN_RCCH_LC_LENGTH_BYTES + 4U]; ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); - rcch[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[2U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[3U] = (s_siteData.locId() >> 0) & 0xFFU; // ... rcch[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address rcch[5U] = (m_dstId >> 0U) & 0xFFU; // ... rcch[6U] = m_causeRsp; // Cause (MM) diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp index e116b7b5d..f611e552c 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_REG_COMM.cpp @@ -49,8 +49,8 @@ void MESSAGE_TYPE_REG_COMM::encode(uint8_t* data, uint32_t length, uint32_t offs uint8_t rcch[NXDN_RCCH_LC_LENGTH_BYTES + 4U]; ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); - rcch[2U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[2U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[3U] = (s_siteData.locId() >> 0) & 0xFFU; // ... rcch[4U] = (m_dstId >> 8U) & 0xFFU; // Target Radio Address rcch[5U] = (m_dstId >> 0U) & 0xFFU; // ... diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp index 82bc54f1c..ea9ddfbb4 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SITE_INFO.cpp @@ -51,16 +51,16 @@ void MESSAGE_TYPE_SITE_INFO::encode(uint8_t* data, uint32_t length, uint32_t off { assert(data != nullptr); - uint8_t siteInfo2 = m_siteData.siteInfo2(); + uint8_t siteInfo2 = s_siteData.siteInfo2(); if ((siteInfo2 & SiteInformation2::IP_NETWORK) == SiteInformation2::IP_NETWORK) siteInfo2 &= ~SiteInformation2::IP_NETWORK; // clear the IP_NETWORK bit -- that will be provided by netActive() uint8_t rcch[NXDN_RCCH_LC_LENGTH_BYTES + 4U]; ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); - rcch[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID - rcch[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ... - rcch[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[1U] = (s_siteData.locId() >> 16) & 0xFFU; // Location ID + rcch[2U] = (s_siteData.locId() >> 8) & 0xFFU; // ... + rcch[3U] = (s_siteData.locId() >> 0) & 0xFFU; // ... rcch[4U] = ((m_bcchCnt & 0x03U) << 6) + // Channel Structure - Number of BCCH ((m_rcchGroupingCnt & 0x07U) << 3) + // ... - Number of Grouping (((m_ccchPagingCnt >> 1) & 0x07U) << 0); // ... - Number of Paging Frames @@ -68,8 +68,8 @@ void MESSAGE_TYPE_SITE_INFO::encode(uint8_t* data, uint32_t length, uint32_t off ((m_ccchMultiCnt & 0x07U) << 4) + // ... - Number of Multipurpose Frames ((m_rcchIterateCnt & 0x0FU) << 0); // ... - Number of Iteration - rcch[6U] = m_siteData.siteInfo1(); // Site Information 1 - rcch[7U] = (m_siteData.netActive() ? SiteInformation2::IP_NETWORK : 0x00U) + // Site Information 2 + rcch[6U] = s_siteData.siteInfo1(); // Site Information 1 + rcch[7U] = (s_siteData.netActive() ? SiteInformation2::IP_NETWORK : 0x00U) + // Site Information 2 siteInfo2; // bryanb: this is currently fixed -- maybe dynamic in the future @@ -84,7 +84,7 @@ void MESSAGE_TYPE_SITE_INFO::encode(uint8_t* data, uint32_t length, uint32_t off rcch[14U] = 1U; // Version - uint16_t channelNo = m_siteData.channelNo() & 0x3FFU; + uint16_t channelNo = s_siteData.channelNo() & 0x3FFU; rcch[15U] = (channelNo >> 6) & 0x0FU; // 1st Control Channel rcch[16U] = (channelNo & 0x3FU) << 2; // ... diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp index f5ab9db0b..931cb84d6 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_SRV_INFO.cpp @@ -46,24 +46,24 @@ void MESSAGE_TYPE_SRV_INFO::encode(uint8_t* data, uint32_t length, uint32_t offs { assert(data != nullptr); - uint8_t siteInfo2 = m_siteData.siteInfo2(); + uint8_t siteInfo2 = s_siteData.siteInfo2(); if ((siteInfo2 & SiteInformation2::IP_NETWORK) == SiteInformation2::IP_NETWORK) siteInfo2 &= ~SiteInformation2::IP_NETWORK; // clear the IP_NETWORK bit -- that will be provided by netActive() uint8_t rcch[NXDN_RCCH_LC_LENGTH_BYTES + 4U]; ::memset(rcch, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES + 4U); - rcch[1U] = (m_siteData.locId() >> 16) & 0xFFU; // Location ID - rcch[2U] = (m_siteData.locId() >> 8) & 0xFFU; // ... - rcch[3U] = (m_siteData.locId() >> 0) & 0xFFU; // ... - rcch[4U] = m_siteData.siteInfo1(); // Site Information 1 - rcch[5U] = (m_siteData.netActive() ? SiteInformation2::IP_NETWORK : 0x00U) + // Site Information 2 + rcch[1U] = (s_siteData.locId() >> 16) & 0xFFU; // Location ID + rcch[2U] = (s_siteData.locId() >> 8) & 0xFFU; // ... + rcch[3U] = (s_siteData.locId() >> 0) & 0xFFU; // ... + rcch[4U] = s_siteData.siteInfo1(); // Site Information 1 + rcch[5U] = (s_siteData.netActive() ? SiteInformation2::IP_NETWORK : 0x00U) + // Site Information 2 siteInfo2; // bryanb: this is currently fixed -- maybe dynamic in the future rcch[8U] = 0U; // Restriction Information - No access restriction / No cycle restriction rcch[9U] = 0U; // ... - No group restriction / No Location Registration Restriction - rcch[10U] = (!m_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO + rcch[10U] = (!s_siteData.netActive() ? 0x01U : 0x00U); // ... - No group ratio restriction / No delay time extension / ISO RCCH::encode(data, rcch, length, offset); } diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp index b11c352d7..005eda64b 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_ASSGN.cpp @@ -63,8 +63,8 @@ void MESSAGE_TYPE_VCALL_ASSGN::encode(uint8_t* data, uint32_t length, uint32_t o rcch[7U] = (m_grpVchNo >> 10) & 0x03U; // Channel rcch[8U] = (m_grpVchNo & 0xFFU); // ... - rcch[10U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[11U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[10U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[11U] = (s_siteData.locId() >> 0) & 0xFFU; // ... RCCH::encode(data, rcch, length, offset); } diff --git a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp index 7ec7b7a61..e6200af4e 100644 --- a/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp +++ b/src/common/nxdn/lc/rcch/MESSAGE_TYPE_VCALL_CONN.cpp @@ -69,8 +69,8 @@ void MESSAGE_TYPE_VCALL_CONN::encode(uint8_t* data, uint32_t length, uint32_t of rcch[6U] = (m_dstId >> 0U) & 0xFFU; // ... rcch[7U] = m_causeRsp; // Cause (VD) - rcch[9U] = (m_siteData.locId() >> 8) & 0xFFU; // Location ID - rcch[10U] = (m_siteData.locId() >> 0) & 0xFFU; // ... + rcch[9U] = (s_siteData.locId() >> 8) & 0xFFU; // Location ID + rcch[10U] = (s_siteData.locId() >> 0) & 0xFFU; // ... RCCH::encode(data, rcch, length, offset); } diff --git a/src/common/p25/Crypto.cpp b/src/common/p25/Crypto.cpp index 5cb1608a2..825f3a153 100644 --- a/src/common/p25/Crypto.cpp +++ b/src/common/p25/Crypto.cpp @@ -12,10 +12,18 @@ #include "p25/P25Defines.h" #include "p25/Crypto.h" #include "AESCrypto.h" +#include "DESCrypto.h" #include "RC4Crypto.h" #include "Log.h" #include "Utils.h" +#if defined(ENABLE_SSL) +#include +#include +#include +#include +#endif // ENABLE_SSL + using namespace ::crypto; using namespace p25; using namespace p25::defines; @@ -27,6 +35,7 @@ using namespace p25::crypto; // Constants // --------------------------------------------------------------------------- +#define TEMP_BUFFER_LEN 1024U #define MAX_ENC_KEY_LENGTH_BYTES 32U // --------------------------------------------------------------------------- @@ -129,6 +138,34 @@ void P25Crypto::generateKeystream() // generate keystream switch (m_tekAlgoId) { + case ALGO_DES: + { + if (m_keystream == nullptr) + m_keystream = new uint8_t[224U]; + ::memset(m_keystream, 0x00U, 224U); + + uint8_t desKey[8U]; + ::memset(desKey, 0x00U, 8U); + uint8_t padLen = (uint8_t)::fmax(8 - m_tekLength, 0); + for (uint8_t i = 0U; i < padLen; i++) + desKey[i] = 0U; + for (uint8_t i = padLen; i < 8U; i++) + desKey[i] = m_tek[i - padLen]; + + DES des = DES(); + + uint8_t input[8U]; + ::memset(input, 0x00U, 8U); + ::memcpy(input, m_mi, 8U); + + for (uint32_t i = 0U; i < (224U / 8U); i++) { + uint8_t* output = des.encryptBlock(input, desKey); + ::memcpy(m_keystream + (i * 8U), output, 8U); + ::memcpy(input, output, 8U); + delete[] output; + } + } + break; case ALGO_AES_256: { if (m_keystream == nullptr) @@ -147,6 +184,7 @@ void P25Crypto::generateKeystream() uint8_t* output = aes.encryptECB(input, 16U, m_tek.get()); ::memcpy(m_keystream + (i * 16U), output, 16U); ::memcpy(input, output, 16U); + delete[] output; } delete[] iv; @@ -195,6 +233,450 @@ void P25Crypto::resetKeystream() } } +/* Helper to crypt a P25 TEK with the given AES-256 KEK. */ + +UInt8Array P25Crypto::cryptAES_TEK(const uint8_t* kek, uint8_t* tek, uint8_t tekLen) +{ +#if defined(ENABLE_SSL) + // static IV with $A6 pattern defined in TIA-102.AACA-C-2023 13.3 + uint8_t iv[AES::BLOCK_BYTES_LEN / 2] = { + 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U + }; + + int len; + uint8_t tempBuf[TEMP_BUFFER_LEN]; + ::memset(tempBuf, 0x00U, TEMP_BUFFER_LEN); + + ERR_load_crypto_strings(); + + EVP_CIPHER_CTX* ctx; + + // create and initialize a cipher context + if (!(ctx = EVP_CIPHER_CTX_new())) { + LogError(LOG_P25, "EVP_CIPHER_CTX_new(), failed to initialize cipher context: %s", ERR_error_string(ERR_get_error(), NULL)); + return nullptr; + } + + // initialize the wrapper context with AES-256-WRAP + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, iv) != 1) { + LogError(LOG_P25, "EVP_EncryptInit_ex(), failed to initialize cipher wrapping context: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // perform the wrapping operation + if (EVP_EncryptUpdate(ctx, tempBuf, &len, tek, tekLen) != 1) { + LogError(LOG_P25, "EVP_EncryptUpdate(), failed to wrap TEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // finalize the wrapping (no output, just padding) + int tempLen; + if (EVP_EncryptFinal_ex(ctx, tempBuf + len, &tempLen) != 1) { + LogError(LOG_P25, "EVP_EncryptFinal_ex(), failed to finalize wrapping TEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + len += tempLen; + + EVP_CIPHER_CTX_free(ctx); + + UInt8Array wrappedKey = std::unique_ptr(new uint8_t[len]); + ::memset(wrappedKey.get(), 0x00U, len); + ::memcpy(wrappedKey.get(), tempBuf, len); + + return wrappedKey; +#else + LogError(LOG_P25, "No OpenSSL, TEK encryption is not supported!"); + return nullptr; +#endif // ENABLE_SSL +} + +/* Helper to decrypt a P25 TEK with the given AES-256 KEK. */ + +UInt8Array P25Crypto::decryptAES_TEK(const uint8_t* kek, uint8_t* tek, uint8_t tekLen) +{ +#if defined(ENABLE_SSL) + // static IV with $A6 pattern defined in TIA-102.AACA-C-2023 13.3 + uint8_t iv[AES::BLOCK_BYTES_LEN / 2] = { + 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U, 0xA6U + }; + + int len; + uint8_t tempBuf[TEMP_BUFFER_LEN]; + ::memset(tempBuf, 0x00U, TEMP_BUFFER_LEN); + + ERR_load_crypto_strings(); + + EVP_CIPHER_CTX* ctx; + + // create and initialize a cipher context + if (!(ctx = EVP_CIPHER_CTX_new())) { + LogError(LOG_P25, "EVP_CIPHER_CTX_new(), failed to initialize cipher context: %s", ERR_error_string(ERR_get_error(), NULL)); + return nullptr; + } + + // initialize the wrapper context with AES-256-WRAP + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, iv) != 1) { + LogError(LOG_P25, "EVP_DecryptInit_ex(), failed to initialize cipher wrapping context: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // perform the wrapping operation + if (EVP_DecryptUpdate(ctx, tempBuf, &len, tek, tekLen) != 1) { + LogError(LOG_P25, "EVP_DecryptUpdate(), failed to unwrap TEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // finalize the wrapping (no output, just padding) + int tempLen; + if (EVP_DecryptFinal_ex(ctx, tempBuf + len, &tempLen) != 1) { + LogError(LOG_P25, "EVP_DecryptFinal_ex(), failed to finalize unwrapping TEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + len += tempLen; + + EVP_CIPHER_CTX_free(ctx); + + UInt8Array unwrappedKey = std::unique_ptr(new uint8_t[len]); + ::memset(unwrappedKey.get(), 0x00U, len); + ::memcpy(unwrappedKey.get(), tempBuf, len); + + return unwrappedKey; +#else + LogError(LOG_P25, "No OpenSSL, TEK encryption is not supported!"); + return nullptr; +#endif // ENABLE_SSL +} + +/* Helper to generate a P25 KMM CBC MAC key with the given AES-256 KEK. */ + +UInt8Array P25Crypto::cryptAES_KMM_CBC_KDF(const uint8_t* kek, const uint8_t* msg, uint16_t msgLen) +{ +#if defined(ENABLE_SSL) + + /* + ** bryanb: some bizarre bullshit requiring a 8-byte IV -- thanks Ilya (https://github.com/ilyacodes) for helping look at this + */ + + uint8_t iv[AES::BLOCK_BYTES_LEN / 2] = { + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U + }; + + uint16_t authLen = msgLen - KMM_AES_MAC_LENGTH; + SET_UINT16(authLen, iv, 6U); + + int len; + uint8_t tempBuf[TEMP_BUFFER_LEN]; + ::memset(tempBuf, 0x00U, TEMP_BUFFER_LEN); + + ERR_load_crypto_strings(); + + EVP_CIPHER_CTX* ctx; + + // create and initialize a cipher context + if (!(ctx = EVP_CIPHER_CTX_new())) { + LogError(LOG_P25, "EVP_CIPHER_CTX_new(), failed to initialize cipher context: %s", ERR_error_string(ERR_get_error(), NULL)); + return nullptr; + } + + // initialize the wrapper context with AES-256-WRAP + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_wrap(), NULL, kek, iv) != 1) { + LogError(LOG_P25, "EVP_EncryptInit_ex(), failed to initialize cipher wrapping context: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // perform the wrapping operation + if (EVP_EncryptUpdate(ctx, tempBuf, &len, kek, MAX_ENC_KEY_LENGTH_BYTES) != 1) { + LogError(LOG_P25, "EVP_EncryptUpdate(), failed to wrap KEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + + // finalize the wrapping (no output, just padding) + int tempLen; + if (EVP_EncryptFinal_ex(ctx, tempBuf + len, &tempLen) != 1) { + LogError(LOG_P25, "EVP_EncryptFinal_ex(), failed to finalize wrapping KEK: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_CIPHER_CTX_free(ctx); + return nullptr; + } + len += tempLen; + + EVP_CIPHER_CTX_free(ctx); + + UInt8Array wrappedKey = std::unique_ptr(new uint8_t[MAX_ENC_KEY_LENGTH_BYTES]); + ::memset(wrappedKey.get(), 0x00U, MAX_ENC_KEY_LENGTH_BYTES); + ::memcpy(wrappedKey.get(), tempBuf + 8U, MAX_ENC_KEY_LENGTH_BYTES); + + return wrappedKey; +#else + LogError(LOG_P25, "No OpenSSL, CBC-MAC generation is not supported!"); + return nullptr; +#endif // ENABLE_SSL +} + +/* Helper to generate a P25 KMM CBC-MAC with the given AES-256 CBC-MAC key. */ + +UInt8Array P25Crypto::cryptAES_KMM_CBC(const uint8_t* macKey, const uint8_t* msg, uint16_t msgLen) +{ + uint8_t iv[AES::BLOCK_BYTES_LEN] = { + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U + }; + + AES aes = AES(AESKeyLength::AES_256); + + // pad the message as necessary + size_t paddedLen = msgLen + (AES::BLOCK_BYTES_LEN - (msgLen % AES::BLOCK_BYTES_LEN)); + uint8_t paddedMessage[TEMP_BUFFER_LEN]; + ::memset(paddedMessage, 0x00U, TEMP_BUFFER_LEN); + + ::memcpy(paddedMessage, msg, msgLen - KMM_AES_MAC_LENGTH - 5U); + ::memcpy(paddedMessage + msgLen - KMM_AES_MAC_LENGTH - 5U, msg + msgLen - 5U, 5U); + + // perform AES-CBC encryption + uint8_t* tempBuf = aes.encryptCBC(paddedMessage, paddedLen, macKey, iv); + + UInt8Array wrappedKey = std::unique_ptr(new uint8_t[8U]); + ::memset(wrappedKey.get(), 0x00U, 8U); + ::memcpy(wrappedKey.get(), tempBuf + (msgLen - AES::BLOCK_BYTES_LEN), 8U); + + delete[] tempBuf; + return wrappedKey; +} + +/* Helper to generate a P25 KMM CMAC MAC key with the given AES-256 KEK. */ + +UInt8Array P25Crypto::cryptAES_KMM_CMAC_KDF(const uint8_t* kek, const uint8_t* msg, uint16_t msgLen, bool hasMN) +{ +#if defined(ENABLE_SSL) + // O T A R M A C + uint8_t label[8U] = { 0x4FU, 0x54U, 0x41U, 0x52U, 0x20U, 0x4DU, 0x41U, 0x43U }; + + uint8_t context[12U]; + ::memset(context, 0x00U, 12U); + + uint8_t contextLen = 0U; + if (hasMN) { + ::memcpy(context, msg, 12U); + contextLen = 12U; + } else { + ::memcpy(context, msg, 10U); + contextLen = 10U; + } + + /** DEBUG REMOVEME */ + Utils::dump(2U, "KEK", kek, MAX_ENC_KEY_LENGTH_BYTES); + Utils::dump(2U, "Label", label, 8U); + Utils::dump(2U, "Context", context, contextLen); + /** DEBUG REMOVEME */ + + size_t len; + uint8_t tempBuf[TEMP_BUFFER_LEN]; + ::memset(tempBuf, 0x00U, TEMP_BUFFER_LEN); + + ERR_load_crypto_strings(); + + // create a library context (required for OpenSSL 3.0+) + OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); + if (!libCtx) { + LogError(LOG_P25, "OSSL_LIB_CTX_new(), failed to finalize OpenSSL: %s", ERR_error_string(ERR_get_error(), NULL)); + return nullptr; + } + + // load the KDF algorithm + EVP_KDF* kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL); + if (!kdf) { + LogError(LOG_P25, "EVP_KDF_fetch(), failed to load OpenSSL KDF algorithm: %s", ERR_error_string(ERR_get_error(), NULL)); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // create a context for the MAC operation + EVP_KDF_CTX* ctx = EVP_KDF_CTX_new(kdf); + if (!ctx) { + LogError(LOG_P25, "EVP_KDF_CTX_new(), failed to create a OpenSSL KDF context: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_KDF_free(kdf); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // set the cipher to AES-256-CBC and initialize the MAC operation + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MAC, "HMAC", 0), + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, "SHA-256", 0), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void*)kek, MAX_ENC_KEY_LENGTH_BYTES), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void*)label, 8U), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void*)context, contextLen), + OSSL_PARAM_END + }; + + // derive MAC key + if (EVP_KDF_derive(ctx, tempBuf, MAX_ENC_KEY_LENGTH_BYTES, params) <= 0) { + LogError(LOG_P25, "EVP_KDF_derive(), failed to derive MAC key: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(kdf); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + EVP_KDF_CTX_free(ctx); + EVP_KDF_free(kdf); + OSSL_LIB_CTX_free(libCtx); + + /** DEBUG REMOVEME */ + Utils::dump(2U, "tempBuf", tempBuf, 128U); + /** DEBUG REMOVEME */ + + UInt8Array wrappedKey = std::unique_ptr(new uint8_t[MAX_ENC_KEY_LENGTH_BYTES]); + ::memset(wrappedKey.get(), 0x00U, MAX_ENC_KEY_LENGTH_BYTES); + ::memcpy(wrappedKey.get(), tempBuf, MAX_ENC_KEY_LENGTH_BYTES); + + return wrappedKey; +#else + LogError(LOG_P25, "No OpenSSL, CMAC generation is not supported!"); + return nullptr; +#endif // ENABLE_SSL +} + +/* Helper to generate a P25 KMM CMAC with the given AES-256 CMAC key. */ + +UInt8Array P25Crypto::cryptAES_KMM_CMAC(const uint8_t* macKey, const uint8_t* msg, uint16_t msgLen) +{ +#if defined(ENABLE_SSL) + size_t len; + uint8_t tempBuf[TEMP_BUFFER_LEN]; + ::memset(tempBuf, 0x00U, TEMP_BUFFER_LEN); + + uint8_t paddedMessage[TEMP_BUFFER_LEN]; + ::memset(paddedMessage, 0x00U, TEMP_BUFFER_LEN); + + ::memcpy(paddedMessage, msg, msgLen - KMM_AES_MAC_LENGTH - 5U); + ::memcpy(paddedMessage + msgLen - KMM_AES_MAC_LENGTH - 5U, msg + msgLen - 5U, 5U); + + // create a library context (required for OpenSSL 3.0+) + OSSL_LIB_CTX* libCtx = OSSL_LIB_CTX_new(); + if (!libCtx) { + LogError(LOG_P25, "OSSL_LIB_CTX_new(), failed to finalize OpenSSL: %s", ERR_error_string(ERR_get_error(), NULL)); + return nullptr; + } + + // fetch the CMAC implementation + EVP_MAC* hmac = EVP_MAC_fetch(libCtx, "CMAC", NULL); + if (!hmac) { + LogError(LOG_P25, "EVP_MAC_fetch(), failed to fetch OpenSSL CMAC: %s", ERR_error_string(ERR_get_error(), NULL)); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // create a context for the MAC operation + EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(hmac); + if (!ctx) { + LogError(LOG_P25, "EVP_MAC_CTX_new(), failed to create a OpenSSL CMAC context: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // set the cipher to AES-256-CBC and initialize the MAC operation + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, "AES-256-CBC", 0), + OSSL_PARAM_END + }; + + // initialize the MAC operation + if (!EVP_MAC_init(ctx, macKey, MAX_ENC_KEY_LENGTH_BYTES, params)) { + LogError(LOG_P25, "EVP_MAC_init(), failed to initialize the AES-256-CBC MAC operation: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // provide the message data to be authenticated + if (!EVP_MAC_update(ctx, paddedMessage, msgLen - KMM_AES_MAC_LENGTH)) { + LogError(LOG_P25, "EVP_MAC_update(), failed to set message data to authenticate: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // get the length of the MAC + if (!EVP_MAC_final(ctx, NULL, &len, 0)) { + LogError(LOG_P25, "EVP_MAC_final(), failed to get MAC length: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + // generate the MAC + if (!EVP_MAC_final(ctx, tempBuf, &len, len)) { + LogError(LOG_P25, "EVP_MAC_final(), failed to get MAC length: %s", ERR_error_string(ERR_get_error(), NULL)); + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + return nullptr; + } + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); + OSSL_LIB_CTX_free(libCtx); + + UInt8Array wrappedKey = std::unique_ptr(new uint8_t[len]); + ::memset(wrappedKey.get(), 0x00U, len); + ::memcpy(wrappedKey.get(), tempBuf, len); + + return wrappedKey; +#else + LogError(LOG_P25, "No OpenSSL, CMAC generation is not supported!"); + return nullptr; +#endif // ENABLE_SSL +} + +/* Helper to crypt a P25 PDU frame using AES-256. */ + +void P25Crypto::cryptAES_PDU(uint8_t* frame, uint8_t frameLen) +{ + if (m_keystream == nullptr) + return; + + uint32_t offset = 16U; + for (uint8_t i = 0U; i < frameLen; i++) { + if (offset > 240U) { + offset = 16U; + } + + frame[i] ^= m_keystream[offset]; + offset++; + } +} + +/* Helper to crypt IMBE audio using DES. */ + +void P25Crypto::cryptDES_IMBE(uint8_t* imbe, DUID::E duid) +{ + if (m_keystream == nullptr) + return; + + uint32_t offset = 8U; + if (duid == DUID::LDU2) { + offset += 101U; + } + + offset += (m_keystreamPos * RAW_IMBE_LENGTH_BYTES) + RAW_IMBE_LENGTH_BYTES + ((m_keystreamPos < 8U) ? 0U : 2U); + m_keystreamPos = (m_keystreamPos + 1U) % 9U; + + for (uint8_t i = 0U; i < RAW_IMBE_LENGTH_BYTES; i++) { + imbe[i] ^= m_keystream[offset + i]; + } +} + /* Helper to crypt IMBE audio using AES-256. */ void P25Crypto::cryptAES_IMBE(uint8_t* imbe, DUID::E duid) diff --git a/src/common/p25/Crypto.h b/src/common/p25/Crypto.h index 7ced74ae0..b267adacc 100644 --- a/src/common/p25/Crypto.h +++ b/src/common/p25/Crypto.h @@ -73,6 +73,76 @@ namespace p25 */ void resetKeystream(); + /** + * @brief Helper to crypt a P25 TEK with the given AES-256 KEK. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param kek Key Encryption Key + * @param tek Traffic Encryption Key + * @param tekLen Traffic Encryption Key Length + * @returns UInt8Array Buffer containing the encryption wrapped TEK. + */ + UInt8Array cryptAES_TEK(const uint8_t* kek, uint8_t* tek, uint8_t tekLen); + /** + * @brief Helper to decrypt a P25 TEK with the given AES-256 KEK. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param kek Key Encryption Key + * @param tek Wrapped Traffic Encryption Key + * @param tekLen Wrapped Traffic Encryption Key Length + * @returns UInt8Array Buffer containing the decrypted TEK. + */ + UInt8Array decryptAES_TEK(const uint8_t* kek, uint8_t* tek, uint8_t tekLen); + /** + * @brief Helper to generate a P25 KMM CBC MAC key with the given AES-256 KEK. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param kek Key Encryption Key + * @param msg KMM Message + * @param msgLen Message Length + * @returns UInt8Array Buffer containing the derived MAC key. + */ + UInt8Array cryptAES_KMM_CBC_KDF(const uint8_t* kek, const uint8_t* msg, uint16_t msgLen); + /** + * @brief Helper to generate a P25 KMM CBC-MAC with the given AES-256 CBC-MAC key. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param macKey CBC-MAC Derived Key + * @param msg KMM Message + * @param msgLen Message Length + * @returns UInt8Array Buffer containing the derived MAC key. + */ + UInt8Array cryptAES_KMM_CBC(const uint8_t* macKey, const uint8_t* msg, uint16_t msgLen); + + /** + * @brief Helper to generate a P25 KMM CMAC MAC key with the given AES-256 KEK. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param kek Key Encryption Key + * @param msg KMM Message + * @param msgLen Message Length + * @param hasMN Message has a message number. + * @returns UInt8Array Buffer containing the derived MAC key. + */ + UInt8Array cryptAES_KMM_CMAC_KDF(const uint8_t* kek, const uint8_t* msg, uint16_t msgLen, bool hasMN); + /** + * @brief Helper to generate a P25 KMM CMAC with the given AES-256 CMAC key. + * @note This assumes AES-256 KEK and will not work with other key types. + * @param macKey CMAC Derived Key + * @param msg KMM Message + * @param msgLen Message Length + * @returns UInt8Array Buffer containing the derived MAC key. + */ + UInt8Array cryptAES_KMM_CMAC(const uint8_t* macKey, const uint8_t* msg, uint16_t msgLen); + + /** + * @brief Helper to crypt a P25 PDU frame using AES-256. + * @param frame Data frame + * @param frameLen Data frame Length + */ + void cryptAES_PDU(uint8_t* frame, uint8_t frameLen); + + /** + * @brief Helper to crypt P25 IMBE audio using DES. + * @param imbe Buffer containing IMBE to crypt. + * @param duid P25 DUID. + */ + void cryptDES_IMBE(uint8_t* imbe, P25DEF::DUID::E duid); /** * @brief Helper to crypt P25 IMBE audio using AES-256. * @param imbe Buffer containing IMBE to crypt. diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index 1b7ff9cb1..fa8ad9578 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -5,7 +5,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (C) 2016 Jonathan Naylor, G4KLX - * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -63,14 +63,45 @@ namespace p25 const uint32_t P25_TDULC_FRAME_LENGTH_BYTES = 54U; const uint32_t P25_TDULC_FRAME_LENGTH_BITS = P25_TDULC_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_P2_FRAME_LENGTH_BYTES = 45U; + const uint32_t P25_P2_FRAME_LENGTH_BITS = P25_P2_FRAME_LENGTH_BYTES * 8U; + const uint32_t P25_NID_LENGTH_BYTES = 8U; const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U; + // TIA-102.BAAA-B Section 7.3 + // 5 5 7 5 F 5 F F 7 7 F F + // 01 01 01 01 01 11 01 01 11 11 01 01 11 11 11 11 01 11 01 11 11 11 11 11 + // +3 +3 +3 +3 +3 -3 +3 +3 -3 -3 +3 +3 -3 -3 -3 -3 +3 -3 +3 -3 -3 -3 -3 -3 const uint8_t P25_SYNC_BYTES[] = { 0x55U, 0x75U, 0xF5U, 0xFFU, 0x77U, 0xFFU }; const uint32_t P25_SYNC_LENGTH_BYTES = 6U; const uint32_t P25_SYNC_LENGTH_BITS = P25_SYNC_LENGTH_BYTES * 8U; const uint8_t P25_START_SYNC = 0x5FU; + // TIA-102.BBAC-A Section 5.1 + // 5 F 5 7 7 D 5 7 7 F F + // 01 01 11 11 01 01 01 11 01 11 11 01 01 01 01 11 01 11 11 11 11 11 + // +3 +3 -3 -3 +3 +3 +3 -3 +3 -3 -3 +3 +3 +3 +3 -3 +3 -3 -3 -3 -3 -3 + const uint8_t P25_P2_IEMI_SYNC_BYTES[] = { 0x05U, 0xF5U, 0x77U, 0xD5U, 0x77U, 0xFFU }; + const uint8_t P25_P2_IEMI_SYNC_LENGTH_BYTES = 6U; + const uint8_t P25_P2_IEMI_SYNC_LENGTH_BITS = 44U; + + // TIA-102.BBAC-A Section 5.1 + // 3 F D 5 5 D D F 5 F 5 + // 11 11 11 11 01 01 01 01 01 11 01 11 01 11 11 01 01 11 11 01 01 + // -3 -3 -3 -3 +3 +3 +3 +3 +3 -3 +3 -3 +3 -3 -3 +3 +3 -3 -3 +3 +3 + const uint8_t P25_P2_OEMI_SYNC_BYTES[] = { 0x03U, 0xFDU, 0x55U, 0xDDU, 0xF5U, 0xF5U }; + const uint8_t P25_P2_OEMI_SYNC_LENGTH_BYTES = 6U; + const uint8_t P25_P2_OEMI_SYNC_LENGTH_BITS = 42U; + + // TIA-102.BBAC-A Section 5.1 + // 5 7 5 D 5 7 F 7 F F + // 01 01 01 11 01 01 11 01 01 01 01 11 11 11 01 11 11 11 11 11 + // +3 +3 +3 -3 +3 +3 -3 +3 +3 +3 +3 -3 -3 -3 +3 -3 -3 -3 -3 -3 + const uint8_t P25_P2_ISCH_SYNC_BYTES[] = { 0x57U, 0x5DU, 0x57U, 0xF7U, 0xFFU }; + const uint8_t P25_P2_ISCH_SYNC_LENGTH_BYTES = 5U; + const uint8_t P25_P2_ISCH_SYNC_LENGTH_BITS = 38U; + const uint32_t P25_PREAMBLE_LENGTH_BYTES = P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES; const uint32_t P25_PREAMBLE_LENGTH_BITS = P25_SYNC_LENGTH_BITS + P25_NID_LENGTH_BITS; @@ -103,6 +134,7 @@ namespace p25 const uint32_t MI_LENGTH_BYTES = 9U; const uint32_t RAW_IMBE_LENGTH_BYTES = 11U; + const uint32_t RAW_AMBE_LENGTH_BYTES = 7U; const uint32_t P25_SS0_START = 70U; const uint32_t P25_SS1_START = 71U; @@ -120,6 +152,9 @@ namespace p25 const uint8_t AUTH_KEY_LENGTH_BYTES = 16U; const uint8_t MAX_ENC_KEY_LENGTH_BYTES = 32U; + const uint8_t MAX_WRAPPED_ENC_KEY_LENGTH_BYTES = 40U; + const uint8_t KMM_AES_MAC_LENGTH = 8U; + const uint8_t KMM_DES_MAC_LENGTH = 4U; /* @} */ /** @name Thresholds */ @@ -199,12 +234,12 @@ namespace p25 namespace ServiceClass { /** @brief Station Service Classes */ enum : uint8_t { - INVALID = 0x00U, //! Invalid Service Class - COMPOSITE = 0x01U, //! Composite Control Channel - DATA = 0x10U, //! Data - VOICE = 0x20U, //! Voice - REG = 0x40U, //! Registration - AUTH = 0x80U //! Authentication + INVALID = 0x00U, //!< Invalid Service Class + COMPOSITE = 0x01U, //!< Composite Control Channel + DATA = 0x10U, //!< Data + VOICE = 0x20U, //!< Voice + REG = 0x40U, //!< Registration + AUTH = 0x80U //!< Authentication }; } @@ -212,23 +247,23 @@ namespace p25 namespace SystemService { /** @brief System Service Types */ enum : uint32_t { - NET_ACTIVE = 0x0200000U, //! Network Active - GROUP_VOICE = 0x0080000U, //! Group Voice - IND_VOICE = 0x0040000U, //! Individual Voice - PSTN_UNIT_VOICE = 0x0020000U, //! PSTN Unit Voice - UNIT_PSTN_VOICE = 0x0010000U, //! Unit PSTN Voice - GROUP_DATA = 0x0004000U, //! Group Data - IND_DATA = 0x0002000U, //! Individual Data - UNIT_REG = 0x0000800U, //! Unit Registration - GROUP_AFF = 0x0000400U, //! Group Affiliation - GROUP_AFF_Q = 0x0000200U, //! Group Affiliation Query - USER_STS = 0x0000040U, //! User Status - USER_MSG = 0x0000020U, //! User Message - UNIT_STS = 0x0000010U, //! Unit Status - USER_STS_Q = 0x0000008U, //! User Status Query - UNIT_STS_Q = 0x0000004U, //! Unit Status Query - CALL_ALRT = 0x0000002U, //! Call Alert - EMERGENCY = 0x0000001U //! Emergency + NET_ACTIVE = 0x0200000U, //!< Network Active + GROUP_VOICE = 0x0080000U, //!< Group Voice + IND_VOICE = 0x0040000U, //!< Individual Voice + PSTN_UNIT_VOICE = 0x0020000U, //!< PSTN Unit Voice + UNIT_PSTN_VOICE = 0x0010000U, //!< Unit PSTN Voice + GROUP_DATA = 0x0004000U, //!< Group Data + IND_DATA = 0x0002000U, //!< Individual Data + UNIT_REG = 0x0000800U, //!< Unit Registration + GROUP_AFF = 0x0000400U, //!< Group Affiliation + GROUP_AFF_Q = 0x0000200U, //!< Group Affiliation Query + USER_STS = 0x0000040U, //!< User Status + USER_MSG = 0x0000020U, //!< User Message + UNIT_STS = 0x0000010U, //!< Unit Status + USER_STS_Q = 0x0000008U, //!< User Status Query + UNIT_STS_Q = 0x0000004U, //!< Unit Status Query + CALL_ALRT = 0x0000002U, //!< Call Alert + EMERGENCY = 0x0000001U //!< Emergency }; } @@ -241,14 +276,14 @@ namespace p25 const uint32_t SYS_SRV_TRUNK = SYS_SRV_DEFAULT | SystemService::GROUP_AFF | SystemService::UNIT_REG | SystemService::GROUP_AFF_Q; /** @} */ - /** @brief Conventional/Failur/Valid/Networked Flags */ + /** @brief Conventional/Failure/Valid/Networked Flags */ namespace CFVA { - /** @brief Conventional/Failur/Valid/Networked Flags */ + /** @brief Conventional/Failure/Valid/Networked Flags */ enum : uint8_t { - CONV = 0x08U, //! Conventional - FAILURE = 0x04U, //! Failure - VALID = 0x02U, //! Valid - NETWORK = 0x01U //! Networked + CONV = 0x08U, //!< Conventional + FAILURE = 0x04U, //!< Failure + VALID = 0x02U, //!< Valid + NETWORK = 0x01U //!< Networked }; } @@ -257,15 +292,15 @@ namespace p25 /** @brief Response Codes */ enum : uint8_t { // General Codes - ACCEPT = 0x00U, //! Accept - FAIL = 0x01U, //! Fail - DENY = 0x02U, //! Deny - REFUSED = 0x03U, //! Refused + ACCEPT = 0x00U, //!< Accept + FAIL = 0x01U, //!< Fail + DENY = 0x02U, //!< Deny + REFUSED = 0x03U, //!< Refused // Answer Codes - ANS_PROCEED = 0x20U, //! Proceed - ANS_DENY = 0x21U, //! Deny - ANS_WAIT = 0x22U //! Wait + ANS_PROCEED = 0x20U, //!< Proceed + ANS_DENY = 0x21U, //!< Deny + ANS_WAIT = 0x22U //!< Wait }; } @@ -273,9 +308,9 @@ namespace p25 namespace CancelService { /** @brief Cancel Service Codes */ enum : uint8_t { - NONE = 0x00U, //! None - TERM_QUE = 0x10U, //! Terminate Queued - TERM_RSRC_ASSIGN = 0x20U //! Terminate Resource Assigned + NONE = 0x00U, //!< None + TERM_QUE = 0x10U, //!< Terminate Queued + TERM_RSRC_ASSIGN = 0x20U //!< Terminate Resource Assigned }; } @@ -284,35 +319,35 @@ namespace p25 /** @brief Reason Codes */ enum : uint8_t { // Denial Codes - DENY_REQ_UNIT_NOT_VALID = 0x10U, //! Requesting Unit Not Valid - DENY_REQ_UNIT_NOT_AUTH = 0x11U, //! Requesting Unit Not Authenticated + DENY_REQ_UNIT_NOT_VALID = 0x10U, //!< Requesting Unit Not Valid + DENY_REQ_UNIT_NOT_AUTH = 0x11U, //!< Requesting Unit Not Authenticated - DENY_TGT_UNIT_NOT_VALID = 0x20U, //! Target Unit Not Vaild - DENY_TGT_UNIT_NOT_AUTH = 0x21U, //! Target Unit Not Authenticated - DENY_SU_FAILED_AUTH = 0x22U, //! Subscriber Failed Authentication - DENY_TGT_UNIT_REFUSED = 0x2FU, //! Target Unit Refused + DENY_TGT_UNIT_NOT_VALID = 0x20U, //!< Target Unit Not Vaild + DENY_TGT_UNIT_NOT_AUTH = 0x21U, //!< Target Unit Not Authenticated + DENY_SU_FAILED_AUTH = 0x22U, //!< Subscriber Failed Authentication + DENY_TGT_UNIT_REFUSED = 0x2FU, //!< Target Unit Refused - DENY_TGT_GROUP_NOT_VALID = 0x30U, //! Target Group Not Valid - DENY_TGT_GROUP_NOT_AUTH = 0x31U, //! Target Group Not Authenticated + DENY_TGT_GROUP_NOT_VALID = 0x30U, //!< Target Group Not Valid + DENY_TGT_GROUP_NOT_AUTH = 0x31U, //!< Target Group Not Authenticated - DENY_NO_NET_RSRC_AVAIL = 0x53U, //! No Network Resources Available - DENY_NO_RF_RSRC_AVAIL = 0x54U, //! No RF Resources Available - DENY_SVC_IN_USE = 0x55U, //! Service In Use + DENY_NO_NET_RSRC_AVAIL = 0x53U, //!< No Network Resources Available + DENY_NO_RF_RSRC_AVAIL = 0x54U, //!< No RF Resources Available + DENY_SVC_IN_USE = 0x55U, //!< Service In Use - DENY_SITE_ACCESS_DENIAL = 0x60U, //! Site Access Denial + DENY_SITE_ACCESS_DENIAL = 0x60U, //!< Site Access Denial - DENY_PTT_COLLIDE = 0x67U, //! Push-to-Talk Collision - DENY_PTT_BONK = 0x77U, //! Push-to-Talk Denial/Bonk + DENY_PTT_COLLIDE = 0x67U, //!< Push-to-Talk Collision + DENY_PTT_BONK = 0x77U, //!< Push-to-Talk Denial/Bonk - DENY_SYS_UNSUPPORTED_SVC = 0xFFU, //! Service Unsupported + DENY_SYS_UNSUPPORTED_SVC = 0xFFU, //!< Service Unsupported // Queue Codes - QUE_REQ_ACTIVE_SERVICE = 0x10U, //! Requested Service Active - QUE_TGT_ACTIVE_SERVICE = 0x20U, //! Target Service Active + QUE_REQ_ACTIVE_SERVICE = 0x10U, //!< Requested Service Active + QUE_TGT_ACTIVE_SERVICE = 0x20U, //!< Target Service Active - QUE_TGT_UNIT_QUEUED = 0x2FU, //! Target Unit Queued + QUE_TGT_UNIT_QUEUED = 0x2FU, //!< Target Unit Queued - QUE_CHN_RESOURCE_NOT_AVAIL = 0x40U //! Channel Resource Not Available + QUE_CHN_RESOURCE_NOT_AVAIL = 0x40U //!< Channel Resource Not Available }; } @@ -320,22 +355,22 @@ namespace p25 namespace ExtendedFunctions { /** @brief Extended Function Opcodes */ enum : uint16_t { - CHECK = 0x0000U, //! Radio Check - UNINHIBIT = 0x007EU, //! Radio Uninhibit - INHIBIT = 0x007FU, //! Radio Inhibit - CHECK_ACK = 0x0080U, //! Radio Check Ack - UNINHIBIT_ACK = 0x00FEU, //! Radio Uninhibit Ack - INHIBIT_ACK = 0x00FFU, //! Radio Inhibit Ack - - DYN_REGRP_REQ = 0x0200U, //! MFID $90 (Motorola) Dynamic Regroup IR - DYN_REGRP_CANCEL = 0x0201U, //! MFID $90 (Motorola) Dynamic Regroup IR Cancellation - DYN_REGRP_LOCK = 0x0202U, //! MFID $90 (Motorola) Lock Selector - DYN_REGRP_UNLOCK = 0x0203U, //! MFID $90 (Motorola) Unlock Selector - - DYN_REGRP_REQ_ACK = 0x0280U, //! MFID $90 (Motorola) Dynamic Regroup IR Ack - DYN_REGRP_CANCEL_ACK = 0x0281U, //! MFID $90 (Motorola) Dynamic Regroup IR Cancellation Ack - DYN_REGRP_LOCK_ACK = 0x0282U, //! MFID $90 (Motorola) Lock Selector Ack - DYN_REGRP_UNLOCK_ACK = 0x0283U, //! MFID $90 (Motorola) Unlock Selector Ack + CHECK = 0x0000U, //!< Radio Check + UNINHIBIT = 0x007EU, //!< Radio Uninhibit + INHIBIT = 0x007FU, //!< Radio Inhibit + CHECK_ACK = 0x0080U, //!< Radio Check Ack + UNINHIBIT_ACK = 0x00FEU, //!< Radio Uninhibit Ack + INHIBIT_ACK = 0x00FFU, //!< Radio Inhibit Ack + + DYN_REGRP_REQ = 0x0200U, //!< MFID $90 (Motorola) Dynamic Regroup IR + DYN_REGRP_CANCEL = 0x0201U, //!< MFID $90 (Motorola) Dynamic Regroup IR Cancellation + DYN_REGRP_LOCK = 0x0202U, //!< MFID $90 (Motorola) Lock Selector + DYN_REGRP_UNLOCK = 0x0203U, //!< MFID $90 (Motorola) Unlock Selector + + DYN_REGRP_REQ_ACK = 0x0280U, //!< MFID $90 (Motorola) Dynamic Regroup IR Ack + DYN_REGRP_CANCEL_ACK = 0x0281U, //!< MFID $90 (Motorola) Dynamic Regroup IR Cancellation Ack + DYN_REGRP_LOCK_ACK = 0x0282U, //!< MFID $90 (Motorola) Lock Selector Ack + DYN_REGRP_UNLOCK_ACK = 0x0283U, //!< MFID $90 (Motorola) Unlock Selector Ack }; } @@ -343,10 +378,10 @@ namespace p25 namespace FrameType { /** @brief DVM Network Frame Types */ enum E : uint8_t { - HDU_VALID = 0x01U, //! HDU Valid - HDU_LATE_ENTRY = 0x02U, //! HDU Late Entry - TERMINATOR = 0x03U, //! TDU/TDULC Terminator - DATA_UNIT = 0x00U //! Standard Data Unit + HDU_VALID = 0x01U, //!< HDU Valid + HDU_LATE_ENTRY = 0x02U, //!< HDU Late Entry + TERMINATOR = 0x03U, //!< TDU/TDULC Terminator + DATA_UNIT = 0x00U //!< Standard Data Unit }; } @@ -354,10 +389,10 @@ namespace p25 namespace PDUFormatType { /** @brief Data Format Type */ enum : uint8_t { - RSP = 0x03U, //! Response - UNCONFIRMED = 0x15U, //! Unconfirmed PDU - CONFIRMED = 0x16U, //! Confirmed PDU - AMBT = 0x17U //! Alternate Multi Block Trunking + RSP = 0x03U, //!< Response + UNCONFIRMED = 0x15U, //!< Unconfirmed PDU + CONFIRMED = 0x16U, //!< Confirmed PDU + AMBT = 0x17U //!< Alternate Multi Block Trunking }; } @@ -365,23 +400,23 @@ namespace p25 namespace PDUSAP { /** @brief Service Access Point */ enum : uint8_t { - USER_DATA = 0x00U, //! User Data - ENC_USER_DATA = 0x01U, //! Encrypted User Data + USER_DATA = 0x00U, //!< User Data + ENC_USER_DATA = 0x01U, //!< Encrypted User Data - PACKET_DATA = 0x04U, //! Packet Data + PACKET_DATA = 0x04U, //!< Packet Data - ARP = 0x05U, //! ARP + ARP = 0x05U, //!< ARP - SNDCP_CTRL_DATA = 0x06U, //! SNDCP Control Data + SNDCP_CTRL_DATA = 0x06U, //!< SNDCP Control Data - EXT_ADDR = 0x1FU, //! Extended Addressing + EXT_ADDR = 0x1FU, //!< Extended Addressing - CONV_DATA_REG = 0x20U, //! Registration + CONV_DATA_REG = 0x20U, //!< Registration - UNENC_KMM = 0x28U, //! Unencrypted KMM - ENC_KMM = 0x29U, //! Encrypted KMM + UNENC_KMM = 0x28U, //!< Unencrypted KMM + ENC_KMM = 0x29U, //!< Encrypted KMM - TRUNK_CTRL = 0x3DU //! Trunking Control + TRUNK_CTRL = 0x3DU //!< Trunking Control }; } @@ -389,9 +424,9 @@ namespace p25 namespace PDUAckClass { /** @brief Acknowledgement Class */ enum : uint8_t { - ACK = 0x00U, //! Acknowledge - NACK = 0x01U, //! Negative Acknowledge - ACK_RETRY = 0x02U //! Acknowledge Retry + ACK = 0x00U, //!< Acknowledge + NACK = 0x01U, //!< Negative Acknowledge + ACK_RETRY = 0x02U //!< Acknowledge Retry }; } @@ -399,17 +434,17 @@ namespace p25 namespace PDUAckType { /** @brief Acknowledgement Type */ enum : uint8_t { - RETRY = 0x00U, //! Retry + RETRY = 0x00U, //!< Retry - ACK = 0x01U, //! Acknowledge + ACK = 0x01U, //!< Acknowledge - NACK_ILLEGAL = 0x00U, //! Illegal Format - NACK_PACKET_CRC = 0x01U, //! Packet CRC - NACK_MEMORY_FULL = 0x02U, //! Memory Full - NACK_SEQ = 0x03U, //! Out of logical sequence FSN - NACK_UNDELIVERABLE = 0x04U, //! Undeliverable - NACK_OUT_OF_SEQ = 0x05U, //! Out of sequence, N(S) != V(R) or V(R) + 1 - NACK_INVL_USER = 0x06U //! Invalid User disallowed by the system + NACK_ILLEGAL = 0x00U, //!< Illegal Format + NACK_PACKET_CRC = 0x01U, //!< Packet CRC + NACK_MEMORY_FULL = 0x02U, //!< Memory Full + NACK_SEQ = 0x03U, //!< Out of logical sequence FSN + NACK_UNDELIVERABLE = 0x04U, //!< Undeliverable + NACK_OUT_OF_SEQ = 0x05U, //!< Out of sequence, N(S) != V(R) or V(R) + 1 + NACK_INVL_USER = 0x06U //!< Invalid User disallowed by the system }; } @@ -417,105 +452,110 @@ namespace p25 namespace PDURegType { /** @brief Registration Type */ enum : uint8_t { - CONNECT = 0x00U, //! Connect - DISCONNECT = 0x01U, //! Disconnect - ACCEPT = 0x04U, //! Accept - DENY = 0x05U //! Deny + CONNECT = 0x00U, //!< Connect + DISCONNECT = 0x01U, //!< Disconnect + ACCEPT = 0x04U, //!< Accept + DENY = 0x05U //!< Deny }; } /** @brief KMM Message Type */ namespace KMM_MessageType { enum : uint8_t { - NULL_CMD = 0x00U, //! Null + NULL_CMD = 0x00U, //!< Null + + CHANGE_RSI_CMD = 0x03U, //!< Change RSI Command + CHANGE_RSI_RSP = 0x04U, //!< Change RSI Response + CHANGEOVER_CMD = 0x05U, //!< Changeover Command + CHANGEOVER_RSP = 0x06U, //!< Changeover Response + + HELLO = 0x0CU, //!< Hello - CHANGE_RSI_CMD = 0x03U, //! Change RSI Command - CHANGE_RSI_RSP = 0x04U, //! Change RSI Response - CHANGEOVER_CMD = 0x05U, //! Changeover Command - CHANGEOVER_RSP = 0x06U, //! Changeover Response + INVENTORY_CMD = 0x0DU, //!< Inventory Command + INVENTORY_RSP = 0x0EU, //!< Inventory Response - HELLO = 0x0CU, //! Hello + MODIFY_KEY_CMD = 0x13U, //!< Modify Key Command - INVENTORY_CMD = 0x0DU, //! Inventory Command - INVENTORY_RSP = 0x0EU, //! Inventory Response + NAK = 0x16U, //!< Negative Ack + NO_SERVICE = 0x17U, //!< No Service - MODIFY_KEY_CMD = 0x13U, //! Modify Key Command + REKEY_ACK = 0x1DU, //!< Rekey Ack + REKEY_CMD = 0x1EU, //!< Rekey Command - NAK = 0x16U, //! Negative Ack - NO_SERVICE = 0x17U, //! No Service + ZEROIZE_CMD = 0x21U, //!< Zeroize Command + ZEROIZE_RSP = 0x22U, //!< Zeroize Response - ZEROIZE_CMD = 0x21U, //! Zeroize Command - ZEROIZE_RSP = 0x22U, //! Zeroize Response + DEREG_CMD = 0x23U, //!< SU Deregistration Command + DEREG_RSP = 0x24U, //!< SU Deregistration Response + REG_CMD = 0x25U, //!< SU Registration Command + REG_RSP = 0x26U, //!< SU Registration Response - DEREG_CMD = 0x23U, //! SU Deregistration Command - DEREG_RSP = 0x24U, //! SU Deregistration Response - REG_CMD = 0x25U, //! SU Registration Command - REG_RSP = 0x26U, //! SU Registration Response + UNABLE_TO_DECRYPT = 0x27U, //!< Unable to Decrypt Response }; } /** @brief KMM Response Kind */ namespace KMM_ResponseKind { enum : uint8_t { - NONE = 0x00U, //! Response Kind 1 (None) - DELAYED = 0x01U, //! Response Kind 2 (Delayed) - IMMEDIATE = 0x02U, //! Response Kind 3 (Immediate) + NONE = 0x00U, //!< Response Kind 1 (None) + DELAYED = 0x01U, //!< Response Kind 2 (Delayed) + IMMEDIATE = 0x02U, //!< Response Kind 3 (Immediate) }; } /** @brief KMM Message Authentication */ namespace KMM_MAC { enum : uint8_t { - NO_MAC = 0x00U, //! No Message Authentication - ENH_MAC = 0x02U, //! Enhanced Message Authentication - DES_MAC = 0x03U, //! DES Message Authentication + NO_MAC = 0x00U, //!< No Message Authentication + ENH_MAC = 0x02U, //!< Enhanced Message Authentication + DES_MAC = 0x03U, //!< DES Message Authentication }; } /** @brief KMM Inventory Type */ namespace KMM_InventoryType { enum : uint8_t { - NULL_INVENTORY = 0x00U, //! Null + NULL_INVENTORY = 0x00U, //!< Null - LIST_ACTIVE_KEYSET_IDS = 0x01U, //! List Active Keyset IDs - LIST_INACTIVE_KEYSET_IDS = 0x02U, //! List Inactive Keyset IDs - LIST_ACTIVE_KEY_IDS = 0x03U, //! List Active Key IDs - LIST_INACTIVE_KEY_IDS = 0x04U, //! List Inactive Key IDs + LIST_ACTIVE_KEYSET_IDS = 0x01U, //!< List Active Keyset IDs + LIST_INACTIVE_KEYSET_IDS = 0x02U, //!< List Inactive Keyset IDs + LIST_ACTIVE_KEY_IDS = 0x03U, //!< List Active Key IDs + LIST_INACTIVE_KEY_IDS = 0x04U, //!< List Inactive Key IDs }; } /** @brief KMM Hello Flag */ namespace KMM_HelloFlag { enum : uint8_t { - IDENT_ONLY = 0x00U, //! KMF or SU Identification Only + IDENT_ONLY = 0x00U, //!< KMF or SU Identification Only - REKEY_REQUEST_UKEK = 0x01U, //! Rekey Request (UKEK Exists) - REKEY_REQUEST_NO_UKEK = 0x02U, //! Rekey Request (UKEK does not exist) + REKEY_REQUEST_UKEK = 0x01U, //!< Rekey Request (UKEK Exists) + REKEY_REQUEST_NO_UKEK = 0x02U, //!< Rekey Request (UKEK does not exist) }; } /** @brief KMM Status */ namespace KMM_Status { enum : uint8_t { - CMD_PERFORMED = 0x00U, //! Command Performed - CMD_NOT_PERFORMED = 0x01U, //! Command Was Not Performed + CMD_PERFORMED = 0x00U, //!< Command Performed + CMD_NOT_PERFORMED = 0x01U, //!< Command Was Not Performed - ITEM_NOT_EXIST = 0x02U, //! Item does not exist - INVALID_MSG_ID = 0x03U, //! Invalid Message ID - INVALID_MAC = 0x04U, //! Invalid Message Authentication Code + ITEM_NOT_EXIST = 0x02U, //!< Item does not exist + INVALID_MSG_ID = 0x03U, //!< Invalid Message ID + INVALID_MAC = 0x04U, //!< Invalid Message Authentication Code - OUT_OF_MEMORY = 0x05U, //! Out of Memory - FAILED_TO_DECRYPT = 0x06U, //! Failed to decrypt message + OUT_OF_MEMORY = 0x05U, //!< Out of Memory + FAILED_TO_DECRYPT = 0x06U, //!< Failed to decrypt message - INVALID_MSG_NUMBER = 0x07U, //! Invalid Message Number - INVALID_KID = 0x08U, //! Invalid Key ID - INVALID_ALGID = 0x09U, //! Invalid Algorithm ID - INVALID_MFID = 0x0AU, //! Invalid Manufacturer ID + INVALID_MSG_NUMBER = 0x07U, //!< Invalid Message Number + INVALID_KID = 0x08U, //!< Invalid Key ID + INVALID_ALGID = 0x09U, //!< Invalid Algorithm ID + INVALID_MFID = 0x0AU, //!< Invalid Manufacturer ID - MI_ALL_ZERO = 0x0CU, //! Message Indicator All Zero - KEY_FAIL = 0x0DU, //! Key Identified by Alg/KID is Erased + MI_ALL_ZERO = 0x0CU, //!< Message Indicator All Zero + KEY_FAIL = 0x0DU, //!< Key Identified by Alg/KID is Erased - UNKNOWN = 0xFFU, //! Unknown + UNKNOWN = 0xFFU, //!< Unknown }; } @@ -524,11 +564,19 @@ namespace p25 /** @brief KMM Decryption Instruction - Message Indicator */ const uint8_t KMM_DECRYPT_INSTRUCT_MI = 0x40U; - /** @brief KMM Key Format TEK */ + /** @brief KMM MAC Format - CBC-MAC */ + const uint8_t KMM_MAC_FORMAT_CBC = 0x40U; + /** @brief KMM MAC Format - CMAC */ + const uint8_t KMM_MAC_FORMAT_CMAC = 0x41U; + + /** @brief KMM Keyset Format - KEKs */ + const uint8_t KEYSET_FORMAT_KEK = 0x80U; + + /** @brief KMM Body/Key Format - TEK Included */ const uint8_t KEY_FORMAT_TEK = 0x80U; - /** @brief KMM Key Format TEK */ + /** @brief KMM Body/Key Format - KEK Exists */ const uint8_t KEY_FORMAT_KEK_EXISTS = 0x40U; - /** @brief KMM Key Format Delete Key */ + /** @brief KMM Key Format - Delete Key */ const uint8_t KEY_FORMAT_DELETE = 0x20U; /** @brief SNDCP version 1 */ @@ -545,13 +593,13 @@ namespace p25 namespace SNDCP_PDUType { /** @brief SNDCP PDU Message Type */ enum : uint8_t { - ACT_TDS_CTX = 0x00U, //! Context Activation Request (ISP) / Context Activation Accept (OSP) + ACT_TDS_CTX = 0x00U, //!< Context Activation Request (ISP) / Context Activation Accept (OSP) - DEACT_TDS_CTX_REQ = 0x02U, //! Deactivate Context Request - ACT_TDS_CTX_REJECT = 0x03U, //! Activate Context Reject + DEACT_TDS_CTX_REQ = 0x02U, //!< Deactivate Context Request + ACT_TDS_CTX_REJECT = 0x03U, //!< Activate Context Reject - RF_UNCONFIRMED = 0x04U, //! Data Unconfirmed - RF_CONFIRMED = 0x05U //! Data Confirmed + RF_UNCONFIRMED = 0x04U, //!< Data Unconfirmed + RF_CONFIRMED = 0x05U //!< Data Confirmed }; } @@ -559,13 +607,13 @@ namespace p25 namespace SNDCPState { /** @brief SNDCP Activation TDS States */ enum E : uint8_t { - IDLE = 0U, //! Idle - Waiting for SU Registration - READY_S = 1U, //! Ready* - Waiting for SU Activation - STANDBY = 2U, //! Standby - SU Activated - READY = 3U, //! Ready - SU Activated and Rx/Tx Data - CLOSED = 4U, //! Closed - SU not yet Registered or Deregistered + IDLE = 0U, //!< Idle - Waiting for SU Registration + READY_S = 1U, //!< Ready* - Waiting for SU Activation + STANDBY = 2U, //!< Standby - SU Activated + READY = 3U, //!< Ready - SU Activated and Rx/Tx Data + CLOSED = 4U, //!< Closed - SU not yet Registered or Deregistered - ILLEGAL = 255U //! Illegal/Unknown + ILLEGAL = 255U //!< Illegal/Unknown }; } @@ -573,9 +621,9 @@ namespace p25 namespace SNDCPNAT { /** @brief SNDCP Network Address Type */ enum : uint8_t { - IPV4_STATIC_ADDR = 0U, //! IPv4 Static Address - IPV4_DYN_ADDR = 1U, //! IPv4 Dynamic Address - IPV4_NO_ADDRESS = 15U //! No Address + IPV4_STATIC_ADDR = 0U, //!< IPv4 Static Address + IPV4_DYN_ADDR = 1U, //!< IPv4 Dynamic Address + IPV4_NO_ADDRESS = 15U //!< No Address }; } @@ -583,12 +631,12 @@ namespace p25 namespace SNDCP_DSUT { /** @brief SNDCP Data Subscriber Unit Type */ enum : uint8_t { - TRUNKED_DATA_ONLY = 0U, //! Trunked Data Only - ALTERNATING_TRUNKED_DATA_VOICE = 1U, //! Alternating Trunked Voice & Data - CONV_DATA_ONLY = 2U, //! Conventional Data Only - ALTERNATING_CONV_DATA_VOICE = 3U, //! Alternating Conventional Voice & Data - TRUNKED_CONV_DATA_ONLY = 4U, //! Trunked and Conventional Data Only - ALT_T_AND_C_DATA_VOICE = 5U //! Alternating Trunked and Conventional Voice & Data + TRUNKED_DATA_ONLY = 0U, //!< Trunked Data Only + ALTERNATING_TRUNKED_DATA_VOICE = 1U, //!< Alternating Trunked Voice & Data + CONV_DATA_ONLY = 2U, //!< Conventional Data Only + ALTERNATING_CONV_DATA_VOICE = 3U, //!< Alternating Conventional Voice & Data + TRUNKED_CONV_DATA_ONLY = 4U, //!< Trunked and Conventional Data Only + ALT_T_AND_C_DATA_VOICE = 5U //!< Alternating Trunked and Conventional Voice & Data }; } @@ -596,22 +644,22 @@ namespace p25 namespace SNDCPReadyTimer { /** @brief SNDCP Ready Timer */ enum : uint8_t { - NOT_ALLOWED = 0U, //! Not Allowed - ONE_SECOND = 1U, //! 1 Second - TWO_SECONDS = 2U, //! 2 Seconds - FOUR_SECONDS = 3U, //! 4 Seconds - SIX_SECONDS = 4U, //! 6 Seconds - EIGHT_SECONDS = 5U, //! 8 Seconds - TEN_SECONDS = 6U, //! 10 Seconds - FIFTEEN_SECONDS = 7U, //! 15 Seconds - TWENTY_SECONDS = 8U, //! 20 Seconds - TWENTYFIVE_SECONDS = 9U, //! 25 Seconds - THIRTY_SECONDS = 10U, //! 30 Seconds - SIXTY_SECONDS = 11U, //! 60 Seconds - ONE_TWENTY_SECONDS = 12U, //! 120 Seconds - ONE_EIGHT_SECONDS = 13U, //! 180 Seconds - THREE_HUNDRED_SECONDS = 14U, //! 300 Seconds - ALWAYS = 15U //! Always + NOT_ALLOWED = 0U, //!< Not Allowed + ONE_SECOND = 1U, //!< 1 Second + TWO_SECONDS = 2U, //!< 2 Seconds + FOUR_SECONDS = 3U, //!< 4 Seconds + SIX_SECONDS = 4U, //!< 6 Seconds + EIGHT_SECONDS = 5U, //!< 8 Seconds + TEN_SECONDS = 6U, //!< 10 Seconds + FIFTEEN_SECONDS = 7U, //!< 15 Seconds + TWENTY_SECONDS = 8U, //!< 20 Seconds + TWENTYFIVE_SECONDS = 9U, //!< 25 Seconds + THIRTY_SECONDS = 10U, //!< 30 Seconds + SIXTY_SECONDS = 11U, //!< 60 Seconds + ONE_TWENTY_SECONDS = 12U, //!< 120 Seconds + ONE_EIGHT_SECONDS = 13U, //!< 180 Seconds + THREE_HUNDRED_SECONDS = 14U, //!< 300 Seconds + ALWAYS = 15U //!< Always }; } @@ -619,22 +667,22 @@ namespace p25 namespace SNDCPStandbyTimer { /** @brief SNDCP Standby Timer */ enum : uint8_t { - NOT_ALLOWED = 0U, //! Not Allowed - TEN_SECONDS = 1U, //! 10 Seconds - THIRTY_SECONDS = 2U, //! 30 Seconds - ONE_MINUTE = 3U, //! 1 Minute - FIVE_MINUTES = 4U, //! 5 Minutes - TEN_MINUTES = 5U, //! 10 Minutes - THIRTY_MINUTES = 6U, //! 30 Minutes - ONE_HOUR = 7U, //! 1 Hour - TWO_HOURS = 8U, //! 2 Hours - FOUR_HOURS = 9U, //! 4 Hours - EIGHT_HOURS = 10U, //! 8 Hours - TWELVE_HOURS = 11U, //! 12 Hours - TWENTY_FOUR_HOURS = 12U, //! 24 Hours - FORTY_EIGHT_HOURS = 13U, //! 48 Hours - SEVENTY_TWO_HOURS = 14U, //! 72 Hours - ALWAYS = 15U //! Always + NOT_ALLOWED = 0U, //!< Not Allowed + TEN_SECONDS = 1U, //!< 10 Seconds + THIRTY_SECONDS = 2U, //!< 30 Seconds + ONE_MINUTE = 3U, //!< 1 Minute + FIVE_MINUTES = 4U, //!< 5 Minutes + TEN_MINUTES = 5U, //!< 10 Minutes + THIRTY_MINUTES = 6U, //!< 30 Minutes + ONE_HOUR = 7U, //!< 1 Hour + TWO_HOURS = 8U, //!< 2 Hours + FOUR_HOURS = 9U, //!< 4 Hours + EIGHT_HOURS = 10U, //!< 8 Hours + TWELVE_HOURS = 11U, //!< 12 Hours + TWENTY_FOUR_HOURS = 12U, //!< 24 Hours + FORTY_EIGHT_HOURS = 13U, //!< 48 Hours + SEVENTY_TWO_HOURS = 14U, //!< 72 Hours + ALWAYS = 15U //!< Always }; } @@ -642,22 +690,22 @@ namespace p25 namespace SNDCPRejectReason { /** @brief SNDCP Reject Reasons */ enum : uint8_t { - ANY_REASON = 0U, //! Any Reason - SU_NOT_PROVISIONED = 1U, //! Subscriber Not Provisioned - SU_DSUT_NOT_SUPPORTED = 2U, //! Subscriber Data Unit Type Not Supported - MAX_TDS_CTX_EXCEEDED = 3U, //! Maximum Number of TDS Contexts Exceeded - SNDCP_VER_NOT_SUPPORTED = 4U, //! SNDCP Version Not Supported - PDS_NOT_SUPPORTED_SITE = 5U, //! Packet Data Service Not Supported on Site - PDS_NOT_SUPPORTED_SYSTEM = 6U, //! Packet Data Service Not Supported on System - - STATIC_IP_NOT_CORRECT = 7U, //! Static IP Address Not Correct - STATIC_IP_ALLOCATION_UNSUPPORTED = 8U, //! Static IP Address Allocation Unsupported - STATIC_IP_IN_USE = 9U, //! Static IP In Use - - IPV4_NOT_SUPPORTED = 10U, //! IPv4 Not Supported - - DYN_IP_POOL_EMPTY = 11U, //! Dynamic IP Address Pool Empty - DYN_IP_ALLOCATION_UNSUPPORTED = 12U //! Dynamic IP Address Allocation Unsupported + ANY_REASON = 0U, //!< Any Reason + SU_NOT_PROVISIONED = 1U, //!< Subscriber Not Provisioned + SU_DSUT_NOT_SUPPORTED = 2U, //!< Subscriber Data Unit Type Not Supported + MAX_TDS_CTX_EXCEEDED = 3U, //!< Maximum Number of TDS Contexts Exceeded + SNDCP_VER_NOT_SUPPORTED = 4U, //!< SNDCP Version Not Supported + PDS_NOT_SUPPORTED_SITE = 5U, //!< Packet Data Service Not Supported on Site + PDS_NOT_SUPPORTED_SYSTEM = 6U, //!< Packet Data Service Not Supported on System + + STATIC_IP_NOT_CORRECT = 7U, //!< Static IP Address Not Correct + STATIC_IP_ALLOCATION_UNSUPPORTED = 8U, //!< Static IP Address Allocation Unsupported + STATIC_IP_IN_USE = 9U, //!< Static IP In Use + + IPV4_NOT_SUPPORTED = 10U, //!< IPv4 Not Supported + + DYN_IP_POOL_EMPTY = 11U, //!< Dynamic IP Address Pool Empty + DYN_IP_ALLOCATION_UNSUPPORTED = 12U //!< Dynamic IP Address Allocation Unsupported }; } @@ -665,8 +713,8 @@ namespace p25 namespace SNDCPDeactivationType { /** @brief SNDCP Deactivation Types */ enum : uint8_t { - DEACT_ALL = 0U, //! Deactivate all NSAPIs - DEACT_THIS_PDU = 1U //! Deactivate NSAPI in this PDU + DEACT_ALL = 0U, //!< Deactivate all NSAPIs + DEACT_THIS_PDU = 1U //!< Deactivate NSAPI in this PDU }; } @@ -679,38 +727,38 @@ namespace p25 namespace LCO { /** @brief LDUx/TDULC Link Control Opcode(s) */ enum : uint8_t { - GROUP = 0x00U, //! GRP VCH USER - Group Voice Channel User - GROUP_UPDT = 0x02U, //! GRP VCH UPDT - Group Voice Channel Update - PRIVATE = 0x03U, //! UU VCH USER - Unit-to-Unit Voice Channel User - UU_ANS_REQ = 0x05U, //! UU ANS REQ - Unit to Unit Answer Request - TEL_INT_VCH_USER = 0x06U, //! TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data - TEL_INT_ANS_RQST = 0x07U, //! TEL INT ANS RQST - Telephone Interconnect Answer Request - EXPLICIT_SOURCE_ID = 0x09U, //! EXPLICIT SOURCE ID - Explicit Source ID - PRIVATE_EXT = 0x0AU, //! UU VCH USER EXT - Unit-to-Unit Voice Channel User Extended - CALL_TERM = 0x0FU, //! CALL TERM - Call Termination or Cancellation - IDEN_UP = 0x18U, //! IDEN UP - Channel Identifier Update - SYS_SRV_BCAST = 0x20U, //! SYS SRV BCAST - System Service Broadcast - ADJ_STS_BCAST = 0x22U, //! ADJ STS BCAST - Adjacent Site Status Broadcast - RFSS_STS_BCAST = 0x23U, //! RFSS STS BCAST - RFSS Status Broadcast - NET_STS_BCAST = 0x24U, //! NET STS BCAST - Network Status Broadcast - CONV_FALLBACK = 0x2AU, //! CONV FALLBACK - Conventional Fallback + GROUP = 0x00U, //!< GRP VCH USER - Group Voice Channel User + GROUP_UPDT = 0x02U, //!< GRP VCH UPDT - Group Voice Channel Update + PRIVATE = 0x03U, //!< UU VCH USER - Unit-to-Unit Voice Channel User + UU_ANS_REQ = 0x05U, //!< UU ANS REQ - Unit to Unit Answer Request + TEL_INT_VCH_USER = 0x06U, //!< TEL INT VCH USER - Telephone Interconnect Voice Channel User / MOT GPS DATA - Motorola In-Band GPS Data + TEL_INT_ANS_RQST = 0x07U, //!< TEL INT ANS RQST - Telephone Interconnect Answer Request + EXPLICIT_SOURCE_ID = 0x09U, //!< EXPLICIT SOURCE ID - Explicit Source ID + PRIVATE_EXT = 0x0AU, //!< UU VCH USER EXT - Unit-to-Unit Voice Channel User Extended + CALL_TERM = 0x0FU, //!< CALL TERM - Call Termination or Cancellation + IDEN_UP = 0x18U, //!< IDEN UP - Channel Identifier Update + SYS_SRV_BCAST = 0x20U, //!< SYS SRV BCAST - System Service Broadcast + ADJ_STS_BCAST = 0x22U, //!< ADJ STS BCAST - Adjacent Site Status Broadcast + RFSS_STS_BCAST = 0x23U, //!< RFSS STS BCAST - RFSS Status Broadcast + NET_STS_BCAST = 0x24U, //!< NET STS BCAST - Network Status Broadcast + CONV_FALLBACK = 0x2AU, //!< CONV FALLBACK - Conventional Fallback // LDUx/TDULC Motorola Link Control Opcode(s) - FAILSOFT = 0x02U, //! FAILSOFT - Failsoft + FAILSOFT = 0x02U, //!< FAILSOFT - Failsoft - MOT_PTT_LOC_HEADER = 0x29U, //! MOT PTT LOC HEADER - Motorola PTT Location Header - MOT_PTT_LOC_PAYLOAD = 0x2AU, //! MOT PTT LOC PAYLOAD - Motorola PTT Location Payload + MOT_PTT_LOC_HEADER = 0x29U, //!< MOT PTT LOC HEADER - Motorola PTT Location Header + MOT_PTT_LOC_PAYLOAD = 0x2AU, //!< MOT PTT LOC PAYLOAD - Motorola PTT Location Payload // LDUx/TDULC Harris Link Control Opcode(s) - HARRIS_PTT_PA_ODD = 0x2AU, //! HARRIS PTT PA ODD - Harris PTT Position and Altitude Odd - HARRIS_PTT_PB_ODD = 0x2BU, //! HARRIS PTT PB ODD - Harris PTT Position and Bearing Odd - HARRIS_PTT_PA_EVEN = 0x2CU, //! HARRIS PTT PA EVEN - Harris PTT Position and Altitude Even - HARRIS_PTT_PB_EVEN = 0x2DU, //! HARRIS PTT PB EVEN - Harris PTT Position and Bearing Even - - HARRIS_USER_ALIAS_PA_ODD = 0x32U, //! HARRIS USER ALIAS PA ODD - Harris User Alias Position and Altitude Odd - HARRIS_USER_ALIAS_PB_ODD = 0x33U, //! HARRIS USER ALIAS PB ODD - Harris User Alias Position and Bearing Odd - HARRIS_USER_ALIAS_PA_EVEN = 0x34U, //! HARRIS USER ALIAS PA EVEN - Harris User Alias Position and Altitude Even - HARRIS_USER_ALIAS_PB_EVEN = 0x35U, //! HARRIS USER ALIAS PB EVEN - Harris User Alias Position and Bearing Even + HARRIS_PTT_PA_ODD = 0x2AU, //!< HARRIS PTT PA ODD - Harris PTT Position and Altitude Odd + HARRIS_PTT_PB_ODD = 0x2BU, //!< HARRIS PTT PB ODD - Harris PTT Position and Bearing Odd + HARRIS_PTT_PA_EVEN = 0x2CU, //!< HARRIS PTT PA EVEN - Harris PTT Position and Altitude Even + HARRIS_PTT_PB_EVEN = 0x2DU, //!< HARRIS PTT PB EVEN - Harris PTT Position and Bearing Even + + HARRIS_USER_ALIAS_PA_ODD = 0x32U, //!< HARRIS USER ALIAS PA ODD - Harris User Alias Position and Altitude Odd + HARRIS_USER_ALIAS_PB_ODD = 0x33U, //!< HARRIS USER ALIAS PB ODD - Harris User Alias Position and Bearing Odd + HARRIS_USER_ALIAS_PA_EVEN = 0x34U, //!< HARRIS USER ALIAS PA EVEN - Harris User Alias Position and Altitude Even + HARRIS_USER_ALIAS_PB_EVEN = 0x35U, //!< HARRIS USER ALIAS PB EVEN - Harris User Alias Position and Bearing Even }; } @@ -719,89 +767,103 @@ namespace p25 /** @brief TSBK Control Opcode(s) */ enum : uint8_t { // TSBK ISP/OSP Shared Opcode(s) - IOSP_GRP_VCH = 0x00U, //! GRP VCH REQ - Group Voice Channel Request (ISP), GRP VCH GRANT - Group Voice Channel Grant (OSP) - IOSP_UU_VCH = 0x04U, //! UU VCH REQ - Unit-to-Unit Voice Channel Request (ISP), UU VCH GRANT - Unit-to-Unit Voice Channel Grant (OSP) - IOSP_UU_ANS = 0x05U, //! UU ANS RSP - Unit-to-Unit Answer Response (ISP), UU ANS REQ - Unit-to-Unit Answer Request (OSP) - IOSP_TELE_INT_DIAL = 0x08U, //! TELE INT DIAL REQ - Telephone Interconnect Request - Explicit (ISP), TELE INT DIAL GRANT - Telephone Interconnect Grant (OSP) - IOSP_TELE_INT_ANS = 0x0AU, //! TELE INT ANS RSP - Telephone Interconnect Answer Response (ISP), TELE INT ANS REQ - Telephone Interconnect Answer Request (OSP) - IOSP_STS_UPDT = 0x18U, //! STS UPDT REQ - Status Update Request (ISP), STS UPDT - Status Update (OSP) - IOSP_STS_Q = 0x1AU, //! STS Q REQ - Status Query Request (ISP), STS Q - Status Query (OSP) - IOSP_MSG_UPDT = 0x1CU, //! MSG UPDT REQ - Message Update Request (ISP), MSG UPDT - Message Update (OSP) - IOSP_RAD_MON = 0x1DU, //! RAD MON REQ - Radio Unit Monitor Request (ISP), RAD MON CMD - Radio Monitor Command (OSP) - IOSP_RAD_MON_ENH = 0x1EU, //! RAD MON ENH REQ - Radio Unit Monitor Enhanced Request (ISP), RAD MON ENH CMD - Radio Unit Monitor Enhanced Command (OSP) - IOSP_CALL_ALRT = 0x1FU, //! CALL ALRT REQ - Call Alert Request (ISP), CALL ALRT - Call Alert (OSP) - IOSP_ACK_RSP = 0x20U, //! ACK RSP U - Acknowledge Response - Unit (ISP), ACK RSP FNE - Acknowledge Response - FNE (OSP) - IOSP_EXT_FNCT = 0x24U, //! EXT FNCT RSP - Extended Function Response (ISP), EXT FNCT CMD - Extended Function Command (OSP) - IOSP_GRP_AFF = 0x28U, //! GRP AFF REQ - Group Affiliation Request (ISP), GRP AFF RSP - Group Affiliation Response (OSP) - IOSP_U_REG = 0x2CU, //! U REG REQ - Unit Registration Request (ISP), U REG RSP - Unit Registration Response (OSP) + IOSP_GRP_VCH = 0x00U, //!< GRP VCH REQ - Group Voice Channel Request (ISP), GRP VCH GRANT - Group Voice Channel Grant (OSP) + IOSP_UU_VCH = 0x04U, //!< UU VCH REQ - Unit-to-Unit Voice Channel Request (ISP), UU VCH GRANT - Unit-to-Unit Voice Channel Grant (OSP) + IOSP_UU_ANS = 0x05U, //!< UU ANS RSP - Unit-to-Unit Answer Response (ISP), UU ANS REQ - Unit-to-Unit Answer Request (OSP) + IOSP_TELE_INT_DIAL = 0x08U, //!< TELE INT DIAL REQ - Telephone Interconnect Request - Explicit (ISP), TELE INT DIAL GRANT - Telephone Interconnect Grant (OSP) + IOSP_TELE_INT_ANS = 0x0AU, //!< TELE INT ANS RSP - Telephone Interconnect Answer Response (ISP), TELE INT ANS REQ - Telephone Interconnect Answer Request (OSP) + IOSP_STS_UPDT = 0x18U, //!< STS UPDT REQ - Status Update Request (ISP), STS UPDT - Status Update (OSP) + IOSP_STS_Q = 0x1AU, //!< STS Q REQ - Status Query Request (ISP), STS Q - Status Query (OSP) + IOSP_MSG_UPDT = 0x1CU, //!< MSG UPDT REQ - Message Update Request (ISP), MSG UPDT - Message Update (OSP) + IOSP_RAD_MON = 0x1DU, //!< RAD MON REQ - Radio Unit Monitor Request (ISP), RAD MON CMD - Radio Monitor Command (OSP) + IOSP_RAD_MON_ENH = 0x1EU, //!< RAD MON ENH REQ - Radio Unit Monitor Enhanced Request (ISP), RAD MON ENH CMD - Radio Unit Monitor Enhanced Command (OSP) + IOSP_CALL_ALRT = 0x1FU, //!< CALL ALRT REQ - Call Alert Request (ISP), CALL ALRT - Call Alert (OSP) + IOSP_ACK_RSP = 0x20U, //!< ACK RSP U - Acknowledge Response - Unit (ISP), ACK RSP FNE - Acknowledge Response - FNE (OSP) + IOSP_EXT_FNCT = 0x24U, //!< EXT FNCT RSP - Extended Function Response (ISP), EXT FNCT CMD - Extended Function Command (OSP) + IOSP_GRP_AFF = 0x28U, //!< GRP AFF REQ - Group Affiliation Request (ISP), GRP AFF RSP - Group Affiliation Response (OSP) + IOSP_U_REG = 0x2CU, //!< U REG REQ - Unit Registration Request (ISP), U REG RSP - Unit Registration Response (OSP) // TSBK Inbound Signalling Packet (ISP) Opcode(s) - ISP_TELE_INT_PSTN_REQ = 0x09U, //! TELE INT PSTN REQ - Telephone Interconnect Request - Implicit - ISP_SNDCP_CH_REQ = 0x12U, //! SNDCP CH REQ - SNDCP Data Channel Request - ISP_SNDCP_REC_REQ = 0x14U, //! SNDCP REC REQ - SNDCP Reconnect Request - ISP_STS_Q_RSP = 0x19U, //! STS Q RSP - Status Query Response - ISP_STS_Q_REQ = 0x1CU, //! STS Q REQ - Status Query Request - ISP_CAN_SRV_REQ = 0x23U, //! CAN SRV REQ - Cancel Service Request - ISP_EMERG_ALRM_REQ = 0x27U, //! EMERG ALRM REQ - Emergency Alarm Request - ISP_GRP_AFF_Q_RSP = 0x29U, //! GRP AFF Q RSP - Group Affiliation Query Response - ISP_U_DEREG_REQ = 0x2BU, //! U DE REG REQ - Unit De-Registration Request - ISP_LOC_REG_REQ = 0x2DU, //! LOC REG REQ - Location Registration Request - ISP_AUTH_RESP = 0x38U, //! AUTH RESP - Authentication Response - ISP_AUTH_RESP_M = 0x39U, //! AUTH RESP M - Authentication Response Mutual - ISP_AUTH_FNE_RST = 0x3AU, //! AUTH FNE RST - Authentication FNE Result - ISP_AUTH_SU_DMD = 0x3BU, //! AUTH SU DMD - Authentication SU Demand + ISP_TELE_INT_PSTN_REQ = 0x09U, //!< TELE INT PSTN REQ - Telephone Interconnect Request - Implicit + ISP_SNDCP_CH_REQ = 0x12U, //!< SNDCP CH REQ - SNDCP Data Channel Request + ISP_SNDCP_REC_REQ = 0x14U, //!< SNDCP REC REQ - SNDCP Reconnect Request + ISP_STS_Q_RSP = 0x19U, //!< STS Q RSP - Status Query Response + ISP_STS_Q_REQ = 0x1CU, //!< STS Q REQ - Status Query Request + ISP_CAN_SRV_REQ = 0x23U, //!< CAN SRV REQ - Cancel Service Request + ISP_EMERG_ALRM_REQ = 0x27U, //!< EMERG ALRM REQ - Emergency Alarm Request + ISP_GRP_AFF_Q_RSP = 0x29U, //!< GRP AFF Q RSP - Group Affiliation Query Response + ISP_U_DEREG_REQ = 0x2BU, //!< U DE REG REQ - Unit De-Registration Request + ISP_LOC_REG_REQ = 0x2DU, //!< LOC REG REQ - Location Registration Request + ISP_AUTH_RESP = 0x38U, //!< AUTH RESP - Authentication Response + ISP_AUTH_RESP_M = 0x39U, //!< AUTH RESP M - Authentication Response Mutual + ISP_AUTH_FNE_RST = 0x3AU, //!< AUTH FNE RST - Authentication FNE Result + ISP_AUTH_SU_DMD = 0x3BU, //!< AUTH SU DMD - Authentication SU Demand // TSBK Outbound Signalling Packet (OSP) Opcode(s) - OSP_GRP_VCH_GRANT_UPD = 0x02U, //! GRP VCH GRANT UPD - Group Voice Channel Grant Update - OSP_UU_VCH_GRANT_UPD = 0x06U, //! UU VCH GRANT UPD - Unit-to-Unit Voice Channel Grant Update - OSP_SNDCP_CH_GNT = 0x14U, //! SNDCP CH GNT - SNDCP Data Channel Grant - OSP_SNDCP_CH_ANN = 0x16U, //! SNDCP CH ANN - SNDCP Data Channel Announcement - OSP_STS_Q = 0x1AU, //! STS Q - Status Query - OSP_QUE_RSP = 0x21U, //! QUE RSP - Queued Response - OSP_DENY_RSP = 0x27U, //! DENY RSP - Deny Response - OSP_SCCB_EXP = 0x29U, //! SCCB - Secondary Control Channel Broadcast - Explicit - OSP_GRP_AFF_Q = 0x2AU, //! GRP AFF Q - Group Affiliation Query - OSP_LOC_REG_RSP = 0x2BU, //! LOC REG RSP - Location Registration Response - OSP_U_REG_CMD = 0x2DU, //! U REG CMD - Unit Registration Command - OSP_U_DEREG_ACK = 0x2FU, //! U DE REG ACK - Unit De-Registration Acknowledge - OSP_SYNC_BCAST = 0x30U, //! SYNC BCAST - Synchronization Broadcast - OSP_AUTH_DMD = 0x31U, //! AUTH DMD - Authentication Demand - OSP_AUTH_FNE_RESP = 0x32U, //! AUTH FNE RESP - Authentication FNE Response - OSP_IDEN_UP_VU = 0x34U, //! IDEN UP VU - Channel Identifier Update for VHF/UHF Bands - OSP_TIME_DATE_ANN = 0x35U, //! TIME DATE ANN - Time and Date Announcement - OSP_SYS_SRV_BCAST = 0x38U, //! SYS SRV BCAST - System Service Broadcast - OSP_SCCB = 0x39U, //! SCCB - Secondary Control Channel Broadcast - OSP_RFSS_STS_BCAST = 0x3AU, //! RFSS STS BCAST - RFSS Status Broadcast - OSP_NET_STS_BCAST = 0x3BU, //! NET STS BCAST - Network Status Broadcast - OSP_ADJ_STS_BCAST = 0x3CU, //! ADJ STS BCAST - Adjacent Site Status Broadcast - OSP_IDEN_UP = 0x3DU, //! IDEN UP - Channel Identifier Update + OSP_GRP_VCH_GRANT_UPD = 0x02U, //!< GRP VCH GRANT UPD - Group Voice Channel Grant Update + OSP_UU_VCH_GRANT_UPD = 0x06U, //!< UU VCH GRANT UPD - Unit-to-Unit Voice Channel Grant Update + OSP_SNDCP_CH_GNT = 0x14U, //!< SNDCP CH GNT - SNDCP Data Channel Grant + OSP_SNDCP_CH_ANN = 0x16U, //!< SNDCP CH ANN - SNDCP Data Channel Announcement + OSP_STS_Q = 0x1AU, //!< STS Q - Status Query + OSP_QUE_RSP = 0x21U, //!< QUE RSP - Queued Response + OSP_DENY_RSP = 0x27U, //!< DENY RSP - Deny Response + OSP_SCCB_EXP = 0x29U, //!< SCCB - Secondary Control Channel Broadcast - Explicit + OSP_GRP_AFF_Q = 0x2AU, //!< GRP AFF Q - Group Affiliation Query + OSP_LOC_REG_RSP = 0x2BU, //!< LOC REG RSP - Location Registration Response + OSP_U_REG_CMD = 0x2DU, //!< U REG CMD - Unit Registration Command + OSP_U_DEREG_ACK = 0x2FU, //!< U DE REG ACK - Unit De-Registration Acknowledge + OSP_SYNC_BCAST = 0x30U, //!< SYNC BCAST - Synchronization Broadcast + OSP_AUTH_DMD = 0x31U, //!< AUTH DMD - Authentication Demand + OSP_AUTH_FNE_RESP = 0x32U, //!< AUTH FNE RESP - Authentication FNE Response + OSP_IDEN_UP_VU = 0x34U, //!< IDEN UP VU - Channel Identifier Update for VHF/UHF Bands + OSP_TIME_DATE_ANN = 0x35U, //!< TIME DATE ANN - Time and Date Announcement + OSP_SYS_SRV_BCAST = 0x38U, //!< SYS SRV BCAST - System Service Broadcast + OSP_SCCB = 0x39U, //!< SCCB - Secondary Control Channel Broadcast + OSP_RFSS_STS_BCAST = 0x3AU, //!< RFSS STS BCAST - RFSS Status Broadcast + OSP_NET_STS_BCAST = 0x3BU, //!< NET STS BCAST - Network Status Broadcast + OSP_ADJ_STS_BCAST = 0x3CU, //!< ADJ STS BCAST - Adjacent Site Status Broadcast + OSP_IDEN_UP = 0x3DU, //!< IDEN UP - Channel Identifier Update // TSBK Motorola Outbound Signalling Packet (OSP) Opcode(s) - OSP_MOT_GRG_ADD = 0x00U, //! MOT GRG ADD - Motorola / Group Regroup Add (Patch Supergroup) - OSP_MOT_GRG_DEL = 0x01U, //! MOT GRG DEL - Motorola / Group Regroup Delete (Unpatch Supergroup) - OSP_MOT_GRG_VCH_GRANT = 0x02U, //! MOT GRG GROUP VCH GRANT / Group Regroup Voice Channel Grant - OSP_MOT_GRG_VCH_UPD = 0x03U, //! MOT GRG GROUP VCH GRANT UPD / Group Regroup Voice Channel Grant Update - OSP_MOT_CC_BSI = 0x0BU, //! MOT CC BSI - Motorola / Control Channel Base Station Identifier - OSP_MOT_PSH_CCH = 0x0EU, //! MOT PSH CCH - Motorola / Planned Control Channel Shutdown + OSP_MOT_GRG_ADD = 0x00U, //!< MOT GRG ADD - Motorola / Group Regroup Add (Patch Supergroup) + OSP_MOT_GRG_DEL = 0x01U, //!< MOT GRG DEL - Motorola / Group Regroup Delete (Unpatch Supergroup) + OSP_MOT_GRG_VCH_GRANT = 0x02U, //!< MOT GRG GROUP VCH GRANT / Group Regroup Voice Channel Grant + OSP_MOT_GRG_VCH_UPD = 0x03U, //!< MOT GRG GROUP VCH GRANT UPD / Group Regroup Voice Channel Grant Update + OSP_MOT_CC_BSI = 0x0BU, //!< MOT CC BSI - Motorola / Control Channel Base Station Identifier + OSP_MOT_PSH_CCH = 0x0EU, //!< MOT PSH CCH - Motorola / Planned Control Channel Shutdown // TSBK DVM Outbound Signalling Packet (OSP) Opcode(s) - OSP_DVM_GIT_HASH = 0x3FU, //! + OSP_DVM_GIT_HASH = 0x3FU, //!< }; } + // TIA-102.BAAC-D Section 2.11 /** @brief Data Unit ID(s) */ namespace DUID { /** @brief Data Unit ID(s) */ enum E : uint8_t { - HDU = 0x00U, //! Header Data Unit - TDU = 0x03U, //! Simple Terminator Data Unit - LDU1 = 0x05U, //! Logical Link Data Unit 1 - VSELP1 = 0x06U, //! Motorola VSELP 1 - TSDU = 0x07U, //! Trunking System Data Unit - VSELP2 = 0x09U, //! Motorola VSELP 2 - LDU2 = 0x0AU, //! Logical Link Data Unit 2 - PDU = 0x0CU, //! Packet Data Unit - TDULC = 0x0FU //! Terminator Data Unit with Link Control + HDU = 0x00U, //!< Header Data Unit + TDU = 0x03U, //!< Simple Terminator Data Unit + LDU1 = 0x05U, //!< Logical Link Data Unit 1 + VSELP1 = 0x06U, //!< Motorola VSELP 1 + TSDU = 0x07U, //!< Trunking System Data Unit + VSELP2 = 0x09U, //!< Motorola VSELP 2 + LDU2 = 0x0AU, //!< Logical Link Data Unit 2 + PDU = 0x0CU, //!< Packet Data Unit + TDULC = 0x0FU //!< Terminator Data Unit with Link Control + }; + } + + /** @brief Phase 2 Data Unit ID(s) */ + namespace P2_DUID { + /** @brief Data Unit ID(s) */ + enum E : uint8_t { + VTCH_4V = 0x00U, //!< Inbound/Outbound 4V + SACCH_SCRAMBLED = 0x03U, //!< SACCH Scrambled + VTCH_2V = 0x06U, //!< Inbound/Outbound 2V + FACCH_SCRAMBLED = 0x09U, //!< FACCH Scrambled + SACCH_UNSCRAMBLED = 0x0CU, //!< SACCH Unscrambled + FACCH_UNSCRAMBLED = 0x0FU //!< FACCH Unscrambled }; } @@ -816,6 +878,15 @@ namespace p25 #define P25_LDU2_STR "P25, LDU2 (Logical Link Data Unit 2)" #define P25_PDU_STR "P25, PDU (Packet Data Unit)" #define P25_TDULC_STR "P25, TDULC (Terminator Data Unit with Link Control)" + + #define P25_KMM_STR "P25, KMM (Key Management Message)" + + #define P25_P2_VTCH_4V_STR "P25 Phase 2, VTCH 4V (Voice Traffic Channel 4Vx)" + #define P25_P2_SACCH_SCRAMBLED_STR "P25 Phase 2, SACCH (Slow Associated Control Channel Scrambled)" + #define P25_P2_VTCH_2V_STR "P25 Phase 2, VTCH 2V (Voice Traffic Channel 2V)" + #define P25_P2_FACCH_SCRAMBLED_STR "P25 Phase 2, FACCH (Fast Associated Control Channel Scrambled)" + #define P25_P2_SACCH_UNSCRAMBLED_STR "P25 Phase 2, SACCH (Slow Associated Control Channel Unscrambled)" + #define P25_P2_FACCH_UNSCRAMBLED_STR "P25 Phase 2, FACCH (Fast Associated Control Channel Unscrambled)" } // namespace defines } // namespace p25 diff --git a/src/common/p25/acl/AccessControl.cpp b/src/common/p25/acl/AccessControl.cpp index 2c0fd7915..aa29cde00 100644 --- a/src/common/p25/acl/AccessControl.cpp +++ b/src/common/p25/acl/AccessControl.cpp @@ -18,15 +18,15 @@ using namespace p25::acl; // Static Class Members // --------------------------------------------------------------------------- -RadioIdLookup* AccessControl::m_ridLookup; -TalkgroupRulesLookup* AccessControl::m_tidLookup; +RadioIdLookup* AccessControl::s_ridLookup; +TalkgroupRulesLookup* AccessControl::s_tidLookup; /* Initializes the P25 access control. */ void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLookup) { - m_ridLookup = ridLookup; - m_tidLookup = tidLookup; + s_ridLookup = ridLookup; + s_tidLookup = tidLookup; } /* Helper to validate a source radio ID. */ @@ -34,8 +34,8 @@ void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupRulesLookup* tidLook bool AccessControl::validateSrcId(uint32_t id) { // check if RID ACLs are enabled - if (!m_ridLookup->getACL()) { - RadioId rid = m_ridLookup->find(id); + if (!s_ridLookup->getACL()) { + RadioId rid = s_ridLookup->find(id); if (!rid.radioDefault() && !rid.radioEnabled()) { return false; } @@ -44,7 +44,7 @@ bool AccessControl::validateSrcId(uint32_t id) } // lookup RID and perform test for validity - RadioId rid = m_ridLookup->find(id); + RadioId rid = s_ridLookup->find(id); if (!rid.radioEnabled()) return false; @@ -64,12 +64,12 @@ bool AccessControl::validateTGId(uint32_t id, bool allowZero) return true; // check if TID ACLs are enabled - if (!m_tidLookup->getACL()) { + if (!s_tidLookup->getACL()) { return true; } // lookup TID and perform test for validity - TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + TalkgroupRuleGroupVoice tid = s_tidLookup->find(id); if (tid.isInvalid()) return false; @@ -88,12 +88,12 @@ bool AccessControl::tgidNonPreferred(uint32_t id) return false; // check if TID ACLs are enabled - if (!m_tidLookup->getACL()) { + if (!s_tidLookup->getACL()) { return false; } // lookup TID and perform test for validity - TalkgroupRuleGroupVoice tid = m_tidLookup->find(id); + TalkgroupRuleGroupVoice tid = s_tidLookup->find(id); if (tid.config().nonPreferred()) return true; diff --git a/src/common/p25/acl/AccessControl.h b/src/common/p25/acl/AccessControl.h index 7f1baf76d..de5e82b6e 100644 --- a/src/common/p25/acl/AccessControl.h +++ b/src/common/p25/acl/AccessControl.h @@ -67,8 +67,8 @@ namespace p25 static bool tgidNonPreferred(uint32_t id); private: - static RadioIdLookup* m_ridLookup; - static TalkgroupRulesLookup* m_tidLookup; + static RadioIdLookup* s_ridLookup; + static TalkgroupRulesLookup* s_tidLookup; }; } // namespace acl } // namespace p25 diff --git a/src/common/p25/data/Assembler.cpp b/src/common/p25/data/Assembler.cpp new file mode 100644 index 000000000..357cb22a3 --- /dev/null +++ b/src/common/p25/data/Assembler.cpp @@ -0,0 +1,520 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/data/Assembler.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +bool Assembler::s_dumpPDUData = false; +bool Assembler::s_verbose = false; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the Assembler class. */ + +Assembler::Assembler() : + dataBlocks(nullptr), + dataHeader(), + m_extendedAddress(false), + m_auxiliaryES(false), + m_dataBlockCnt(0U), + m_undecodableBlockCnt(0U), + m_packetCRCFailed(false), + m_complete(false), + m_pduUserData(nullptr), + m_pduUserDataLength(0U), + m_blockCount(0U), + m_dataOffset(0U), + m_usingCustomWriter(false), + m_blockWriter(nullptr) +{ + dataBlocks = new data::DataBlock[P25_MAX_PDU_BLOCKS]; + + m_pduUserData = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + + m_rawPDU = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(m_rawPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); +} + +/* Finalizes a instance of the Assembler class. */ + +Assembler::~Assembler() +{ + if (dataBlocks != nullptr) + delete[] dataBlocks; + if (m_pduUserData != nullptr) + delete[] m_pduUserData; +} + +/* Helper to disassemble a P25 PDU frame into user data. */ + +bool Assembler::disassemble(const uint8_t* pduBlock, uint32_t blockLength, bool resetState) +{ + assert(pduBlock != nullptr); + + if (resetState) { + resetDisassemblyState(); + } + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, PDU Disassembler Block", pduBlock, blockLength); +#endif + + UInt8Array dataArray = std::make_unique(P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + uint8_t* data = dataArray.get(); + ::memset(data, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + + if (m_blockCount == 0U) { + bool ret = dataHeader.decode(pduBlock); + if (!ret) { + LogWarning(LOG_P25, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", pduBlock, P25_PDU_FEC_LENGTH_BYTES); + + resetDisassemblyState(); + return false; + } + + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset(), dataHeader.getLLId()); + } + + // make sure we don't get a PDU with more blocks then we support + if (dataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { + LogError(LOG_P25, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", dataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); + + resetDisassemblyState(); + return false; + } + + m_blockCount++; + m_complete = false; + return true; + } + else { + ::memcpy(m_rawPDU + ((m_blockCount - 1U) * blockLength), pduBlock, blockLength); + m_dataOffset += blockLength; + m_blockCount++; + + if (m_blockCount - 1U >= dataHeader.getBlocksToFollow()) { +#if DEBUG_P25_PDU_DATA + LogDebugEx(LOG_P25, "Assembler::disassemble()", "complete PDU, blocksToFollow = %u, blockCount = %u", dataHeader.getBlocksToFollow(), m_blockCount); + Utils::dump(1U, "Assembler::disassemble() rawPDU", m_rawPDU, m_dataOffset); +#endif + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + uint32_t offset = 0U; + uint32_t dataOffset = 0U; + + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + + m_dataBlockCnt = 0U; + + // process all blocks in the data stream + // if the primary header has a header offset ensure data if offset by that amount + if (dataHeader.getHeaderOffset() > 0U) { + offset += dataHeader.getHeaderOffset(); + } + + uint32_t packetLength = dataHeader.getPacketLength(); + uint32_t padLength = dataHeader.getPadLength(); + uint32_t secondHeaderOffset = 0U; + + // decode data blocks + for (uint32_t i = 0U; i < blocksToFollow; i++) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + ::memcpy(buffer, m_rawPDU + offset, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = dataBlocks[i].decode(buffer, dataHeader); + if (ret) { + // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks + // set this block as the last block for full packet CRC + if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) || (dataHeader.getFormat() == PDUFormatType::UNCONFIRMED)) { + if ((m_dataBlockCnt + 1U) == blocksToFollow) { + dataBlocks[i].setLastBlock(true); + } + } + + // fake data block serial number for unconfirmed mode + if (dataHeader.getFormat() == PDUFormatType::UNCONFIRMED && dataBlocks[i].getSerialNo() == 0U) + dataBlocks[i].setSerialNo(i); + + // are we processing extended address data from the first block? + if (dataHeader.getSAP() == PDUSAP::EXT_ADDR && dataBlocks[i].getSerialNo() == 0U) { + uint8_t secondHeader[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; + ::memset(secondHeader, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + dataBlocks[i].getData(secondHeader); + + dataHeader.decodeExtAddr(secondHeader); + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, srcLlId = %u", + dataBlocks[i].getSerialNo(), dataBlocks[i].getFormat(), dataBlocks[i].getLastBlock(), dataHeader.getEXSAP(), dataHeader.getSrcLLId()); + } + + m_extendedAddress = true; + if (dataHeader.getFormat() == PDUFormatType::CONFIRMED) + secondHeaderOffset += 4U; + else + secondHeaderOffset += P25_PDU_HEADER_LENGTH_BYTES; + } + else { + // are we processing auxiliary ES data from the first block? + if ((dataHeader.getSAP() == PDUSAP::ENC_USER_DATA || dataHeader.getSAP() == PDUSAP::ENC_KMM) && + dataBlocks[i].getSerialNo() == 0U) { + uint8_t secondHeader[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; + ::memset(secondHeader, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + dataBlocks[i].getData(secondHeader); + + dataHeader.decodeAuxES(secondHeader); + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, algoId = $%02X, kId = $%04X", + dataBlocks[i].getSerialNo(), dataBlocks[i].getFormat(), dataBlocks[i].getLastBlock(), dataHeader.getEXSAP(), + dataHeader.getAlgId(), dataHeader.getKId()); + + if (dataHeader.getAlgId() != ALGO_UNENCRYPT) { + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + + dataHeader.getMI(mi); + + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, Enc Sync, block %u, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + dataBlocks[i].getSerialNo(), mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); + } + } + + m_auxiliaryES = true; + if (dataHeader.getFormat() == PDUFormatType::CONFIRMED) + secondHeaderOffset += 13U; + else + secondHeaderOffset += P25_PDU_HEADER_LENGTH_BYTES + 1U; + } + else { + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u", + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlocks[i].getSerialNo() : m_dataBlockCnt, dataBlocks[i].getFormat(), + dataBlocks[i].getLastBlock()); + } + } + } + + dataBlocks[i].getData(m_pduUserData + dataOffset); + + // is this the first unconfirmed data block after a auxiliary ES header? + if (i == 0U && dataHeader.getFormat() == PDUFormatType::UNCONFIRMED && m_auxiliaryES) { + uint8_t exSAP = m_pduUserData[0U]; // first byte of the first data block after an aux ES header is the extended SAP + dataHeader.setEXSAP(exSAP); + } + + dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + m_dataBlockCnt++; + } + else { + if (dataBlocks[i].getFormat() == PDUFormatType::CONFIRMED) { + LogWarning(LOG_P25, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC), block %u", i); + + // to prevent data block offset errors fill the bad block with 0's + uint8_t blankBuf[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; + ::memset(blankBuf, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + ::memcpy(m_pduUserData + dataOffset, blankBuf, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + + dataOffset += P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; + m_dataBlockCnt++; + m_undecodableBlockCnt++; + } + else { + LogWarning(LOG_P25, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); + + // to prevent data block offset errors fill the bad block with 0's + uint8_t blankBuf[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(blankBuf, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(m_pduUserData + dataOffset, blankBuf, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + dataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; + m_dataBlockCnt++; + m_undecodableBlockCnt++; + } + + if (s_dumpPDUData) { + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + } + } + + offset += P25_PDU_FEC_LENGTH_BYTES; + } + +#if DEBUG_P25_PDU_DATA + LogDebugEx(LOG_P25, "Assembler::disassemble()", "packetLength = %u, secondHeaderOffset = %u, padLength = %u, pduLength = %u", packetLength, secondHeaderOffset, padLength, dataHeader.getPDULength()); +#endif + if (dataHeader.getBlocksToFollow() > 0U) { + if (padLength > 0U) { + // move CRC-32 properly before padding to check CRC of user data + uint8_t crcBytes[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memset(crcBytes, 0x00U, packetLength); + ::memcpy(crcBytes, m_pduUserData, packetLength); + ::memcpy(crcBytes + packetLength, m_pduUserData + packetLength + padLength, 4U); + + bool crcRet = edac::CRC::checkCRC32(crcBytes, packetLength + 4U); + if (!crcRet) { + LogWarning(LOG_P25, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", dataHeader.getBlocksToFollow(), m_pduUserDataLength); + m_packetCRCFailed = true; + } + } else { + bool crcRet = edac::CRC::checkCRC32(m_pduUserData, packetLength + 4U); + if (!crcRet) { + LogWarning(LOG_P25, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", dataHeader.getBlocksToFollow(), m_pduUserDataLength); + m_packetCRCFailed = true; + } + } + } + + // reorganize PDU buffer for second header offsetting + if (secondHeaderOffset > 0U) { + uint8_t tempBuf[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memcpy(tempBuf, m_pduUserData + secondHeaderOffset, packetLength - 4U); + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + ::memcpy(m_pduUserData, tempBuf, packetLength - secondHeaderOffset); + } + + if (s_dumpPDUData && m_dataBlockCnt > 0U) { + Utils::dump(1U, "P25, PDU Packet", m_pduUserData, packetLength - secondHeaderOffset); + } + + if (m_dataBlockCnt < blocksToFollow) { + LogWarning(LOG_P25, P25_PDU_STR ", incomplete PDU (%d / %d blocks)", m_dataBlockCnt, blocksToFollow); + } + + m_pduUserDataLength = packetLength - secondHeaderOffset; + + m_blockCount = 0U; + m_complete = true; + return true; + } else { + return true; + } + } + + return false; +} + +/* Helper to assemble user data as a P25 PDU packet. */ + +UInt8Array Assembler::assemble(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, + const uint8_t* pduUserData, uint32_t* assembledBitLength, void* userContext) +{ + assert(pduUserData != nullptr); + + if (assembledBitLength != nullptr) + *assembledBitLength = 0U; + + uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; + + UInt8Array dataArray = std::make_unique((bitLength / 8U) + 1U); + uint8_t* data = dataArray.get(); + ::memset(data, 0x00U, (bitLength / 8U) + 1U); + + uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset(), bitLength, dataHeader.getLLId()); + } + + // generate the PDU header and 1/2 rate Trellis + dataHeader.encode(block); + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, PDU Assembler Block", block, P25_PDU_FEC_LENGTH_BYTES); +#endif + + if (m_usingCustomWriter && m_blockWriter != nullptr) + m_blockWriter(userContext, 0U, block, P25_PDU_FEC_LENGTH_BYTES, false); + else + Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); + offset += P25_PDU_FEC_LENGTH_BITS; + + if (pduUserData != nullptr && blocksToFollow > 0U) { + uint32_t dataOffset = 0U; + uint32_t pduLength = dataHeader.getPDULength() + dataHeader.getPadLength(); + uint32_t dataBlockCnt = 1U; + uint32_t secondHeaderOffset = 0U; + + // we pad 20 bytes of extra space -- confirmed data will use various extra space in the PDU + DECLARE_UINT8_ARRAY(packetData, pduLength + 20U); + + // are we processing extended address data from the first block? + if (dataHeader.getSAP() == PDUSAP::EXT_ADDR && extendedAddress) { + if (dataHeader.getFormat() == PDUFormatType::CONFIRMED) + secondHeaderOffset += 4U; + else + secondHeaderOffset += P25_PDU_HEADER_LENGTH_BYTES; + dataHeader.encodeExtAddr(packetData); + + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", + dataHeader.getEXSAP(), dataHeader.getSrcLLId()); + } + } + + // are we processing auxiliary ES data from the first block? + if ((dataHeader.getSAP() == PDUSAP::ENC_USER_DATA || dataHeader.getSAP() == PDUSAP::ENC_KMM) && auxiliaryES) { + if (dataHeader.getFormat() == PDUFormatType::CONFIRMED) + secondHeaderOffset += 13U; + else + secondHeaderOffset += P25_PDU_HEADER_LENGTH_BYTES + 1U; + dataHeader.encodeAuxES(packetData); + + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", OSP, auxiliary ES, algId = $%02X, kId = $%04X", + dataHeader.getAlgId(), dataHeader.getKId()); + + if (dataHeader.getAlgId() != ALGO_UNENCRYPT) { + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + + dataHeader.getMI(mi); + + LogInfoEx(LOG_P25, P25_PDU_STR ", OSP, Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); + } + } + } + + uint32_t packetLength = dataHeader.getPacketLength(); + uint32_t padLength = dataHeader.getPadLength(); +#if DEBUG_P25_PDU_DATA + LogDebugEx(LOG_P25, "Assembler::assemble()", "packetLength = %u, secondHeaderOffset = %u, padLength = %u, pduLength = %u", packetLength, secondHeaderOffset, padLength, pduLength); +#endif + if (dataHeader.getFormat() != PDUFormatType::AMBT) { + ::memcpy(packetData + secondHeaderOffset, pduUserData, packetLength); + edac::CRC::addCRC32(packetData, packetLength + 4U); + + if (padLength > 0U) { + // move the CRC-32 to the end of the packet data after the padding + uint8_t crcBytes[4U]; + ::memcpy(crcBytes, packetData + packetLength, 4U); + ::memset(packetData + packetLength, 0x00U, 4U); + ::memcpy(packetData + (packetLength + padLength), crcBytes, 4U); + } + } else { + // our AMBTs have a pre-calculated CRC-32 -- we don't need to do it ourselves + ::memcpy(packetData + secondHeaderOffset, pduUserData, pduLength); + } + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, Assembled PDU User Data", packetData, packetLength + padLength + 4U); +#endif + + // generate the PDU data + for (uint32_t i = 0U; i < blocksToFollow; i++) { + DataBlock dataBlock = DataBlock(); + dataBlock.setFormat(dataHeader); + dataBlock.setSerialNo(i); + dataBlock.setData(packetData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); + + if (s_verbose) { + LogInfoEx(LOG_P25, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), + dataBlock.getLastBlock()); + } + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataBlock.encode(block); + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, PDU Assembler Block", block, P25_PDU_FEC_LENGTH_BYTES); +#endif + + if (m_usingCustomWriter && m_blockWriter != nullptr) + m_blockWriter(userContext, dataBlockCnt, block, P25_PDU_FEC_LENGTH_BYTES, dataBlock.getLastBlock()); + else + Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + dataBlockCnt++; + } + } + + if (assembledBitLength != nullptr) + *assembledBitLength = bitLength; + + if (m_usingCustomWriter) { + return nullptr; + } + return dataArray; +} + +/* Gets the raw user user data stored. */ + +uint32_t Assembler::getUserData(uint8_t* buffer) const +{ + assert(buffer != nullptr); + assert(m_pduUserData != nullptr); + + if (m_complete) { + ::memcpy(buffer, m_pduUserData, m_pduUserDataLength); + return m_pduUserDataLength; + } else { + return 0U; + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to reset the disassembly state. */ + +void Assembler::resetDisassemblyState() +{ + dataHeader.reset(); + + m_extendedAddress = false; + m_auxiliaryES = false; + + m_dataBlockCnt = 0U; + m_undecodableBlockCnt = 0U; + + m_blockCount = 0U; + m_dataOffset = 0U; + + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + ::memset(m_rawPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); + + m_packetCRCFailed = false; + m_complete = false; +} diff --git a/src/common/p25/data/Assembler.h b/src/common/p25/data/Assembler.h new file mode 100644 index 000000000..85cbfef83 --- /dev/null +++ b/src/common/p25/data/Assembler.h @@ -0,0 +1,169 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file Assembler.h + * @ingroup p25_pdu + * @file Assembler.cpp + * @ingroup p25_pdu + */ +#if !defined(__P25_DATA__ASSEMBLER_H__) +#define __P25_DATA__ASSEMBLER_H__ + +#include "common/Defines.h" +#include "common/p25/data/DataBlock.h" +#include "common/p25/data/DataHeader.h" + +#include +#include + +namespace p25 +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements a packet assembler for P25 PDU packet streams. + * @ingroup p25_pdu + */ + class HOST_SW_API Assembler { + public: + /** + * @brief Initializes a new instance of the Assembler class. + */ + Assembler(); + /** + * @brief Finalizes a instance of the Assembler class. + */ + ~Assembler(); + + /** + * @brief Helper to disassemble a P25 PDU frame into user data. + * @param[in] pduBlock Buffer containing a PDU block to disassemble. + * @param blockLength Length of PDU block buffer. + * @param resetState Flag indicating the current disassembly state should be reset. + * @returns bool True, if entire P25 PDU packet is disassembled, otherwise false. + */ + bool disassemble(const uint8_t* pduBlock, uint32_t blockLength, bool resetState = false); + /** + * @brief Helper to assemble user data as a P25 PDU packet. + * @note When using a custom block writer, this will return null. + * @param dataHeader Instance of a PDU data header. + * @param extendedAddress Flag indicating whether or not extended addressing is in use. + * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. + * @param[in] pduUserData Buffer containing user data to assemble. + * @param[out] assembledBitLength Length of assembled packet in bits. + * @param[in] userContext User supplied context data to pass to custom block writer. + * @returns UInt8Array Assembled PDU buffer. + */ + UInt8Array assemble(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, const uint8_t* pduUserData, + uint32_t* assembledBitLength, void* userContext = nullptr); + + /** + * @brief Helper to set the custom block writer callback. + * @param callback + */ + void setBlockWriter(std::function&& callback) + { + m_blockWriter = callback; + if (callback == nullptr) + m_usingCustomWriter = false; + else + m_usingCustomWriter = true; + } + + /** + * @brief Gets the raw user user data stored. + * @param[out] buffer Buffer to copy bytes in data block to. + * @returns uint32_t Number of bytes copied. + */ + uint32_t getUserData(uint8_t* buffer) const; + /** + * @brief Gets the length of the raw user data stored. (This will include the 4 bytes for the CRC-32). + * @returns uint32_t Length of raw user data stored. + */ + uint32_t getUserDataLength() const { return m_pduUserDataLength; } + + /** + * @brief Sets the flag indicating whether or not the assembler will dump PDU data. + * @param dumpPDUData Flag indicating PDU log dumping. + */ + static void setDumpPDUData(bool dumpPDUData) { s_dumpPDUData = dumpPDUData; } + /** + * @brief Sets the flag indicating verbose log output. + * @param verbose Flag indicating verbose log output. + */ + static void setVerbose(bool verbose) { s_verbose = verbose; } + + public: + /** + * @brief Data Blocks in disassmbled packet. + */ + data::DataBlock* dataBlocks; + /** + * @brief Data Header from disassembled packet. + */ + data::DataHeader dataHeader; + + /** + * @brief Flag indicating the disassembled packet contains extended addressing. + */ + DECLARE_PROPERTY(bool, extendedAddress, ExtendedAddress); + /** + * @brief Flag indicating the disassembled packet contains an auxiliary ES. + */ + DECLARE_PROPERTY(bool, auxiliaryES, AuxiliaryES); + /** + * @brief Count of data blocks in disassmebled packet. + */ + DECLARE_PROPERTY(uint8_t, dataBlockCnt, DataBlockCount); + /** + * @brief Count of data blocks in disassmebled packet that were undecodable. + */ + DECLARE_PROPERTY(uint8_t, undecodableBlockCnt, UndecodableBlockCount); + + /** + * @brief Flag indicating resulting user data failed the CRC-32 check. + */ + DECLARE_PROPERTY(bool, packetCRCFailed, PacketCRCFailed); + /** + * @brief Flag indicating disassembly is complete and user data is available. + */ + DECLARE_PROPERTY(bool, complete, Complete); + + private: + uint8_t* m_pduUserData; + uint32_t m_pduUserDataLength; + + uint8_t* m_rawPDU; + + uint32_t m_blockCount; + uint32_t m_dataOffset; + + static bool s_dumpPDUData; + static bool s_verbose; + bool m_usingCustomWriter; + + /** + * @brief Custom block writing callback. + */ + std::function m_blockWriter; + + /** + * @brief Internal helper to reset the disassembly state. + */ + void resetDisassemblyState(); + }; + } // namespace data +} // namespace p25 + +#endif // __P25_DATA__ASSEMBLER_H__ diff --git a/src/common/p25/data/DataBlock.cpp b/src/common/p25/data/DataBlock.cpp index e750ef32b..63e346d57 100644 --- a/src/common/p25/data/DataBlock.cpp +++ b/src/common/p25/data/DataBlock.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2018-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2018-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -25,6 +25,13 @@ using namespace p25::data; // Public Class Members // --------------------------------------------------------------------------- +/* Initializes a copy instance of the DataBlock class. */ + +DataBlock::DataBlock(const DataBlock& data) : DataBlock() +{ + copy(data); +} + /* Initializes a new instance of the DataBlock class. */ DataBlock::DataBlock() : @@ -42,13 +49,15 @@ DataBlock::DataBlock() : DataBlock::~DataBlock() { - if (m_data != nullptr) + if (m_data != nullptr) { delete[] m_data; + m_data = nullptr; + } } /* Decodes P25 PDU data block. */ -bool DataBlock::decode(const uint8_t* data, const DataHeader& header) +bool DataBlock::decode(const uint8_t* data, const DataHeader& header, bool noTrellis) { assert(data != nullptr); assert(m_data != nullptr); @@ -66,10 +75,14 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) if (m_fmt == PDUFormatType::CONFIRMED) { // decode 3/4 rate Trellis try { - bool valid = m_trellis.decode34(data, buffer); - if (!valid) { - LogError(LOG_P25, "DataBlock::decode(), failed to decode Trellis 3/4 rate coding"); - return false; + if (!noTrellis) { + bool valid = m_trellis.decode34(data, buffer); + if (!valid) { + LogError(LOG_P25, "DataBlock::decode(), failed to decode Trellis 3/4 rate coding"); + return false; + } + } else { + ::memcpy(buffer, data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); } #if DEBUG_P25_PDU_DATA @@ -95,10 +108,15 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) } } +#if DEBUG_P25_PDU_DATA + Utils::dump(2U, "P25, CRC Bit Buffer", crcBuffer, P25_PDU_CONFIRMED_LENGTH_BYTES); +#endif + // compute CRC-9 for the packet uint16_t calculated = edac::CRC::createCRC9(crcBuffer, 135U); if ((crc ^ calculated) != 0) { - LogWarning(LOG_P25, "PDU, fmt = $%02X, invalid crc = $%04X != $%04X (computed)", m_fmt, crc, calculated); + LogError(LOG_P25, "PDU, fmt = $%02X, invalid crc = $%04X != $%04X (computed)", m_fmt, crc, calculated); + return false; } #if DEBUG_P25_PDU_DATA @@ -113,10 +131,14 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) else if ((m_fmt == PDUFormatType::UNCONFIRMED) || (m_fmt == PDUFormatType::RSP) || (m_fmt == PDUFormatType::AMBT)) { // decode 1/2 rate Trellis try { - bool valid = m_trellis.decode12(data, buffer); - if (!valid) { - LogError(LOG_P25, "DataBlock::decode(), failed to decode Trellis 1/2 rate coding"); - return false; + if (!noTrellis) { + bool valid = m_trellis.decode12(data, buffer); + if (!valid) { + LogError(LOG_P25, "DataBlock::decode(), failed to decode Trellis 1/2 rate coding"); + return false; + } + } else { + ::memcpy(buffer, data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); } ::memset(m_data, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); @@ -141,7 +163,7 @@ bool DataBlock::decode(const uint8_t* data, const DataHeader& header) /* Encodes a P25 PDU data block. */ -void DataBlock::encode(uint8_t* data) +void DataBlock::encode(uint8_t* data, bool noTrellis) { assert(data != nullptr); assert(m_data != nullptr); @@ -175,7 +197,11 @@ void DataBlock::encode(uint8_t* data) Utils::dump(1U, "P25, DataBlock::encode(), Confirmed PDU Data Block", buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); #endif - m_trellis.encode34(buffer, data); + if (!noTrellis) { + m_trellis.encode34(buffer, data); + } else { + ::memcpy(data, buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); + } } else if (m_fmt == PDUFormatType::UNCONFIRMED || m_fmt == PDUFormatType::RSP || m_fmt == PDUFormatType::AMBT) { uint8_t buffer[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; @@ -187,7 +213,11 @@ void DataBlock::encode(uint8_t* data) Utils::dump(1U, "P25, DataBlock::encode(), Unconfirmed PDU Data Block", buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); #endif - m_trellis.encode12(buffer, data); + if (!noTrellis) { + m_trellis.encode12(buffer, data); + } else { + ::memcpy(data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + } } else { LogError(LOG_P25, "unknown FMT value in PDU, fmt = $%02X", m_fmt); @@ -254,3 +284,22 @@ uint32_t DataBlock::getData(uint8_t* buffer) const return 0U; } } + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void DataBlock::copy(const DataBlock& data) +{ + m_serialNo = data.m_serialNo; + m_lastBlock = data.m_lastBlock; + + m_fmt = data.m_fmt; + m_headerSap = data.m_headerSap; + + if (m_data != nullptr && data.m_data != nullptr) { + ::memcpy(m_data, data.m_data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + } +} diff --git a/src/common/p25/data/DataBlock.h b/src/common/p25/data/DataBlock.h index 303e8b0b0..4bf7b9fa2 100644 --- a/src/common/p25/data/DataBlock.h +++ b/src/common/p25/data/DataBlock.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2018-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2018-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -36,6 +36,11 @@ namespace p25 */ class HOST_SW_API DataBlock { public: + /** + * @brief Initializes a copy instance of the DataBlock class. + * @param data Instance of DataBlock class to copy from. + */ + DataBlock(const DataBlock& data); /** * @brief Initializes a new instance of the DataBlock class. */ @@ -49,14 +54,16 @@ namespace p25 * @brief Decodes P25 PDU data block. * @param[in] data Buffer containing a PDU data block to decode. * @param header P25 PDU data header. + * @param noTrellis Flag indicating not to perform Trellis encoding. * @returns bool True, if PDU data block decoded, otherwise false. */ - bool decode(const uint8_t* data, const DataHeader& header); + bool decode(const uint8_t* data, const DataHeader& header, bool noTrellis = false); /** * @brief Encodes a P25 PDU data block. * @param[out] data Buffer to encode a PDU data block. + * @param noTrellis Flag indicating not to perform Trellis encoding. */ - void encode(uint8_t* data); + void encode(uint8_t* data, bool noTrellis = false); /** * @brief Sets the data format. @@ -104,6 +111,11 @@ namespace p25 uint8_t m_headerSap; uint8_t* m_data; + + /** + * @brief Internal helper to copy the class. + */ + void copy(const DataBlock& data); }; } // namespace data } // namespace p25 diff --git a/src/common/p25/data/DataHeader.cpp b/src/common/p25/data/DataHeader.cpp index 211953dac..ee6d3f174 100644 --- a/src/common/p25/data/DataHeader.cpp +++ b/src/common/p25/data/DataHeader.cpp @@ -27,15 +27,22 @@ using namespace p25::data; // --------------------------------------------------------------------------- #if FORCE_TSBK_CRC_WARN -bool DataHeader::m_warnCRC = true; +bool DataHeader::s_warnCRC = true; #else -bool DataHeader::m_warnCRC = false; +bool DataHeader::s_warnCRC = false; #endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- +/* Initializes a copy instance of the DataHeader class. */ + +DataHeader::DataHeader(const DataHeader& data) : DataHeader() +{ + copy(data); +} + /* Initializes a new instance of the DataHeader class. */ DataHeader::DataHeader() : @@ -61,22 +68,48 @@ DataHeader::DataHeader() : m_ambtOpcode(0U), m_ambtField8(0U), m_ambtField9(0U), + m_algId(ALGO_UNENCRYPT), + m_kId(0U), m_trellis(), m_data(nullptr), - m_extAddrData(nullptr) + m_extAddrData(nullptr), + m_auxESData(nullptr), + m_mi(nullptr) { m_data = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(m_data, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); m_extAddrData = new uint8_t[P25_PDU_HEADER_LENGTH_BYTES]; ::memset(m_extAddrData, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + m_auxESData = new uint8_t[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; + ::memset(m_auxESData, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); } /* Finalizes a instance of the DataHeader class. */ DataHeader::~DataHeader() { - delete[] m_data; - delete[] m_extAddrData; + if (m_data != nullptr) { + delete[] m_data; + m_data = nullptr; + } + + if (m_extAddrData != nullptr) { + delete[] m_extAddrData; + m_extAddrData = nullptr; + } + + if (m_auxESData != nullptr) { + delete[] m_auxESData; + m_auxESData = nullptr; + } + + if (m_mi != nullptr) { + delete[] m_mi; + m_mi = nullptr; + } } /* Decodes P25 PDU data header. */ @@ -97,7 +130,7 @@ bool DataHeader::decode(const uint8_t* data, bool noTrellis) if (valid) { valid = edac::CRC::checkCCITT162(m_data, P25_PDU_HEADER_LENGTH_BYTES); if (!valid) { - if (m_warnCRC) { + if (s_warnCRC) { // if we're already warning instead of erroring CRC, don't announce invalid CRC in the // case where no CRC is defined if ((m_data[P25_PDU_HEADER_LENGTH_BYTES - 2U] != 0x00U) && (m_data[P25_PDU_HEADER_LENGTH_BYTES - 1U] != 0x00U)) { @@ -264,7 +297,7 @@ void DataHeader::encode(uint8_t* data, bool noTrellis) /* Decodes P25 PDU extended addressing header. */ -bool DataHeader::decodeExtAddr(const uint8_t* data, bool noTrellis) +bool DataHeader::decodeExtAddr(const uint8_t* data) { assert(data != nullptr); @@ -282,30 +315,22 @@ bool DataHeader::decodeExtAddr(const uint8_t* data, bool noTrellis) m_srcLlId = (m_extAddrData[0U] << 16) + (m_extAddrData[1U] << 8) + // Source Logical Link ID m_extAddrData[2U]; } else if (m_fmt == PDUFormatType::UNCONFIRMED) { - // decode 1/2 rate Trellis & check CRC-CCITT 16 - bool valid = true; - if (noTrellis) { - ::memcpy(m_extAddrData, data, P25_PDU_HEADER_LENGTH_BYTES); - } - else { - valid = m_trellis.decode12(data, m_extAddrData); - } + ::memcpy(m_extAddrData, data, P25_PDU_HEADER_LENGTH_BYTES); - if (valid) { - valid = edac::CRC::checkCCITT162(m_extAddrData, P25_PDU_HEADER_LENGTH_BYTES); - if (!valid) { - if (m_warnCRC) { - // if we're already warning instead of erroring CRC, don't announce invalid CRC in the - // case where no CRC is defined - if ((m_extAddrData[P25_PDU_HEADER_LENGTH_BYTES - 2U] != 0x00U) && (m_extAddrData[P25_PDU_HEADER_LENGTH_BYTES - 1U] != 0x00U)) { - LogWarning(LOG_P25, "DataHeader::decodeExtAddr(), failed CRC CCITT-162 check"); - } - - valid = true; // ignore CRC error - } - else { - LogError(LOG_P25, "DataHeader::decodeExtAddr(), failed CRC CCITT-162 check"); + // check CRC-CCITT 16 + bool valid = edac::CRC::checkCCITT162(m_extAddrData, P25_PDU_HEADER_LENGTH_BYTES); + if (!valid) { + if (s_warnCRC) { + // if we're already warning instead of erroring CRC, don't announce invalid CRC in the + // case where no CRC is defined + if ((m_extAddrData[P25_PDU_HEADER_LENGTH_BYTES - 2U] != 0x00U) && (m_extAddrData[P25_PDU_HEADER_LENGTH_BYTES - 1U] != 0x00U)) { + LogWarning(LOG_P25, "DataHeader::decodeExtAddr(), failed CRC CCITT-162 check"); } + + valid = true; // ignore CRC error + } + else { + LogError(LOG_P25, "DataHeader::decodeExtAddr(), failed CRC CCITT-162 check"); } } @@ -327,7 +352,7 @@ bool DataHeader::decodeExtAddr(const uint8_t* data, bool noTrellis) /* Encodes P25 PDU extended addressing header. */ -void DataHeader::encodeExtAddr(uint8_t* data, bool noTrellis) +void DataHeader::encodeExtAddr(uint8_t* data) { assert(data != nullptr); @@ -346,7 +371,7 @@ void DataHeader::encodeExtAddr(uint8_t* data, bool noTrellis) header[2U] = (m_srcLlId >> 0) & 0xFFU; #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25, DataHeader::encodeExtAddr(), PDU Header Data", header, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, DataHeader::encodeExtAddr(), PDU Extended Address Data", header, P25_PDU_HEADER_LENGTH_BYTES); #endif ::memcpy(data, header, 4U); // only copy the 4 bytes of header data for confirmed @@ -366,22 +391,130 @@ void DataHeader::encodeExtAddr(uint8_t* data, bool noTrellis) edac::CRC::addCCITT162(header, P25_PDU_HEADER_LENGTH_BYTES); #if DEBUG_P25_PDU_DATA - Utils::dump(1U, "P25, DataHeader::encodeExtAddr(), PDU Header Data", header, P25_PDU_HEADER_LENGTH_BYTES); + Utils::dump(1U, "P25, DataHeader::encodeExtAddr(), PDU Extended Address Data", header, P25_PDU_HEADER_LENGTH_BYTES); +#endif + ::memcpy(data, header, P25_PDU_HEADER_LENGTH_BYTES); + } +} + +/* Decodes P25 PDU auxiliary ES header. */ + +bool DataHeader::decodeAuxES(const uint8_t* data) +{ + assert(data != nullptr); + + ::memset(m_auxESData, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + + if (m_sap != PDUSAP::ENC_USER_DATA && m_sap != PDUSAP::ENC_KMM) + return false; + + if (m_fmt == PDUFormatType::CONFIRMED) { + ::memcpy(m_auxESData, data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, DataHeader::decodeAuxES(), PDU Auxiliary ES Data", m_auxESData, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); +#endif + + m_algId = m_auxESData[9U]; // Algorithm ID + if (m_algId != ALGO_UNENCRYPT) { + if (m_mi != nullptr) + delete[] m_mi; + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, m_auxESData, MI_LENGTH_BYTES); // Message Indicator + + m_kId = (m_auxESData[10U] << 8) + m_auxESData[11U]; // Key ID + } + else { + if (m_mi != nullptr) + delete[] m_mi; + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_kId = 0x0000U; + } + + m_exSap = m_auxESData[12U] & 0x3FU; // Service Access Point + } else if (m_fmt == PDUFormatType::UNCONFIRMED) { + ::memcpy(m_auxESData, data, P25_PDU_HEADER_LENGTH_BYTES); + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, DataHeader::decodeAuxES(), PDU Auxiliary ES Data", m_auxESData, P25_PDU_HEADER_LENGTH_BYTES); #endif - if (!noTrellis) { - // encode 1/2 rate Trellis - m_trellis.encode12(header, data); - } else { - ::memcpy(data, header, P25_PDU_HEADER_LENGTH_BYTES); + m_algId = m_auxESData[9U]; // Algorithm ID + if (m_algId != ALGO_UNENCRYPT) { + if (m_mi != nullptr) + delete[] m_mi; + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, m_auxESData, MI_LENGTH_BYTES); // Message Indicator + + m_kId = (m_auxESData[10U] << 8) + m_auxESData[11U]; // Key ID + } + else { + if (m_mi != nullptr) + delete[] m_mi; + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + + m_kId = 0x0000U; } } + + return true; +} + +/* Encodes P25 PDU auxiliary ES header. */ + +void DataHeader::encodeAuxES(uint8_t* data) +{ + assert(data != nullptr); + + if (m_sap != PDUSAP::ENC_USER_DATA && m_sap != PDUSAP::ENC_KMM) + return; + + uint8_t header[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + + if (m_fmt == PDUFormatType::CONFIRMED) { + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + header[i] = m_mi[i]; // Message Indicator + + header[9U] = m_algId; // Algorithm ID + header[10U] = (m_kId >> 8) & 0xFFU; // Key ID + header[11U] = (m_kId >> 0) & 0xFFU; // ... + + header[12U] = m_exSap & 0x3FU; // Service Access Point + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, DataHeader::encodeExtAddr(), PDU Auxiliary ES Data", header, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); +#endif + + ::memcpy(data, header, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + } else if (m_fmt == PDUFormatType::UNCONFIRMED) { + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + header[i] = m_mi[i]; // Message Indicator + + header[9U] = m_algId; // Algorithm ID + header[10U] = (m_kId >> 8) & 0xFFU; // Key ID + header[11U] = (m_kId >> 0) & 0xFFU; // ... + + header[12U] = m_exSap & 0x3FU; // Service Access Point + +#if DEBUG_P25_PDU_DATA + Utils::dump(1U, "P25, DataHeader::encodeAuxES(), PDU Auxiliary ES Data", header, P25_PDU_HEADER_LENGTH_BYTES); +#endif + ::memcpy(data, header, P25_PDU_HEADER_LENGTH_BYTES + 1U); + } } /* Helper to reset data values to defaults. */ void DataHeader::reset() { + if (m_data == nullptr) + return; // bail bail bail + m_ackNeeded = false; m_outbound = false; @@ -414,7 +547,18 @@ void DataHeader::reset() m_ambtField8 = 0U; m_ambtField9 = 0U; - ::memset(m_data, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + m_algId = ALGO_UNENCRYPT; + m_kId = 0U; + + if (m_data != nullptr) + ::memset(m_data, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + if (m_mi != nullptr) { + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + } else { + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + } } /* Gets the total length in bytes of enclosed packet data. */ @@ -471,6 +615,22 @@ uint32_t DataHeader::getExtAddrData(uint8_t* buffer) const return P25_PDU_HEADER_LENGTH_BYTES; } +/* Gets the raw auxiliary ES header data. */ + +uint32_t DataHeader::getAuxiliaryESData(uint8_t* buffer) const +{ + assert(buffer != nullptr); + assert(m_auxESData != nullptr); + + if (m_fmt == PDUFormatType::CONFIRMED) { + ::memcpy(buffer, m_auxESData, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + return P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; + } else { + ::memcpy(buffer, m_auxESData, P25_PDU_HEADER_LENGTH_BYTES); + return P25_PDU_HEADER_LENGTH_BYTES; + } +} + /* Helper to calculate the number of blocks to follow and padding length for a PDU. */ void DataHeader::calculateLength(uint32_t packetLength) @@ -484,6 +644,10 @@ void DataHeader::calculateLength(uint32_t packetLength) len += 4U; } + if ((m_sap == PDUSAP::ENC_USER_DATA || m_sap == PDUSAP::ENC_KMM)) { + len += 13U; + } + uint32_t blockLen = (m_fmt == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; if (len > blockLen) { @@ -507,3 +671,83 @@ uint32_t DataHeader::calculatePadLength(uint8_t fmt, uint32_t packetLength) return P25_PDU_UNCONFIRMED_LENGTH_BYTES - (len % P25_PDU_UNCONFIRMED_LENGTH_BYTES); } } + +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void DataHeader::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void DataHeader::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void DataHeader::copy(const DataHeader& data) +{ + m_ackNeeded = data.m_ackNeeded; + m_outbound = data.m_outbound; + + m_fmt = data.m_fmt; + m_sap = data.m_sap; + + m_mfId = data.m_mfId; + m_llId = data.m_llId; + + m_blocksToFollow = data.m_blocksToFollow; + m_padLength = data.m_padLength; + + m_F = data.m_F; + m_S = data.m_S; + m_fsn = data.m_fsn; + m_Ns = data.m_Ns; + m_lastFragment = data.m_lastFragment; + m_headerOffset = data.m_headerOffset; + + m_exSap = data.m_exSap; + m_srcLlId = data.m_srcLlId; + + m_rspClass = data.m_rspClass; + m_rspType = data.m_rspType; + m_rspStatus = data.m_rspStatus; + + m_ambtOpcode = data.m_ambtOpcode; + m_ambtField8 = data.m_ambtField8; + m_ambtField9 = data.m_ambtField9; + + m_algId = data.m_algId; + m_kId = data.m_kId; + + if (m_data != nullptr && data.m_data != nullptr) { + ::memcpy(m_data, data.m_data, P25_PDU_HEADER_LENGTH_BYTES); + } + + if (m_extAddrData != nullptr && data.m_extAddrData != nullptr) { + ::memcpy(m_extAddrData, data.m_extAddrData, P25_PDU_HEADER_LENGTH_BYTES); + } + + if (m_auxESData != nullptr && data.m_auxESData != nullptr) { + ::memcpy(m_auxESData, data.m_auxESData, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + } + + if (m_mi != nullptr && data.m_mi != nullptr) { + ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); + } +} diff --git a/src/common/p25/data/DataHeader.h b/src/common/p25/data/DataHeader.h index d59c8c5c4..93b5fa476 100644 --- a/src/common/p25/data/DataHeader.h +++ b/src/common/p25/data/DataHeader.h @@ -39,6 +39,11 @@ namespace p25 */ class HOST_SW_API DataHeader { public: + /** + * @brief Initializes a copy instance of the DataHeader class. + * @param data Instance of DataHeader class to copy from. + */ + DataHeader(const DataHeader& data); /** * @brief Initializes a new instance of the DataHeader class. */ @@ -65,16 +70,26 @@ namespace p25 /** * @brief Decodes P25 PDU extended addressing header. * @param[in] data Buffer containing a PDU data header to decode. - * @param noTrellis Flag indicating not to perform Trellis encoding. * @returns bool True, if PDU data header decoded, otherwise false. */ - bool decodeExtAddr(const uint8_t* data, bool noTrellis = false); + bool decodeExtAddr(const uint8_t* data); /** * @brief Encodes P25 PDU extended addressing header. * @param[out] data Buffer to encode a PDU data header. - * @param noTrellis Flag indicating not to perform Trellis encoding. */ - void encodeExtAddr(uint8_t* data, bool noTrellis = false); + void encodeExtAddr(uint8_t* data); + + /** + * @brief Decodes P25 PDU auxiliary ES header. + * @param[in] data Buffer containing a PDU data header to decode. + * @returns bool True, if PDU data header decoded, otherwise false. + */ + bool decodeAuxES(const uint8_t* data); + /** + * @brief Encodes P25 PDU auxiliary ES header. + * @param[out] data Buffer to encode a PDU data header. + */ + void encodeAuxES(uint8_t* data); /** * @brief Helper to reset data values to defaults. @@ -104,6 +119,12 @@ namespace p25 * @returns uint32_t Length of data copied. */ uint32_t getExtAddrData(uint8_t* buffer) const; + /** + * @brief Gets the raw auxiliary ES header data. + * @param[out] buffer Buffer to copy raw header data to. + * @returns uint32_t Length of data copied. + */ + uint32_t getAuxiliaryESData(uint8_t* buffer) const; /** * @brief Helper to calculate the number of blocks to follow and padding length for a PDU. @@ -115,7 +136,7 @@ namespace p25 * @brief Sets the flag indicating CRC-errors should be warnings and not errors. * @param warnCRC Flag indicating CRC-errors should be treated as warnings. */ - static void setWarnCRC(bool warnCRC) { m_warnCRC = warnCRC; } + static void setWarnCRC(bool warnCRC) { s_warnCRC = warnCRC; } /** * @brief Helper to determine the pad length for a given packet length. @@ -125,6 +146,19 @@ namespace p25 */ static uint32_t calculatePadLength(uint8_t fmt, uint32_t packetLength); + /** @name Encryption data */ + /** + * @brief Sets the encryption message indicator. + * @param[in] mi Buffer containing the 9-byte Message Indicator. + */ + void setMI(const uint8_t* mi); + /** + * @brief Gets the encryption message indicator. + * @param[out] mi Buffer containing the 9-byte Message Indicator. + */ + void getMI(uint8_t* mi) const; + /** @} */ + public: /** * @brief Flag indicating if acknowledgement is needed. @@ -226,13 +260,33 @@ namespace p25 DECLARE_PROPERTY(uint8_t, ambtField9, AMBTField9); /** @} */ + /** @name Encryption data */ + /** + * @brief Encryption algorithm ID. + */ + DECLARE_PROPERTY(uint8_t, algId, AlgId); + /** + * @brief Encryption key ID. + */ + DECLARE_PROPERTY(uint32_t, kId, KId); + /** @} */ + private: edac::Trellis m_trellis; uint8_t* m_data; uint8_t* m_extAddrData; - - static bool m_warnCRC; + uint8_t* m_auxESData; + + // Encryption data + uint8_t* m_mi; + + static bool s_warnCRC; + + /** + * @brief Internal helper to copy the class. + */ + void copy(const DataHeader& data); }; } // namespace data } // namespace p25 diff --git a/src/common/p25/dfsi/DFSIDefines.h b/src/common/p25/dfsi/DFSIDefines.h index 9630cd993..29a73283f 100644 --- a/src/common/p25/dfsi/DFSIDefines.h +++ b/src/common/p25/dfsi/DFSIDefines.h @@ -49,6 +49,8 @@ namespace p25 const uint32_t DFSI_MOT_TSBK_LEN = 24U; const uint32_t DFSI_MOT_TDULC_LEN = 21U; + const uint32_t DFSI_PDU_BLOCK_CNT = 4U; + const uint32_t DFSI_TIA_VHDR_LEN = 22U; const uint32_t DFSI_MOT_ICW_LENGTH = 6U; @@ -73,64 +75,90 @@ namespace p25 const uint32_t DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES = 17U; const uint32_t DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES = 16U; + const uint32_t DFSI_P2_4V_FRAME_LENGTH_BYTES = 39U; + const uint32_t DFSI_P2_2V_FRAME_LENGTH_BYTES = 32U; + /** * @addtogroup p25_dfsi * @{ */ - const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //! - const uint8_t DFSI_RTP_MOT_PAYLOAD_TYPE = 0x5DU; //! + const uint8_t DFSI_RTP_PAYLOAD_TYPE = 0x64U; //!< + const uint8_t DFSI_RTP_MOT_PAYLOAD_TYPE = 0x5DU; //!< + const uint8_t DFSI_RTP_MOT_DATA_PAYLOAD_TYPE = 0x5EU; //!< - const uint8_t DFSI_RTP_SEQ_HANDSHAKE = 0x00U; //! - const uint8_t DFSI_RTP_SEQ_STARTSTOP = 0x01U; //! + const uint8_t DFSI_RTP_SEQ_HANDSHAKE = 0x00U; //!< + const uint8_t DFSI_RTP_SEQ_STARTSTOP = 0x01U; //!< - const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //! + const uint8_t DFSI_MOT_ICW_FMT_TYPE3 = 0x02U; //!< - const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //! No Operation - const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //! Stream Payload - const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //! RSSI Data - const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //! RSSI Data - const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //! Stop Stream - const uint8_t DFSI_MOT_ICW_TX_ADDRESS = 0x2CU; //! Tx Device Address - const uint8_t DFSI_MOT_ICW_RX_ADDRESS = 0x35U; //! Rx Device Address + const uint8_t DFSI_MOT_ICW_PARM_NOP = 0x00U; //!< No Operation + const uint8_t DSFI_MOT_ICW_PARM_PAYLOAD = 0x0CU; //!< Stream Payload + const uint8_t DFSI_MOT_ICW_PARM_RSSI1 = 0x1AU; //!< RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_RSSI2 = 0x1BU; //!< RSSI Data + const uint8_t DFSI_MOT_ICW_PARM_STOP = 0x25U; //!< Stop Stream + const uint8_t DFSI_MOT_ICW_TX_ADDRESS = 0x2CU; //!< Tx Device Address + const uint8_t DFSI_MOT_ICW_RX_ADDRESS = 0x35U; //!< Rx Device Address - const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //! Talkaround - const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //! Busy - const uint8_t DFSI_BUSY_BITS_INBOUND = 0x02U; //! Inbound - const uint8_t DFSI_BUSY_BITS_IDLE = 0x03U; //! Idle + const uint8_t DFSI_BUSY_BITS_TALKAROUND = 0x00U; //!< Talkaround + const uint8_t DFSI_BUSY_BITS_BUSY = 0x01U; //!< Busy + const uint8_t DFSI_BUSY_BITS_INBOUND = 0x02U; //!< Inbound + const uint8_t DFSI_BUSY_BITS_IDLE = 0x03U; //!< Idle /** @brief DFSI Frame Type */ namespace DFSIFrameType { /** @brief DFSI Frame Type */ enum E : uint8_t { - MOT_START_STOP = 0x00U, // Motorola/V.24 Start/Stop Stream - - MOT_VHDR_1 = 0x60U, // Motorola/V.24 Voice Header 1 - MOT_VHDR_2 = 0x61U, // Motorola/V.24 Voice Header 2 - - LDU1_VOICE1 = 0x62U, // IMBE LDU1 - Voice 1 - LDU1_VOICE2 = 0x63U, // IMBE LDU1 - Voice 2 - LDU1_VOICE3 = 0x64U, // IMBE LDU1 - Voice 3 + Link Control - LDU1_VOICE4 = 0x65U, // IMBE LDU1 - Voice 4 + Link Control - LDU1_VOICE5 = 0x66U, // IMBE LDU1 - Voice 5 + Link Control - LDU1_VOICE6 = 0x67U, // IMBE LDU1 - Voice 6 + Link Control - LDU1_VOICE7 = 0x68U, // IMBE LDU1 - Voice 7 + Link Control - LDU1_VOICE8 = 0x69U, // IMBE LDU1 - Voice 8 + Link Control - LDU1_VOICE9 = 0x6AU, // IMBE LDU1 - Voice 9 + Low Speed Data - - LDU2_VOICE10 = 0x6BU, // IMBE LDU2 - Voice 10 - LDU2_VOICE11 = 0x6CU, // IMBE LDU2 - Voice 11 - LDU2_VOICE12 = 0x6DU, // IMBE LDU2 - Voice 12 + Encryption Sync - LDU2_VOICE13 = 0x6EU, // IMBE LDU2 - Voice 13 + Encryption Sync - LDU2_VOICE14 = 0x6FU, // IMBE LDU2 - Voice 14 + Encryption Sync - LDU2_VOICE15 = 0x70U, // IMBE LDU2 - Voice 15 + Encryption Sync - LDU2_VOICE16 = 0x71U, // IMBE LDU2 - Voice 16 + Encryption Sync - LDU2_VOICE17 = 0x72U, // IMBE LDU2 - Voice 17 + Encryption Sync - LDU2_VOICE18 = 0x73U, // IMBE LDU2 - Voice 18 + Low Speed Data - - MOT_TDULC = 0x74U, // Motorola/V.24 TDULC - MOT_PDU_SINGLE = 0x87U, // Motorola/V.24 PDU (Single Block) - MOT_TSBK = 0xA1U // Motorola/V.24 TSBK (Single Block) + MOT_START_STOP = 0x00U, //!< Motorola/V.24 Start/Stop Stream + + MOT_VHDR_1 = 0x60U, //!< Motorola/V.24 Voice Header 1 + MOT_VHDR_2 = 0x61U, //!< Motorola/V.24 Voice Header 2 + + LDU1_VOICE1 = 0x62U, //!< IMBE LDU1 - Voice 1 + LDU1_VOICE2 = 0x63U, //!< IMBE LDU1 - Voice 2 + LDU1_VOICE3 = 0x64U, //!< IMBE LDU1 - Voice 3 + Link Control + LDU1_VOICE4 = 0x65U, //!< IMBE LDU1 - Voice 4 + Link Control + LDU1_VOICE5 = 0x66U, //!< IMBE LDU1 - Voice 5 + Link Control + LDU1_VOICE6 = 0x67U, //!< IMBE LDU1 - Voice 6 + Link Control + LDU1_VOICE7 = 0x68U, //!< IMBE LDU1 - Voice 7 + Link Control + LDU1_VOICE8 = 0x69U, //!< IMBE LDU1 - Voice 8 + Link Control + LDU1_VOICE9 = 0x6AU, //!< IMBE LDU1 - Voice 9 + Low Speed Data + + LDU2_VOICE10 = 0x6BU, //!< IMBE LDU2 - Voice 10 + LDU2_VOICE11 = 0x6CU, //!< IMBE LDU2 - Voice 11 + LDU2_VOICE12 = 0x6DU, //!< IMBE LDU2 - Voice 12 + Encryption Sync + LDU2_VOICE13 = 0x6EU, //!< IMBE LDU2 - Voice 13 + Encryption Sync + LDU2_VOICE14 = 0x6FU, //!< IMBE LDU2 - Voice 14 + Encryption Sync + LDU2_VOICE15 = 0x70U, //!< IMBE LDU2 - Voice 15 + Encryption Sync + LDU2_VOICE16 = 0x71U, //!< IMBE LDU2 - Voice 16 + Encryption Sync + LDU2_VOICE17 = 0x72U, //!< IMBE LDU2 - Voice 17 + Encryption Sync + LDU2_VOICE18 = 0x73U, //!< IMBE LDU2 - Voice 18 + Low Speed Data + + MOT_TDULC = 0x74U, //!< Motorola/V.24 TDULC + + MOT_PDU_UNCONF_HEADER = 0x80U, //!< Motorola/V.24 PDU (Unconfirmed Block Header) + MOT_PDU_UNCONF_BLOCK_1 = 0x81U, //!< Motorola/V.24 PDU (Unconfirmed Block 1) + MOT_PDU_UNCONF_BLOCK_2 = 0x82U, //!< Motorola/V.24 PDU (Unconfirmed Block 2) + MOT_PDU_UNCONF_BLOCK_3 = 0x83U, //!< Motorola/V.24 PDU (Unconfirmed Block 3) + MOT_PDU_UNCONF_BLOCK_4 = 0x84U, //!< Motorola/V.24 PDU (Unconfirmed Block 4) + MOT_PDU_UNCONF_END = 0x85U, //!< Motorola/V.24 PDU (Unconfirmed Block End) + MOT_PDU_SINGLE_UNCONF = 0x87U, //!< Motorola/V.24 PDU (Single Unconfirmed Block) + + MOT_PDU_CONF_HEADER = 0x88U, //!< Motorola/V.24 PDU (Confirmed Block Header) + MOT_PDU_CONF_BLOCK_1 = 0x89U, //!< Motorola/V.24 PDU (Confirmed Block 1) + MOT_PDU_CONF_BLOCK_2 = 0x8AU, //!< Motorola/V.24 PDU (Confirmed Block 2) + MOT_PDU_CONF_BLOCK_3 = 0x8BU, //!< Motorola/V.24 PDU (Confirmed Block 3) + MOT_PDU_CONF_BLOCK_4 = 0x8CU, //!< Motorola/V.24 PDU (Confirmed Block 4) + MOT_PDU_CONF_END = 0x8DU, //!< Motorola/V.24 PDU (Confirmed Block End) + MOT_PDU_SINGLE_CONF = 0x8FU, //!< Motorola/V.24 PDU (Single Confirmed Block) + + MOT_TSBK = 0xA1U, //!< Motorola/V.24 TSBK (Single Block) + + P2_4VA = 0xF0U, //!< AMBE - Inbound/Outbound 4Va (P25 Phase 2) + P2_4VB = 0xF1U, //!< AMBE - Inbound/Outbound 4Vb (P25 Phase 2) + P2_4VC = 0xF2U, //!< AMBE - Inbound/Outbound 4Vc (P25 Phase 2) + P2_4VD = 0xF3U, //!< AMBE - Inbound/Outbound 4Vd (P25 Phase 2) + P2_2V = 0xF4U, //!< AMBE - Inbound/Outbound 2V (P25 Phase 2) }; } diff --git a/src/common/p25/dfsi/LC.cpp b/src/common/p25/dfsi/LC.cpp index eaa3ee3a5..3e688e666 100644 --- a/src/common/p25/dfsi/LC.cpp +++ b/src/common/p25/dfsi/LC.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" @@ -651,6 +651,233 @@ void LC::encodeLDU2(uint8_t* data, const uint8_t* imbe) ::memcpy(data, dfsiFrame, frameLength); } +/* Decode a Phase 2 4Vx voice frame. */ + +bool LC::decodeP2_4V(const uint8_t* data, uint8_t* ambe1, uint8_t* ambe2, + uint8_t* ambe3, uint8_t* ambe4) +{ + assert(data != nullptr); + assert(ambe1 != nullptr); + assert(ambe2 != nullptr); + assert(ambe3 != nullptr); + assert(ambe4 != nullptr); + + m_frameType = (DFSIFrameType::E)data[0U]; // Frame Type + + ::memcpy(ambe1, data + 1U, RAW_AMBE_LENGTH_BYTES); // AMBE1 + ::memcpy(ambe2, data + 8U, RAW_AMBE_LENGTH_BYTES); // AMBE2 + ::memcpy(ambe3, data + 15U, RAW_AMBE_LENGTH_BYTES); // AMBE3 + ::memcpy(ambe4, data + 22U, RAW_AMBE_LENGTH_BYTES); // AMBE4 + + uint8_t slotNo = data[38U] & 0x0FU; // Slot Number + m_control->setSlotNo(slotNo); + + // different frame types mean different things + switch (m_frameType) + { + case DFSIFrameType::P2_4VA: + { + m_control->setAlgId(data[34U]); // Algorithm ID + uint32_t kid = (data[35U] << 8) | (data[36U] << 0); // Key ID + m_control->setKId(kid); + } + break; + case DFSIFrameType::P2_4VB: + { + m_mi[0U] = data[34U]; // Message Indicator + m_mi[1U] = data[35U]; + m_mi[2U] = data[36U]; + } + break; + case DFSIFrameType::P2_4VC: + { + m_mi[3U] = data[34U]; // Message Indicator + m_mi[4U] = data[35U]; + m_mi[5U] = data[36U]; + } + break; + case DFSIFrameType::P2_4VD: + { + m_mi[6U] = data[34U]; // Message Indicator + m_mi[7U] = data[35U]; + m_mi[8U] = data[36U]; + m_control->setMI(m_mi); + } + break; + + default: + { + LogError(LOG_P25, "LC::decodeP2_4V(), invalid frame type, frameType = $%02X", m_frameType); + return false; + } + break; + } + + return true; +} + +/* Encode a Phase 2 4Vx voice frame. */ + +void LC::encodeP2_4V(uint8_t* data, const uint8_t* ambe1, const uint8_t* ambe2, const uint8_t* ambe3, const uint8_t* ambe4) +{ + assert(data != nullptr); + assert(ambe1 != nullptr); + assert(ambe2 != nullptr); + assert(ambe3 != nullptr); + assert(ambe4 != nullptr); + + // generate MI data + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + m_control->getMI(mi); + + DECLARE_UINT8_ARRAY(dfsiFrame, DFSI_P2_4V_FRAME_LENGTH_BYTES); + + dfsiFrame[0U] = m_frameType; // Frame Type + + ::memcpy(dfsiFrame + 1U, ambe1, RAW_AMBE_LENGTH_BYTES); // AMBE1 + ::memcpy(dfsiFrame + 8U, ambe2, RAW_AMBE_LENGTH_BYTES); // AMBE2 + ::memcpy(dfsiFrame + 15U, ambe3, RAW_AMBE_LENGTH_BYTES); // AMBE3 + ::memcpy(dfsiFrame + 22U, ambe4, RAW_AMBE_LENGTH_BYTES); // AMBE4 + + uint8_t slotNo = m_control->getSlotNo(); + dfsiFrame[38U] = slotNo & 0x0FU; // Slot Number + + uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + rs[i] = mi[i]; // Message Indicator + + rs[9U] = m_control->getAlgId(); // Algorithm ID + rs[10U] = (m_control->getKId() >> 8) & 0xFFU; // Key ID + rs[11U] = (m_control->getKId() >> 0) & 0xFFU; // ... + + // different frame types mean different things + switch (m_frameType) + { + case DFSIFrameType::P2_4VA: + { + dfsiFrame[34U] = rs[9U]; // Algorithm ID + dfsiFrame[35U] = rs[10U]; // Key ID + dfsiFrame[36U] = rs[11U]; + } + break; + case DFSIFrameType::P2_4VB: + { + dfsiFrame[34U] = rs[0U]; // Message Indicator + dfsiFrame[35U] = rs[1U]; + dfsiFrame[36U] = rs[2U]; + } + break; + case DFSIFrameType::P2_4VC: + { + dfsiFrame[34U] = rs[3U]; // Message Indicator + dfsiFrame[35U] = rs[4U]; + dfsiFrame[36U] = rs[5U]; + } + break; + case DFSIFrameType::P2_4VD: + { + dfsiFrame[34U] = rs[6U]; // Message Indicator + dfsiFrame[35U] = rs[7U]; + dfsiFrame[36U] = rs[8U]; + } + break; + + default: + { + LogError(LOG_P25, "LC::encodeP2_4V(), invalid frame type, frameType = $%02X", m_frameType); + return; + } + break; + } + +#if DEBUG_P25_DFSI + LogDebugEx(LOG_P25, "dfsi::LC::encodeP2_4V()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeP2_4V(), DFSI Phase 2 4Vx Frame", dfsiFrame, DFSI_P2_4V_FRAME_LENGTH_BYTES); +#endif + + ::memcpy(data, dfsiFrame, DFSI_P2_4V_FRAME_LENGTH_BYTES); +} + +/* Decode a Phase 2 2V voice frame.*/ + +bool LC::decodeP2_2V(const uint8_t* data, uint8_t* ambe1, uint8_t* ambe2) +{ + assert(data != nullptr); + assert(ambe1 != nullptr); + assert(ambe2 != nullptr); + + m_frameType = (DFSIFrameType::E)data[0U]; // Frame Type + + ::memcpy(ambe1, data + 1U, RAW_AMBE_LENGTH_BYTES); // AMBE1 + ::memcpy(ambe2, data + 8U, RAW_AMBE_LENGTH_BYTES); // AMBE2 + + uint8_t essErrorCnt = data[17U]; + + if (essErrorCnt == 0U) { + m_control->setAlgId(data[18U]); // Algorithm ID + uint32_t kid = (data[19U] << 8) | (data[20U] << 0); // Key ID + m_control->setKId(kid); + + ::memcpy(m_mi, data + 21U, MI_LENGTH_BYTES); // Message Indicator + m_control->setMI(m_mi); + } + + uint8_t slotNo = data[31U] & 0x0FU; // Slot Number + m_control->setSlotNo(slotNo); + + return false; +} + +/* Encode a Phase 2 2V voice frame.*/ + +void LC::encodeP2_2V(uint8_t* data, const uint8_t* ambe1, const uint8_t* ambe2) +{ + assert(data != nullptr); + assert(ambe1 != nullptr); + assert(ambe2 != nullptr); + + // generate MI data + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + m_control->getMI(mi); + + DECLARE_UINT8_ARRAY(dfsiFrame, DFSI_P2_2V_FRAME_LENGTH_BYTES); + + dfsiFrame[0U] = m_frameType; // Frame Type + + ::memcpy(dfsiFrame + 1U, ambe1, RAW_AMBE_LENGTH_BYTES); // AMBE1 + ::memcpy(dfsiFrame + 8U, ambe2, RAW_AMBE_LENGTH_BYTES); // AMBE2 + + uint8_t slotNo = m_control->getSlotNo(); + dfsiFrame[31U] = slotNo & 0x0FU; // Slot Number + + uint8_t rs[P25_LDU_LC_FEC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_FEC_LENGTH_BYTES); + + for (uint32_t i = 0; i < MI_LENGTH_BYTES; i++) + rs[i] = mi[i]; // Message Indicator + + rs[9U] = m_control->getAlgId(); // Algorithm ID + rs[10U] = (m_control->getKId() >> 8) & 0xFFU; // Key ID + rs[11U] = (m_control->getKId() >> 0) & 0xFFU; // ... + + dfsiFrame[18U] = rs[9U]; // Algorithm ID + dfsiFrame[19U] = rs[10U]; // Key ID + dfsiFrame[20U] = rs[11U]; + + ::memcpy(dfsiFrame + 21U, mi, MI_LENGTH_BYTES); // Message Indicator + +#if DEBUG_P25_DFSI + LogDebugEx(LOG_P25, "dfsi::LC::encodeP2_2V()", "frameType = $%02X", m_frameType); + Utils::dump(2U, "P25, dfsi::LC::encodeP2_2V(), DFSI Phase 2 2V Frame", dfsiFrame, DFSI_P2_2V_FRAME_LENGTH_BYTES); +#endif + + ::memcpy(data, dfsiFrame, DFSI_P2_2V_FRAME_LENGTH_BYTES); +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/dfsi/LC.h b/src/common/p25/dfsi/LC.h index 7af0492b2..b1f498d2a 100644 --- a/src/common/p25/dfsi/LC.h +++ b/src/common/p25/dfsi/LC.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2024,2025 Bryan Biedenkapp, N2PLL * */ /** @@ -98,6 +98,44 @@ namespace p25 */ void encodeLDU2(uint8_t* data, const uint8_t* imbe); + /** + * @brief Decode a Phase 2 4Vx voice frame. + * @param data Buffer containing the P2 4V frame to decode. + * @param ambe1 Raw AMBE from P2 4V frame. + * @param ambe2 Raw AMBE from P2 4V frame. + * @param ambe3 Raw AMBE from P2 4V frame. + * @param ambe4 Raw AMBE from P2 4V frame. + * @return bool True, if P2 4V frame decoded, otherwise false. + */ + bool decodeP2_4V(const uint8_t* data, uint8_t* ambe1, uint8_t* ambe2, + uint8_t* ambe3, uint8_t* ambe4); + /** + * @brief Encode a Phase 2 4Vx voice frame. + * @param data Buffer to encode a P2 4V frame. + * @param ambe1 Raw AMBE from P2 4V frame. + * @param ambe2 Raw AMBE from P2 4V frame. + * @param ambe3 Raw AMBE from P2 4V frame. + * @param ambe4 Raw AMBE from P2 4V frame. + */ + void encodeP2_4V(uint8_t* data, const uint8_t* ambe1, const uint8_t* ambe2, + const uint8_t* ambe3, const uint8_t* ambe4); + + /** + * @brief Decode a Phase 2 2V voice frame. + * @param data Buffer containing the P2 2V frame to decode. + * @param ambe1 Raw AMBE from P2 2V frame. + * @param ambe2 Raw AMBE from P2 2V frame. + * @return bool True, if P2 2V frame decoded, otherwise false. + */ + bool decodeP2_2V(const uint8_t* data, uint8_t* ambe1, uint8_t* ambe2); + /** + * @brief Encode a Phase 2 2V voice frame. + * @param data Buffer to encode a P2 2V frame. + * @param ambe1 Raw AMBE from P2 2V frame. + * @param ambe2 Raw AMBE from P2 2V frame. + */ + void encodeP2_2V(uint8_t* data, const uint8_t* ambe1, const uint8_t* ambe2); + public: // Common Data /** diff --git a/src/common/p25/dfsi/frames/FrameDefines.h b/src/common/p25/dfsi/frames/FrameDefines.h index eb8c01bf3..ed9589236 100644 --- a/src/common/p25/dfsi/frames/FrameDefines.h +++ b/src/common/p25/dfsi/frames/FrameDefines.h @@ -44,17 +44,17 @@ namespace p25 namespace FSCMessageType { /** @brief FSC Control Service Message.*/ enum E : uint8_t { - FSC_CONNECT = 0, //! Establish connection with FSS - FSC_HEARTBEAT = 1, //! Heartbeat/Connectivity Maintenance - FSC_ACK = 2, //! Control Service Ack. + FSC_CONNECT = 0, //!< Establish connection with FSS + FSC_HEARTBEAT = 1, //!< Heartbeat/Connectivity Maintenance + FSC_ACK = 2, //!< Control Service Ack. - FSC_SEL_CHAN = 5, //! Channel Selection + FSC_SEL_CHAN = 5, //!< Channel Selection - FSC_REPORT_SEL_MODES = 8, //! Report Selected Modes + FSC_REPORT_SEL_MODES = 8, //!< Report Selected Modes - FSC_DISCONNECT = 9, //! Detach Control Service + FSC_DISCONNECT = 9, //!< Detach Control Service - FSC_INVALID = 127, //! Invalid Control Message + FSC_INVALID = 127, //!< Invalid Control Message }; } @@ -62,14 +62,14 @@ namespace p25 namespace FSCAckResponseCode { /** @brief FSC ACK/NAK Codes. */ enum E : uint8_t { - CONTROL_ACK = 0, //! Acknowledgement. - CONTROL_NAK = 1, //! Unspecified Negative Acknowledgement. - CONTROL_NAK_CONNECTED = 2, //! Server is connected to some other host. - CONTROL_NAK_M_UNSUPP = 3, //! Unsupported Manufactuerer Message. - CONTROL_NAK_V_UNSUPP = 4, //! Unsupported Message Version. - CONTROL_NAK_F_UNSUPP = 5, //! Unsupported Function. - CONTROL_NAK_PARMS = 6, //! Bad / Unsupported Command Parameters. - CONTROL_NAK_BUSY = 7 //! FSS is currently busy with a function. + CONTROL_ACK = 0, //!< Acknowledgement. + CONTROL_NAK = 1, //!< Unspecified Negative Acknowledgement. + CONTROL_NAK_CONNECTED = 2, //!< Server is connected to some other host. + CONTROL_NAK_M_UNSUPP = 3, //!< Unsupported Manufactuerer Message. + CONTROL_NAK_V_UNSUPP = 4, //!< Unsupported Message Version. + CONTROL_NAK_F_UNSUPP = 5, //!< Unsupported Function. + CONTROL_NAK_PARMS = 6, //!< Bad / Unsupported Command Parameters. + CONTROL_NAK_BUSY = 7 //!< FSS is currently busy with a function. }; } @@ -77,17 +77,17 @@ namespace p25 namespace BlockType { /** @brief DFSI Block Types */ enum E : uint8_t { - FULL_RATE_VOICE = 0, //! Full Rate Voice + FULL_RATE_VOICE = 0, //!< Full Rate Voice - VOICE_HEADER_P1 = 6, //! Voice Header 1 - VOICE_HEADER_P2 = 7, //! Voice Header 2 + VOICE_HEADER_P1 = 6, //!< Voice Header 1 + VOICE_HEADER_P2 = 7, //!< Voice Header 2 - START_OF_STREAM = 9, //! Start of Stream - END_OF_STREAM = 10, //! End of Stream + START_OF_STREAM = 9, //!< Start of Stream + END_OF_STREAM = 10, //!< End of Stream - START_OF_STREAM_ACK = 14, //! Start of Stream Ack + START_OF_STREAM_ACK = 14, //!< Start of Stream Ack - UNDEFINED = 127 //! Undefined + UNDEFINED = 127 //!< Undefined }; } @@ -95,8 +95,8 @@ namespace p25 namespace MotStartStreamOpcode { /** @brief Motorola Start of Stream Operation */ enum E : uint8_t { - TRANSMIT = 0x02U, //! Transmit - RECEIVE = 0x04U, //! Receive + TRANSMIT = 0x02U, //!< Transmit + RECEIVE = 0x04U, //!< Receive }; } @@ -104,10 +104,10 @@ namespace p25 namespace MotStreamPayload { /** @brief Motorola Stream Payload */ enum E : uint8_t { - VOICE = 0x0BU, //! P25 Voice - DATA = 0x0CU, //! P25 Data - TERM_LC = 0x0EU, //! P25 Termination Link Control - TSBK = 0x0FU //! P25 TSBK + VOICE = 0x0BU, //!< P25 Voice + DATA = 0x0CU, //!< P25 Data + TERM_LC = 0x0EU, //!< P25 Termination Link Control + TSBK = 0x0FU //!< P25 TSBK }; } /** @} */ diff --git a/src/common/p25/kmm/KMMDeregistrationCommand.cpp b/src/common/p25/kmm/KMMDeregistrationCommand.cpp index c26a81b6e..401e06d89 100644 --- a/src/common/p25/kmm/KMMDeregistrationCommand.cpp +++ b/src/common/p25/kmm/KMMDeregistrationCommand.cpp @@ -36,6 +36,13 @@ KMMDeregistrationCommand::KMMDeregistrationCommand() : KMMFrame(), KMMDeregistrationCommand::~KMMDeregistrationCommand() = default; +/* Gets the byte length of this KMMDeregistrationCommand. */ + +uint32_t KMMDeregistrationCommand::length() const +{ + return KMMFrame::length() + KMM_BODY_DEREGISTRATION_CMD_LENGTH; +} + /* Decode a KMM modify key. */ bool KMMDeregistrationCommand::decode(const uint8_t* data) @@ -44,8 +51,8 @@ bool KMMDeregistrationCommand::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_bodyFormat = data[10U]; // Body Format - m_kmfRSI = GET_UINT24(data, 11U); // KMF RSI + m_bodyFormat = data[10U + m_bodyOffset]; // Body Format + m_kmfRSI = GET_UINT24(data, 11U + m_bodyOffset); // KMF RSI return true; } @@ -55,13 +62,20 @@ bool KMMDeregistrationCommand::decode(const uint8_t* data) void KMMDeregistrationCommand::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_DEREGISTRATION_CMD_LENGTH; + m_messageLength = length(); m_bodyFormat = 0U; // reset this to none -- we don't support warm start right now KMMFrame::encodeHeader(data); - data[10U] = m_bodyFormat; // Body Format - SET_UINT24(m_kmfRSI, data, 11U); // KMF RSI + data[10U + m_bodyOffset] = m_bodyFormat; // Body Format + SET_UINT24(m_kmfRSI, data, 11U + m_bodyOffset); // KMF RSI +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMDeregistrationCommand::toString() +{ + return std::string("KMM, DEREG_CMD (Deregistration Command)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMDeregistrationCommand.h b/src/common/p25/kmm/KMMDeregistrationCommand.h index e93c8a278..bc8f203bd 100644 --- a/src/common/p25/kmm/KMMDeregistrationCommand.h +++ b/src/common/p25/kmm/KMMDeregistrationCommand.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_DEREGISTRATION_CMD_LENGTH = KMM_FRAME_LENGTH + 4U; + const uint32_t KMM_BODY_DEREGISTRATION_CMD_LENGTH = 4U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMDeregistrationCommand(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM deregistration command. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief diff --git a/src/common/p25/kmm/KMMDeregistrationResponse.cpp b/src/common/p25/kmm/KMMDeregistrationResponse.cpp index f3ace5847..bdba65ead 100644 --- a/src/common/p25/kmm/KMMDeregistrationResponse.cpp +++ b/src/common/p25/kmm/KMMDeregistrationResponse.cpp @@ -35,6 +35,13 @@ KMMDeregistrationResponse::KMMDeregistrationResponse() : KMMFrame(), KMMDeregistrationResponse::~KMMDeregistrationResponse() = default; +/* Gets the byte length of this KMMDeregistrationResponse. */ + +uint32_t KMMDeregistrationResponse::length() const +{ + return KMMFrame::length() + KMM_BODY_DEREGISTRATION_RSP_LENGTH; +} + /* Decode a KMM modify key. */ bool KMMDeregistrationResponse::decode(const uint8_t* data) @@ -43,7 +50,7 @@ bool KMMDeregistrationResponse::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_status = data[10U]; // Status + m_status = data[10U + m_bodyOffset]; // Status return true; } @@ -53,11 +60,18 @@ bool KMMDeregistrationResponse::decode(const uint8_t* data) void KMMDeregistrationResponse::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_DEREGISTRATION_RSP_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_status; // Status + data[10U + m_bodyOffset] = m_status; // Status +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMDeregistrationResponse::toString() +{ + return std::string("KMM, DEREG_RSP (Deregistration Response)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMDeregistrationResponse.h b/src/common/p25/kmm/KMMDeregistrationResponse.h index 2b1badba5..aa0373f2b 100644 --- a/src/common/p25/kmm/KMMDeregistrationResponse.h +++ b/src/common/p25/kmm/KMMDeregistrationResponse.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_DEREGISTRATION_RSP_LENGTH = KMM_FRAME_LENGTH + 1U; + const uint32_t KMM_BODY_DEREGISTRATION_RSP_LENGTH = 1U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMDeregistrationResponse(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM deregistration response. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief Deregistration response status. diff --git a/src/common/p25/kmm/KMMFactory.cpp b/src/common/p25/kmm/KMMFactory.cpp index eec2a2fa4..5fb15c0a7 100644 --- a/src/common/p25/kmm/KMMFactory.cpp +++ b/src/common/p25/kmm/KMMFactory.cpp @@ -87,6 +87,12 @@ std::unique_ptr KMMFactory::create(const uint8_t* data) return decode(new KMMRegistrationCommand(), data); case KMM_MessageType::REG_RSP: return decode(new KMMRegistrationResponse(), data); + case KMM_MessageType::REKEY_ACK: + return decode(new KMMRekeyAck(), data); + case KMM_MessageType::REKEY_CMD: + return decode(new KMMRekeyCommand(), data); + case KMM_MessageType::UNABLE_TO_DECRYPT: + return decode(new KMMUnableToDecrypt(), data); default: LogError(LOG_P25, "KMMFactory::create(), unknown KMM message ID value, messageId = $%02X", messageId); break; diff --git a/src/common/p25/kmm/KMMFactory.h b/src/common/p25/kmm/KMMFactory.h index ce9dd0109..306369d50 100644 --- a/src/common/p25/kmm/KMMFactory.h +++ b/src/common/p25/kmm/KMMFactory.h @@ -33,6 +33,9 @@ #include "common/p25/kmm/KMMNoService.h" #include "common/p25/kmm/KMMRegistrationCommand.h" #include "common/p25/kmm/KMMRegistrationResponse.h" +#include "common/p25/kmm/KMMRekeyAck.h" +#include "common/p25/kmm/KMMRekeyCommand.h" +#include "common/p25/kmm/KMMUnableToDecrypt.h" #include "common/p25/kmm/KMMZeroize.h" namespace p25 diff --git a/src/common/p25/kmm/KMMFrame.cpp b/src/common/p25/kmm/KMMFrame.cpp index dbd9a5867..af3076123 100644 --- a/src/common/p25/kmm/KMMFrame.cpp +++ b/src/common/p25/kmm/KMMFrame.cpp @@ -9,10 +9,12 @@ */ #include "Defines.h" #include "p25/P25Defines.h" +#include "p25/Crypto.h" #include "p25/kmm/KMMFrame.h" #include "Log.h" using namespace p25; +using namespace p25::crypto; using namespace p25::defines; using namespace p25::kmm; @@ -35,16 +37,110 @@ KMMFrame::KMMFrame() : m_messageId(KMM_MessageType::NULL_CMD), m_messageLength(KMM_FRAME_LENGTH), m_respKind(KMM_ResponseKind::NONE), + m_macType(KMM_MAC::NO_MAC), + m_macAlgId(ALGO_UNENCRYPT), + m_macKId(0U), + m_macFormat(0U), + m_messageNumber(0U), + m_dstLlId(0U), + m_srcLlId(0U), m_complete(true), - m_mfMessageNumber(0U), - m_mfMac(KMM_MAC::NO_MAC) + m_messageFullLength(0U), + m_bodyOffset(0U), + m_mac(nullptr) { - /* stub */ + m_mac = new uint8_t[P25DEF::KMM_AES_MAC_LENGTH]; + ::memset(m_mac, 0x00U, P25DEF::KMM_AES_MAC_LENGTH); } /* Finalizes a instance of the KMMFrame class. */ -KMMFrame::~KMMFrame() = default; +KMMFrame::~KMMFrame() +{ + if (m_mac != nullptr) + delete[] m_mac; +} + +/* Generate a MAC code for the given KMM frame. */ + +void KMMFrame::generateMAC(uint8_t* kek, uint8_t* data) +{ + assert(data != nullptr); + + if (m_macType == KMM_MAC::NO_MAC) { + ::LogError(LOG_P25, "KMMFrame::generateMAC(), MAC type is set to no MAC, aborting MAC signing"); + return; + } + + if (m_macAlgId == ALGO_UNENCRYPT) { + ::LogError(LOG_P25, "KMMFrame::generateMAC(), MAC algorithm is not set, aborting MAC signing"); + return; + } + + if (m_macKId == 0U) { + ::LogError(LOG_P25, "KMMFrame::generateMAC(), MAC key ID is not set, aborting MAC signing"); + return; + } + + switch (m_macType) { + case KMM_MAC::DES_MAC: + ::LogError(LOG_P25, "KMMFrame::generateMAC(), DES MAC type is not supported, macType = $%02X", m_macType); + return; + + case KMM_MAC::ENH_MAC: + { + uint8_t macLength = P25DEF::KMM_AES_MAC_LENGTH; + P25Crypto crypto; + + switch (m_macFormat) { + case KMM_MAC_FORMAT_CBC: + { + // generate intermediate derived key + UInt8Array macKey = crypto.cryptAES_KMM_CBC_KDF(kek, data, m_messageFullLength); + + // generate MAC + UInt8Array mac = crypto.cryptAES_KMM_CBC(macKey.get(), data, m_messageFullLength); + + ::memset(data + m_messageFullLength - (macLength + 5U), 0x00U, macLength); + ::memcpy(data + m_messageFullLength - (macLength + 5U), mac.get(), macLength); + } + break; + + case KMM_MAC_FORMAT_CMAC: + { + // generate intermediate derived key + UInt8Array macKey = crypto.cryptAES_KMM_CMAC_KDF(kek, data, m_messageFullLength, m_messageNumber > 0U); + + // generate MAC + UInt8Array mac = crypto.cryptAES_KMM_CMAC(macKey.get(), data, m_messageFullLength); + + ::memset(data + m_messageFullLength - (macLength + 5U), 0x00U, macLength); + ::memcpy(data + m_messageFullLength - (macLength + 5U), mac.get(), macLength); + } + break; + + default: + ::LogError(LOG_P25, "KMMFrame::generateMAC(), unknown KMM MAC format type value, macType = $%02X", m_macType); + break; + } + } + break; + + case KMM_MAC::NO_MAC: + break; + + default: + ::LogError(LOG_P25, "KMMFrame::generateMAC(), unknown KMM MAC inventory type value, macType = $%02X", m_macType); + break; + } +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMFrame::toString() +{ + return std::string("KMM, UNKNOWN (Unknown KMM)"); +} // --------------------------------------------------------------------------- // Protected Class Members @@ -58,10 +154,11 @@ bool KMMFrame::decodeHeader(const uint8_t* data) m_messageId = data[0U]; // Message ID m_messageLength = GET_UINT16(data, 1U); // Message Length + m_messageFullLength = m_messageLength + 3U; // length including ID and length fields m_respKind = (data[3U] >> 6U) & 0x03U; // Response Kind - m_mfMessageNumber = (data[3U] >> 4U) & 0x03U; // Message Number - m_mfMac = (data[3U] >> 2U) & 0x03U; // MAC + bool hasMN = ((data[3U] >> 4U) & 0x03U) == 0x02U; // Message Number Flag + m_macType = (data[3U] >> 2U) & 0x03U; // MAC Type bool done = (data[3U] & 0x01U) == 0x01U; // Done Flag if (!done) @@ -72,6 +169,46 @@ bool KMMFrame::decodeHeader(const uint8_t* data) m_dstLlId = GET_UINT24(data, 4U); // Destination RSI m_srcLlId = GET_UINT24(data, 7U); // Source RSI + if (hasMN) { + m_bodyOffset = 2U; + m_messageNumber = GET_UINT16(data, 10U); // Message Number + } + + switch (m_macType) { + case KMM_MAC::DES_MAC: + { + uint8_t macLength = P25DEF::KMM_DES_MAC_LENGTH; + + m_macAlgId = data[m_messageFullLength - 4U]; + m_macKId = GET_UINT16(data, m_messageFullLength - 3U); + m_macFormat = data[m_messageFullLength - 1U]; + + ::memset(m_mac, 0x00U, macLength); + ::memcpy(m_mac, data + m_messageFullLength - (macLength + 5U), macLength); + } + break; + + case KMM_MAC::ENH_MAC: + { + uint8_t macLength = P25DEF::KMM_AES_MAC_LENGTH; + + m_macAlgId = data[m_messageFullLength - 4U]; + m_macKId = GET_UINT16(data, m_messageFullLength - 3U); + m_macFormat = data[m_messageFullLength - 1U]; + + ::memset(m_mac, 0x00U, macLength); + ::memcpy(m_mac, data + m_messageFullLength - (macLength + 5U), macLength); + } + break; + + case KMM_MAC::NO_MAC: + break; + + default: + ::LogError(LOG_P25, "KMMFrame::decodeHeader(), unknown KMM MAC inventory type value, macType = $%02X", m_macType); + break; + } + return true; } @@ -83,14 +220,46 @@ void KMMFrame::encodeHeader(uint8_t* data) data[0U] = m_messageId; // Message ID SET_UINT16(m_messageLength, data, 1U); // Message Length + m_messageFullLength = m_messageLength + 3U; data[3U] = ((m_respKind & 0x03U) << 6U) + // Response Kind - ((m_mfMessageNumber & 0x03U) << 4U) + // Message Number - ((m_mfMac & 0x03U) << 2U) + // MAC + ((m_messageNumber > 0U) ? 0x20U : 0x00U) + // Message Number Flag + ((m_macType & 0x03U) << 2U) + // MAC Type ((!m_complete) ? 0x01U : 0x00U); // Done Flag SET_UINT24(m_dstLlId, data, 4U); // Destination RSI SET_UINT24(m_srcLlId, data, 7U); // Source RSI + + if (m_messageNumber > 0U) { + SET_UINT16(m_messageNumber, data, 10U); // Message Number + m_bodyOffset = 2U; + } + + switch (m_macType) { + case KMM_MAC::DES_MAC: + ::LogError(LOG_P25, "KMMFrame::decodeHeader(), DES MAC type is not supported, macType = $%02X", m_macType); + return; + + case KMM_MAC::ENH_MAC: + { + uint8_t macLength = P25DEF::KMM_AES_MAC_LENGTH; + + data[m_messageFullLength - 5U] = macLength; + data[m_messageFullLength - 4U] = m_macAlgId; + SET_UINT16(m_macKId, data, m_messageFullLength - 3U); + data[m_messageFullLength - 1U] = m_macFormat; + + ::memcpy(data + m_messageFullLength - (macLength + 5U), m_mac, macLength); + } + break; + + case KMM_MAC::NO_MAC: + break; + + default: + ::LogError(LOG_P25, "KMMFrame::decodeHeader(), unknown KMM MAC inventory type value, macType = $%02X", m_macType); + break; + } } /* Internal helper to copy the the class. */ @@ -99,9 +268,12 @@ void KMMFrame::copy(const KMMFrame& data) { m_messageId = data.m_messageId; m_messageLength = data.m_messageLength; + m_messageFullLength = data.m_messageFullLength; m_respKind = data.m_respKind; m_complete = data.m_complete; - m_mfMessageNumber = data.m_mfMessageNumber; - m_mfMac = data.m_mfMac; + m_messageNumber = data.m_messageNumber; + m_macAlgId = data.m_macAlgId; + m_macKId = data.m_macKId; + m_macType = data.m_macType; } diff --git a/src/common/p25/kmm/KMMFrame.h b/src/common/p25/kmm/KMMFrame.h index 5b7581320..ee3914634 100644 --- a/src/common/p25/kmm/KMMFrame.h +++ b/src/common/p25/kmm/KMMFrame.h @@ -21,6 +21,7 @@ #define __P25_KMM__KMM_FRAME_H__ #include "common/Defines.h" +#include "common/p25/P25Defines.h" #include "common/Utils.h" #include @@ -70,7 +71,27 @@ namespace p25 * @brief Gets the byte length of this KMMFrame. * @return uint32_t Length of KMMFrame. */ - virtual uint32_t length() const { return KMM_FRAME_LENGTH; } + virtual uint32_t length() const + { + uint32_t len = KMM_FRAME_LENGTH; + if (m_messageNumber > 0U) + len += 2U; + if (m_macType == P25DEF::KMM_MAC::ENH_MAC) + len += P25DEF::KMM_AES_MAC_LENGTH + 5U; + + return len; + } + + /** + * @brief Gets the full byte length of this KMMFrame. + * @return uint32_t Full Length of KMMFrame. + */ + uint32_t fullLength() + { + m_messageLength = length(); + m_messageFullLength = m_messageLength + 3U; + return m_messageFullLength; + } /** * @brief Decode a KMM frame. @@ -84,6 +105,19 @@ namespace p25 */ virtual void encode(uint8_t* data) = 0; + /** + * @brief Generate a MAC code for the given KMM frame. + * @param kek Key Encryption Key + * @param[out] data Buffer to encode KMM MAC to. + */ + void generateMAC(uint8_t* kek, uint8_t* data); + + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + virtual std::string toString(); + public: // Common Data /** @@ -100,6 +134,27 @@ namespace p25 */ DECLARE_PROTECTED_PROPERTY(uint8_t, respKind, ResponseKind); + /** + * @brief Message Authentication Type. + */ + DECLARE_PROTECTED_PROPERTY(uint8_t, macType, MACType); + /** + * @brief Message Authentication Algorithm ID. + */ + DECLARE_PROTECTED_PROPERTY(uint8_t, macAlgId, MACAlgId); + /** + * @brief Message Authentication Key ID. + */ + DECLARE_PROTECTED_PROPERTY(uint16_t, macKId, MACKId); + /** + * @brief Message Authentication Format. + */ + DECLARE_PROTECTED_PROPERTY(uint16_t, macFormat, MACFormat); + /** + * @brief Message Number. + */ + DECLARE_PROTECTED_PROPERTY(uint16_t, messageNumber, MessageNumber); + /** * @brief Destination Logical link ID. */ @@ -115,8 +170,10 @@ namespace p25 DECLARE_PROTECTED_PROPERTY(bool, complete, Complete); protected: - uint8_t m_mfMessageNumber; - uint8_t m_mfMac; + uint16_t m_messageFullLength; //!< Complete length of entire frame in bytes. + uint8_t m_bodyOffset; //!< Offset to KMM frame body data. + + uint8_t* m_mac; /** * @brief Internal helper to decode a KMM header. diff --git a/src/common/p25/kmm/KMMHello.cpp b/src/common/p25/kmm/KMMHello.cpp index e18a01393..60e5df17c 100644 --- a/src/common/p25/kmm/KMMHello.cpp +++ b/src/common/p25/kmm/KMMHello.cpp @@ -35,6 +35,13 @@ KMMHello::KMMHello() : KMMFrame(), KMMHello::~KMMHello() = default; +/* Gets the byte length of this KMMHello. */ + +uint32_t KMMHello::length() const +{ + return KMMFrame::length() + KMM_BODY_HELLO_LENGTH; +} + /* Decode a KMM modify key. */ bool KMMHello::decode(const uint8_t* data) @@ -43,7 +50,7 @@ bool KMMHello::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_flag = data[10U]; // Hello Flag + m_flag = data[10U + m_bodyOffset]; // Hello Flag return true; } @@ -53,11 +60,18 @@ bool KMMHello::decode(const uint8_t* data) void KMMHello::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_HELLO_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_flag; // Hello Flag + data[10U + m_bodyOffset] = m_flag; // Hello Flag +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMHello::toString() +{ + return std::string("KMM, HELLO (Hello)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMHello.h b/src/common/p25/kmm/KMMHello.h index 251b18f81..fbb5a20f6 100644 --- a/src/common/p25/kmm/KMMHello.h +++ b/src/common/p25/kmm/KMMHello.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_HELLO_LENGTH = KMM_FRAME_LENGTH + 1U; + const uint32_t KMM_BODY_HELLO_LENGTH = 1U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMHello(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM hello. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief diff --git a/src/common/p25/kmm/KMMInventoryCommand.cpp b/src/common/p25/kmm/KMMInventoryCommand.cpp index c36aeb023..3450de81c 100644 --- a/src/common/p25/kmm/KMMInventoryCommand.cpp +++ b/src/common/p25/kmm/KMMInventoryCommand.cpp @@ -35,6 +35,13 @@ KMMInventoryCommand::KMMInventoryCommand() : KMMFrame(), KMMInventoryCommand::~KMMInventoryCommand() = default; +/* Gets the byte length of this KMMInventoryCommand. */ + +uint32_t KMMInventoryCommand::length() const +{ + return KMMFrame::length() + KMM_BODY_INVENTORY_CMD_LENGTH; +} + /* Decode a KMM inventory command. */ bool KMMInventoryCommand::decode(const uint8_t* data) @@ -43,7 +50,7 @@ bool KMMInventoryCommand::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_inventoryType = data[10U]; // Inventory Type + m_inventoryType = data[10U + m_bodyOffset]; // Inventory Type return true; } @@ -53,11 +60,18 @@ bool KMMInventoryCommand::decode(const uint8_t* data) void KMMInventoryCommand::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_INVENTORY_CMD_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_inventoryType; // Inventory Type + data[10U + m_bodyOffset] = m_inventoryType; // Inventory Type +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMInventoryCommand::toString() +{ + return std::string("KMM, INVENTORY_CMD (Inventory Command)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMInventoryCommand.h b/src/common/p25/kmm/KMMInventoryCommand.h index 81d1c3a18..4b34db43b 100644 --- a/src/common/p25/kmm/KMMInventoryCommand.h +++ b/src/common/p25/kmm/KMMInventoryCommand.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_INVENTORY_CMD_LENGTH = KMM_FRAME_LENGTH + 1U; + const uint32_t KMM_BODY_INVENTORY_CMD_LENGTH = 1U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMInventoryCommand(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM inventory command. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief Inventory type. diff --git a/src/common/p25/kmm/KMMInventoryResponseHeader.cpp b/src/common/p25/kmm/KMMInventoryResponseHeader.cpp index 89bc8744e..ecdb3d68d 100644 --- a/src/common/p25/kmm/KMMInventoryResponseHeader.cpp +++ b/src/common/p25/kmm/KMMInventoryResponseHeader.cpp @@ -35,6 +35,13 @@ KMMInventoryResponseHeader::KMMInventoryResponseHeader() : KMMFrame(), KMMInventoryResponseHeader::~KMMInventoryResponseHeader() = default; +/* Gets the byte length of this KMMInventoryResponseHeader. */ + +uint32_t KMMInventoryResponseHeader::length() const +{ + return KMMFrame::length() + KMM_BODY_INV_RSP_HDR_LENGTH; +} + /* Decode a KMM inventory response header. */ bool KMMInventoryResponseHeader::decode(const uint8_t* data) @@ -43,8 +50,8 @@ bool KMMInventoryResponseHeader::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_inventoryType = data[10U]; // Inventory Type - m_numberOfItems = GET_UINT16(data, 11U); // Number of Items + m_inventoryType = data[10U + m_bodyOffset]; // Inventory Type + m_numberOfItems = GET_UINT16(data, 11U + m_bodyOffset); // Number of Items return true; } @@ -54,12 +61,19 @@ bool KMMInventoryResponseHeader::decode(const uint8_t* data) void KMMInventoryResponseHeader::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_INVENTORY_RSP_HDR_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_inventoryType; // Inventory Type - SET_UINT16(m_numberOfItems, data, 11U); // Number of Items + data[10U + m_bodyOffset] = m_inventoryType; // Inventory Type + SET_UINT16(m_numberOfItems, data, 11U + m_bodyOffset); // Number of Items +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMInventoryResponseHeader::toString() +{ + return std::string("KMM, INVENTORY_RSP (Inventory Response)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMInventoryResponseHeader.h b/src/common/p25/kmm/KMMInventoryResponseHeader.h index 816b60eb3..7c8c521a5 100644 --- a/src/common/p25/kmm/KMMInventoryResponseHeader.h +++ b/src/common/p25/kmm/KMMInventoryResponseHeader.h @@ -36,10 +36,10 @@ namespace p25 * @{ */ - const uint32_t KMM_INVENTORY_RSP_HDR_LENGTH = KMM_FRAME_LENGTH + 3U; + const uint32_t KMM_BODY_INV_RSP_HDR_LENGTH = 3U; /** @} */ - + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -55,6 +55,12 @@ namespace p25 */ ~KMMInventoryResponseHeader(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM inventory response header. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + virtual std::string toString() override; + public: /** * @brief Inventory type. diff --git a/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.cpp b/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.cpp index 38ea812f3..4c283aac3 100644 --- a/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.cpp +++ b/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.cpp @@ -48,7 +48,7 @@ KMMInventoryResponseListKeyIDs::~KMMInventoryResponseListKeyIDs() = default; uint32_t KMMInventoryResponseListKeyIDs::length() const { - uint32_t len = KMM_INVENTORY_RSP_HDR_LENGTH + 3U; + uint32_t len = KMMInventoryResponseHeader::length() + KMM_BODY_INV_RSP_LIST_KIDS_LENGTH; len += m_keyIds.size() * 2U; return len; @@ -62,13 +62,13 @@ bool KMMInventoryResponseListKeyIDs::decode(const uint8_t* data) KMMInventoryResponseHeader::decodeHeader(data); - m_keysetId = data[13U]; - m_algId = data[14U]; - m_numberOfKeyIDs = data[15U]; + m_keysetId = data[13U + m_bodyOffset]; + m_algId = data[14U + m_bodyOffset]; + m_numberOfKeyIDs = data[15U + m_bodyOffset]; uint16_t offset = 0U; for (uint16_t i = 0U; i < m_numberOfKeyIDs; i++) { - uint16_t keyId = GET_UINT16(data, 16U + offset); + uint16_t keyId = GET_UINT16(data, 16U + (m_bodyOffset + offset)); m_keyIds.push_back(keyId); offset += 2U; } @@ -81,22 +81,29 @@ bool KMMInventoryResponseListKeyIDs::decode(const uint8_t* data) void KMMInventoryResponseListKeyIDs::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_INVENTORY_RSP_HDR_LENGTH; + m_messageLength = length(); m_numberOfItems = 1U; // this is a naive approach... KMMInventoryResponseHeader::encodeHeader(data); - data[13U] = m_keysetId; - data[14U] = m_algId; - data[15U] = m_numberOfKeyIDs; + data[13U + m_bodyOffset] = m_keysetId; + data[14U + m_bodyOffset] = m_algId; + data[15U + m_bodyOffset] = m_numberOfKeyIDs; uint16_t offset = 0U; for (auto entry : m_keyIds) { - SET_UINT16(entry, data, 16U + offset); + SET_UINT16(entry, data, 16U + (m_bodyOffset + offset)); offset += 2U; } } +/* Returns a string that represents the current KMM frame. */ + +std::string KMMInventoryResponseListKeyIDs::toString() +{ + return std::string("KMM, INVENTORY_RSP (Inventory Response, Active Key IDs)"); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.h b/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.h index 0ab5ea9e6..b44666330 100644 --- a/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.h +++ b/src/common/p25/kmm/KMMInventoryResponseListKeyIDs.h @@ -28,6 +28,19 @@ namespace p25 { namespace kmm { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup p25_kmm + * @{ + */ + + const uint32_t KMM_BODY_INV_RSP_LIST_KIDS_LENGTH = 3U; + + /** @} */ + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -61,6 +74,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief Encryption keyset ID. diff --git a/src/common/p25/kmm/KMMInventoryResponseListKeysets.cpp b/src/common/p25/kmm/KMMInventoryResponseListKeysets.cpp index 9844937db..2f6b29d3a 100644 --- a/src/common/p25/kmm/KMMInventoryResponseListKeysets.cpp +++ b/src/common/p25/kmm/KMMInventoryResponseListKeysets.cpp @@ -40,7 +40,7 @@ KMMInventoryResponseListKeysets::~KMMInventoryResponseListKeysets() = default; uint32_t KMMInventoryResponseListKeysets::length() const { - uint32_t len = KMM_INVENTORY_RSP_HDR_LENGTH; + uint32_t len = KMMInventoryResponseHeader::length(); len += m_keysetIds.size(); return len; @@ -55,7 +55,7 @@ bool KMMInventoryResponseListKeysets::decode(const uint8_t* data) KMMInventoryResponseHeader::decodeHeader(data); for (uint16_t i = 0U; i < m_numberOfItems; i++) { - uint8_t keysetId = data[13U + i]; + uint8_t keysetId = data[13U + (m_bodyOffset + i)]; m_keysetIds.push_back(keysetId); } @@ -67,18 +67,25 @@ bool KMMInventoryResponseListKeysets::decode(const uint8_t* data) void KMMInventoryResponseListKeysets::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_INVENTORY_RSP_HDR_LENGTH; + m_messageLength = length(); m_numberOfItems = (uint16_t)m_keysetIds.size(); KMMInventoryResponseHeader::encodeHeader(data); uint16_t offset = 0U; for (auto entry : m_keysetIds) { - data[13U + offset] = entry; + data[13U + (m_bodyOffset + offset)] = entry; offset += 1U; } } +/* Returns a string that represents the current KMM frame. */ + +std::string KMMInventoryResponseListKeysets::toString() +{ + return std::string("KMM, INVENTORY_RSP (Inventory Response, Active Keyset IDs)"); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMInventoryResponseListKeysets.h b/src/common/p25/kmm/KMMInventoryResponseListKeysets.h index a04f06d27..617752a31 100644 --- a/src/common/p25/kmm/KMMInventoryResponseListKeysets.h +++ b/src/common/p25/kmm/KMMInventoryResponseListKeysets.h @@ -61,6 +61,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief List of keyset IDs. diff --git a/src/common/p25/kmm/KMMModifyKey.cpp b/src/common/p25/kmm/KMMModifyKey.cpp index cefb14347..993d140e1 100644 --- a/src/common/p25/kmm/KMMModifyKey.cpp +++ b/src/common/p25/kmm/KMMModifyKey.cpp @@ -52,7 +52,7 @@ KMMModifyKey::~KMMModifyKey() uint32_t KMMModifyKey::length() const { - uint32_t len = KMM_MODIFY_KEY_LENGTH; + uint32_t len = KMMFrame::length() + KMM_BODY_MODIFY_KEY_LENGTH; if (m_miSet) len += MI_LENGTH_BYTES; len += m_keysetItem.length(); @@ -68,39 +68,39 @@ bool KMMModifyKey::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_decryptInfoFmt = data[10U]; // Decryption Instruction Format - m_algId = data[11U]; // Algorithm ID - m_kId = GET_UINT16(data, 12U); // Key ID + m_decryptInfoFmt = data[10U + m_bodyOffset]; // Decryption Instruction Format + m_algId = data[11U + m_bodyOffset]; // Algorithm ID + m_kId = GET_UINT16(data, 12U + m_bodyOffset); // Key ID uint16_t offset = 0U; if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); - ::memcpy(m_mi, data + 14U, MI_LENGTH_BYTES); - offset += 9U; + ::memcpy(m_mi, data + (m_bodyOffset + 14U), MI_LENGTH_BYTES); + offset += MI_LENGTH_BYTES; } - m_keysetItem.keysetId(data[14U + offset]); - m_keysetItem.algId(data[15U + offset]); - m_keysetItem.keyLength(data[16U + offset]); + m_keysetItem.keysetId(data[14U + (m_bodyOffset + offset)]); + m_keysetItem.algId(data[15U + (m_bodyOffset + offset)]); + m_keysetItem.keyLength(data[16U + (m_bodyOffset + offset)]); - uint8_t keyCount = data[17U + offset]; + uint8_t keyCount = data[17U + (m_bodyOffset + offset)]; for (uint8_t i = 0U; i < keyCount; i++) { KeyItem key = KeyItem(); DECLARE_UINT8_ARRAY(keyPayload, m_keysetItem.keyLength()); - uint8_t keyFormat = data[18U + offset]; + uint8_t keyFormat = data[18U + (m_bodyOffset + offset)]; uint8_t keyNameLen = keyFormat & 0x1FU; key.keyFormat(keyFormat & 0xE0U); - uint16_t sln = GET_UINT16(data, 19U + offset); + uint16_t sln = GET_UINT16(data, 19U + (m_bodyOffset + offset)); key.sln(sln); - uint16_t kId = GET_UINT16(data, 21U + offset); + uint16_t kId = GET_UINT16(data, 21U + (m_bodyOffset + offset)); key.kId(kId); - ::memcpy(keyPayload, data + (23U + offset), m_keysetItem.keyLength()); + ::memcpy(keyPayload, data + (23U + (m_bodyOffset + offset)), m_keysetItem.keyLength()); key.setKey(keyPayload, m_keysetItem.keyLength()); m_keysetItem.push_back(key); @@ -124,37 +124,67 @@ void KMMModifyKey::encode(uint8_t* data) m_decryptInfoFmt = KMM_DECRYPT_INSTRUCT_NONE; } - data[10U] = m_decryptInfoFmt; // Decryption Instruction Format - data[11U] = m_algId; // Algorithm ID - SET_UINT16(m_kId, data, 12U); // Key ID + data[10U + m_bodyOffset] = m_decryptInfoFmt; // Decryption Instruction Format + data[11U + m_bodyOffset] = m_algId; // Algorithm ID + SET_UINT16(m_kId, data, 12U + m_bodyOffset); // Key ID uint16_t offset = 0U; if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { - ::memcpy(data + 14U, m_mi, MI_LENGTH_BYTES); + ::memcpy(data + (m_bodyOffset + 14U), m_mi, MI_LENGTH_BYTES); offset += 9U; } - data[14U + offset] = m_keysetItem.keysetId(); - data[15U + offset] = m_keysetItem.algId(); - data[16U + offset] = m_keysetItem.keyLength(); + data[14U + (m_bodyOffset + offset)] = m_keysetItem.keysetId(); + data[15U + (m_bodyOffset + offset)] = m_keysetItem.algId(); + data[16U + (m_bodyOffset + offset)] = m_keysetItem.keyLength(); uint8_t keyCount = m_keysetItem.keys().size(); - data[17U + offset] = keyCount; + data[17U + (m_bodyOffset + offset)] = keyCount; for (auto key : m_keysetItem.keys()) { uint8_t keyNameLen = key.keyFormat() & 0x1FU; - data[18U + offset] = key.keyFormat(); - SET_UINT16(key.sln(), data, 19U + offset); - SET_UINT16(key.kId(), data, 21U + offset); + data[18U + (m_bodyOffset + offset)] = key.keyFormat(); + SET_UINT16(key.sln(), data, 19U + (m_bodyOffset + offset)); + SET_UINT16(key.kId(), data, 21U + (m_bodyOffset + offset)); DECLARE_UINT8_ARRAY(keyPayload, m_keysetItem.keyLength()); key.getKey(keyPayload); - ::memcpy(data + (23U + offset), keyPayload, m_keysetItem.keyLength()); + ::memcpy(data + (23U + (m_bodyOffset + offset)), keyPayload, m_keysetItem.keyLength()); offset += 5U + keyNameLen + m_keysetItem.keyLength(); } } +/* Returns a string that represents the current KMM frame. */ + +std::string KMMModifyKey::toString() +{ + return std::string("KMM, MODIFY_KEY_CMD (Modify Key)"); +} + +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void KMMModifyKey::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + m_miSet = true; + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void KMMModifyKey::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMModifyKey.h b/src/common/p25/kmm/KMMModifyKey.h index 86fc9501e..fa8e0cac3 100644 --- a/src/common/p25/kmm/KMMModifyKey.h +++ b/src/common/p25/kmm/KMMModifyKey.h @@ -37,7 +37,7 @@ namespace p25 * @{ */ - const uint32_t KMM_MODIFY_KEY_LENGTH = KMM_FRAME_LENGTH + 8U; + const uint32_t KMM_BODY_MODIFY_KEY_LENGTH = 8U; /** @} */ @@ -74,6 +74,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + /** @name Encryption data */ /** * @brief Sets the encryption message indicator. @@ -99,7 +105,7 @@ namespace p25 /** * @brief Encryption key ID. */ - DECLARE_PROPERTY(uint32_t, kId, KId); + DECLARE_PROPERTY(uint16_t, kId, KId); /** * @brief diff --git a/src/common/p25/kmm/KMMNegativeAck.cpp b/src/common/p25/kmm/KMMNegativeAck.cpp index c3c45ce0c..f80ad3091 100644 --- a/src/common/p25/kmm/KMMNegativeAck.cpp +++ b/src/common/p25/kmm/KMMNegativeAck.cpp @@ -37,6 +37,14 @@ KMMNegativeAck::KMMNegativeAck() : KMMFrame(), KMMNegativeAck::~KMMNegativeAck() = default; +/* Gets the byte length of this KMMNegativeAck. */ + +uint32_t KMMNegativeAck::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_NEGATIVE_ACK_LENGTH; + return len; +} + /* Decode a KMM NAK. */ bool KMMNegativeAck::decode(const uint8_t* data) @@ -45,9 +53,9 @@ bool KMMNegativeAck::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_messageId = data[10U]; // Message ID - m_messageNo = GET_UINT16(data, 11U); // Message Number - m_status = data[13U]; // Status + m_messageId = data[10U + m_bodyOffset]; // Message ID + m_messageNo = GET_UINT16(data, 11U + m_bodyOffset); // Message Number + m_status = data[13U + m_bodyOffset]; // Status return true; } @@ -57,13 +65,20 @@ bool KMMNegativeAck::decode(const uint8_t* data) void KMMNegativeAck::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_NEGATIVE_ACK_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_messageId; // Message ID - SET_UINT16(m_messageNo, data, 11U); // Message Number - data[13U] = m_status; + data[10U + m_bodyOffset] = m_messageId; // Message ID + SET_UINT16(m_messageNo, data, 11U + m_bodyOffset); // Message Number + data[13U + m_bodyOffset] = m_status; // Status +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMNegativeAck::toString() +{ + return std::string("KMM, NAK (Negative Acknowledge)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMNegativeAck.h b/src/common/p25/kmm/KMMNegativeAck.h index bf3669ff2..a8c070350 100644 --- a/src/common/p25/kmm/KMMNegativeAck.h +++ b/src/common/p25/kmm/KMMNegativeAck.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_NEGATIVE_ACK_LENGTH = KMM_FRAME_LENGTH + 4U; + const uint32_t KMM_BODY_NEGATIVE_ACK_LENGTH = 4U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMNegativeAck(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM NAK. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief diff --git a/src/common/p25/kmm/KMMNoService.cpp b/src/common/p25/kmm/KMMNoService.cpp index d231d283b..03c030ec1 100644 --- a/src/common/p25/kmm/KMMNoService.cpp +++ b/src/common/p25/kmm/KMMNoService.cpp @@ -50,11 +50,18 @@ bool KMMNoService::decode(const uint8_t* data) void KMMNoService::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_NO_SERVICE_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); } +/* Returns a string that represents the current KMM frame. */ + +std::string KMMNoService::toString() +{ + return std::string("KMM, NO_SERVICE (No Service)"); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMNoService.h b/src/common/p25/kmm/KMMNoService.h index 62d5540ab..8af8a0dba 100644 --- a/src/common/p25/kmm/KMMNoService.h +++ b/src/common/p25/kmm/KMMNoService.h @@ -27,19 +27,6 @@ namespace p25 { namespace kmm { - // --------------------------------------------------------------------------- - // Constants - // --------------------------------------------------------------------------- - - /** - * @addtogroup p25_kmm - * @{ - */ - - const uint32_t KMM_NO_SERVICE_LENGTH = KMM_FRAME_LENGTH; - - /** @} */ - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -67,6 +54,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: DECLARE_COPY(KMMNoService); }; diff --git a/src/common/p25/kmm/KMMRegistrationCommand.cpp b/src/common/p25/kmm/KMMRegistrationCommand.cpp index c45101ec0..33a554556 100644 --- a/src/common/p25/kmm/KMMRegistrationCommand.cpp +++ b/src/common/p25/kmm/KMMRegistrationCommand.cpp @@ -36,6 +36,14 @@ KMMRegistrationCommand::KMMRegistrationCommand() : KMMFrame(), KMMRegistrationCommand::~KMMRegistrationCommand() = default; +/* Gets the byte length of this KMMRegistrationCommand. */ + +uint32_t KMMRegistrationCommand::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_REGISTRATION_CMD_LENGTH; + return len; +} + /* Decode a KMM modify key. */ bool KMMRegistrationCommand::decode(const uint8_t* data) @@ -44,8 +52,8 @@ bool KMMRegistrationCommand::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_bodyFormat = data[10U]; // Body Format - m_kmfRSI = GET_UINT24(data, 11U); // KMF RSI + m_bodyFormat = data[10U + m_bodyOffset]; // Body Format + m_kmfRSI = GET_UINT24(data, 11U + m_bodyOffset); // KMF RSI return true; } @@ -55,13 +63,20 @@ bool KMMRegistrationCommand::decode(const uint8_t* data) void KMMRegistrationCommand::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_REGISTRATION_CMD_LENGTH; + m_messageLength = length(); m_bodyFormat = 0U; // reset this to none -- we don't support warm start right now KMMFrame::encodeHeader(data); - data[10U] = m_bodyFormat; // Body Format - SET_UINT24(m_kmfRSI, data, 11U); // KMF RSI + data[10U + m_bodyOffset] = m_bodyFormat; // Body Format + SET_UINT24(m_kmfRSI, data, 11U + m_bodyOffset); // KMF RSI +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMRegistrationCommand::toString() +{ + return std::string("KMM, REG_CMD (Registration Command)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMRegistrationCommand.h b/src/common/p25/kmm/KMMRegistrationCommand.h index 95a060fa3..033563d4a 100644 --- a/src/common/p25/kmm/KMMRegistrationCommand.h +++ b/src/common/p25/kmm/KMMRegistrationCommand.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_REGISTRATION_CMD_LENGTH = KMM_FRAME_LENGTH + 4U; + const uint32_t KMM_BODY_REGISTRATION_CMD_LENGTH = 4U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMRegistrationCommand(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM deregistration command. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief diff --git a/src/common/p25/kmm/KMMRegistrationResponse.cpp b/src/common/p25/kmm/KMMRegistrationResponse.cpp index eeba92ffd..40900cd4a 100644 --- a/src/common/p25/kmm/KMMRegistrationResponse.cpp +++ b/src/common/p25/kmm/KMMRegistrationResponse.cpp @@ -27,7 +27,7 @@ using namespace p25::kmm; KMMRegistrationResponse::KMMRegistrationResponse() : KMMFrame(), m_status(KMM_Status::CMD_PERFORMED) { - m_messageId = KMM_MessageType::DEREG_RSP; + m_messageId = KMM_MessageType::REG_RSP; m_respKind = KMM_ResponseKind::IMMEDIATE; } @@ -35,6 +35,14 @@ KMMRegistrationResponse::KMMRegistrationResponse() : KMMFrame(), KMMRegistrationResponse::~KMMRegistrationResponse() = default; +/* Gets the byte length of this KMMRegistrationResponse. */ + +uint32_t KMMRegistrationResponse::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_REGISTRATION_RSP_LENGTH; + return len; +} + /* Decode a KMM modify key. */ bool KMMRegistrationResponse::decode(const uint8_t* data) @@ -43,7 +51,7 @@ bool KMMRegistrationResponse::decode(const uint8_t* data) KMMFrame::decodeHeader(data); - m_status = data[10U]; // Status + m_status = data[10U + m_bodyOffset]; // Status return true; } @@ -53,11 +61,18 @@ bool KMMRegistrationResponse::decode(const uint8_t* data) void KMMRegistrationResponse::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_REGISTRATION_RSP_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); - data[10U] = m_status; // Status + data[10U + m_bodyOffset] = m_status; // Status +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMRegistrationResponse::toString() +{ + return std::string("KMM, REG_RSP (Registration Response)"); } // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMRegistrationResponse.h b/src/common/p25/kmm/KMMRegistrationResponse.h index 5dadbeb3c..ccd891440 100644 --- a/src/common/p25/kmm/KMMRegistrationResponse.h +++ b/src/common/p25/kmm/KMMRegistrationResponse.h @@ -36,7 +36,7 @@ namespace p25 * @{ */ - const uint32_t KMM_REGISTRATION_RSP_LENGTH = KMM_FRAME_LENGTH + 1U; + const uint32_t KMM_BODY_REGISTRATION_RSP_LENGTH = 1U; /** @} */ @@ -55,6 +55,12 @@ namespace p25 */ ~KMMRegistrationResponse(); + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + /** * @brief Decode a KMM deregistration response. * @param[in] data Buffer containing KMM frame data to decode. @@ -67,6 +73,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: /** * @brief diff --git a/src/common/p25/kmm/KMMRekeyAck.cpp b/src/common/p25/kmm/KMMRekeyAck.cpp new file mode 100644 index 000000000..61a321f51 --- /dev/null +++ b/src/common/p25/kmm/KMMRekeyAck.cpp @@ -0,0 +1,122 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/kmm/KMMRekeyAck.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::kmm; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the KMMRekeyAck class. */ + +KMMRekeyAck::KMMRekeyAck() : KMMFrame(), + m_messageId(0U), + m_numberOfKeyStatus(0U), + m_keystatus() +{ + m_messageId = KMM_MessageType::REKEY_ACK; + m_respKind = KMM_ResponseKind::NONE; +} + +/* Finalizes a instance of the KMMRekeyAck class. */ + +KMMRekeyAck::~KMMRekeyAck() = default; + +/* Gets the byte length of this KMMRekeyAck. */ + +uint32_t KMMRekeyAck::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_REKEY_ACK_LENGTH; + len += m_keystatus.size() * 4U; + + return len; +} + +/* Decode a KMM rekey ack. */ + +bool KMMRekeyAck::decode(const uint8_t* data) +{ + assert(data != nullptr); + + KMMFrame::decodeHeader(data); + + m_messageId = data[10U + m_bodyOffset]; // Message ID + m_numberOfKeyStatus = data[11U + m_bodyOffset]; // Number of Key Status + + uint16_t offset = 0U; + for (uint8_t i = 0U; i < m_numberOfKeyStatus; i++) { + KeyStatus keyStatus = KeyStatus(); + + keyStatus.algId(data[12U + (m_bodyOffset + offset)]); + + uint16_t kId = GET_UINT16(data, 13U + (m_bodyOffset + offset)); + keyStatus.kId(kId); + + keyStatus.status(data[15U + (m_bodyOffset + offset)]); + + m_keystatus.push_back(keyStatus); + offset += 4U; + } + + return true; +} + +/* Encode a KMM rekey ack. */ + +void KMMRekeyAck::encode(uint8_t* data) +{ + assert(data != nullptr); + m_messageLength = length(); + + KMMFrame::encodeHeader(data); + + data[10U + m_bodyOffset] = m_messageId; // Message ID + data[11U + m_bodyOffset] = m_numberOfKeyStatus; // Number of Key Status + + uint16_t offset = 0U; + for (auto keyStatus : m_keystatus) { + data[12U + (m_bodyOffset + offset)] = keyStatus.algId(); + SET_UINT16(keyStatus.kId(), data, 13U + (m_bodyOffset + offset)); + + data[15U + (m_bodyOffset + offset)] = keyStatus.status(); + offset += 4U; + } +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMRekeyAck::toString() +{ + return std::string("KMM, REKEY_ACK (Rekey Acknowledge)"); +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void KMMRekeyAck::copy(const KMMRekeyAck& data) +{ + KMMFrame::copy(data); + + m_messageId = data.m_messageId; + m_numberOfKeyStatus = data.m_numberOfKeyStatus; + + m_keystatus = data.m_keystatus; +} diff --git a/src/common/p25/kmm/KMMRekeyAck.h b/src/common/p25/kmm/KMMRekeyAck.h new file mode 100644 index 000000000..831442a1a --- /dev/null +++ b/src/common/p25/kmm/KMMRekeyAck.h @@ -0,0 +1,103 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file KMMRekeyAck.h + * @ingroup p25_kmm + * @file KMMRekeyAck.cpp + * @ingroup p25_kmm + */ +#if !defined(__P25_KMM__KMM_REKEY_ACK_H__) +#define __P25_KMM__KMM_REKEY_ACK_H__ + +#include "common/Defines.h" +#include "common/p25/kmm/KMMFrame.h" +#include "common/p25/kmm/KeysetItem.h" +#include "common/Utils.h" + +#include +#include + +namespace p25 +{ + namespace kmm + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup p25_kmm + * @{ + */ + + const uint32_t KMM_BODY_REKEY_ACK_LENGTH = 2U; + + /** @} */ + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + class HOST_SW_API KMMRekeyAck : public KMMFrame { + public: + /** + * @brief Initializes a new instance of the KMMRekeyAck class. + */ + KMMRekeyAck(); + /** + * @brief Finalizes a instance of the KMMRekeyAck class. + */ + ~KMMRekeyAck(); + + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + + /** + * @brief Decode a KMM rekey ack. + * @param[in] data Buffer containing KMM frame data to decode. + * @returns bool True, if decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a KMM rekey ack. + * @param[out] data Buffer to encode KMM frame data to. + */ + void encode(uint8_t* data) override; + + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + + public: + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, messageId, MessageId); + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, numberOfKeyStatus, NumberOfKeyStatus); + + /** + * @brief List of key status. + */ + DECLARE_PROPERTY(std::vector, keystatus, KeyStatus); + + DECLARE_COPY(KMMRekeyAck); + }; + } // namespace kmm +} // namespace p25 + +#endif // __P25_KMM__KMM_REKEY_ACK_H__ diff --git a/src/common/p25/kmm/KMMRekeyCommand.cpp b/src/common/p25/kmm/KMMRekeyCommand.cpp new file mode 100644 index 000000000..d463a3aed --- /dev/null +++ b/src/common/p25/kmm/KMMRekeyCommand.cpp @@ -0,0 +1,228 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/kmm/KMMRekeyCommand.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::kmm; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the KMMRekeyCommand class. */ + +KMMRekeyCommand::KMMRekeyCommand() : KMMFrame(), + m_decryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE), + m_algId(ALGO_UNENCRYPT), + m_kId(0U), + m_keysets(), + m_miSet(false), + m_mi(nullptr) +{ + m_messageId = KMM_MessageType::REKEY_CMD; + m_respKind = KMM_ResponseKind::IMMEDIATE; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); +} + +/* Finalizes a instance of the KMMRekeyCommand class. */ + +KMMRekeyCommand::~KMMRekeyCommand() +{ + if (m_mi != nullptr) { + delete[] m_mi; + m_mi = nullptr; + } +} + +/* Gets the byte length of this KMMRekeyCommand. */ + +uint32_t KMMRekeyCommand::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_REKEY_CMD_LENGTH; + if (m_miSet) + len += MI_LENGTH_BYTES; + + for (auto keysetItem : m_keysets) + len += keysetItem.length(); + + return len; +} + +/* Decode a KMM rekey command. */ + +bool KMMRekeyCommand::decode(const uint8_t* data) +{ + assert(data != nullptr); + + KMMFrame::decodeHeader(data); + + m_decryptInfoFmt = data[10U + m_bodyOffset]; // Decryption Instruction Format + m_algId = data[11U + m_bodyOffset]; // Algorithm ID + m_kId = GET_UINT16(data, 12U + m_bodyOffset); // Key ID + + uint16_t offset = 0U; + if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, data + (m_bodyOffset + 14U), MI_LENGTH_BYTES); + offset += 9U; + } + + uint8_t keysetCount = data[14U + (m_bodyOffset + offset)]; + for (uint8_t n = 0U; n < keysetCount; n++) { + KeysetItem keysetItem = KeysetItem(); + keysetItem.keysetId(data[16U + (m_bodyOffset + offset)]); + keysetItem.algId(data[17U + (m_bodyOffset + offset)]); + keysetItem.keyLength(data[18U + (m_bodyOffset + offset)]); + + uint8_t keyCount = data[19U + (m_bodyOffset + offset)]; + for (uint8_t i = 0U; i < keyCount; i++) { + KeyItem key = KeyItem(); + + DECLARE_UINT8_ARRAY(keyPayload, keysetItem.keyLength()); + + uint8_t keyFormat = data[20U + (m_bodyOffset + offset)]; + uint8_t keyNameLen = keyFormat & 0x1FU; + + key.keyFormat(keyFormat & 0xE0U); + + uint16_t sln = GET_UINT16(data, 21U + (m_bodyOffset + offset)); + key.sln(sln); + + uint16_t kId = GET_UINT16(data, 23U + (m_bodyOffset + offset)); + key.kId(kId); + + ::memcpy(keyPayload, data + (25U + (m_bodyOffset + offset)), keysetItem.keyLength()); + key.setKey(keyPayload, keysetItem.keyLength()); + + keysetItem.push_back(key); + + offset += 5U + keyNameLen + keysetItem.keyLength(); + } + + m_keysets.push_back(keysetItem); + offset += 4U; + } + + return true; +} + +/* Encode a KMM rekey command. */ + +void KMMRekeyCommand::encode(uint8_t* data) +{ + assert(data != nullptr); + m_messageLength = length(); + + KMMFrame::encodeHeader(data); + + if (!m_miSet && m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { + m_decryptInfoFmt = KMM_DECRYPT_INSTRUCT_NONE; + } + + data[10U + m_bodyOffset] = m_decryptInfoFmt; // Decryption Instruction Format + data[11U + m_bodyOffset] = m_algId; // Algorithm ID + SET_UINT16(m_kId, data, 12U + m_bodyOffset); // Key ID + + uint16_t offset = 0U; + if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { + ::memcpy(data + (m_bodyOffset + 14U), m_mi, MI_LENGTH_BYTES); + offset += 9U; + } + + uint8_t keysetCount = m_keysets.size(); + data[14U + (m_bodyOffset + offset)] = keysetCount; + + for (auto keysetItem : m_keysets) { + data[15U + (m_bodyOffset + offset)] = 0U; // currently we won't send KEKs + data[16U + (m_bodyOffset + offset)] = keysetItem.keysetId(); + data[17U + (m_bodyOffset + offset)] = keysetItem.algId(); + data[18U + (m_bodyOffset + offset)] = keysetItem.keyLength(); + + uint8_t keyCount = keysetItem.keys().size(); + data[19U + (m_bodyOffset + offset)] = keyCount; + for (auto key : keysetItem.keys()) { + uint8_t keyNameLen = key.keyFormat() & 0x1FU; + data[20U + (m_bodyOffset + offset)] = key.keyFormat(); + SET_UINT16(key.sln(), data, 21U + (m_bodyOffset + offset)); + SET_UINT16(key.kId(), data, 23U + (m_bodyOffset + offset)); + + DECLARE_UINT8_ARRAY(keyPayload, keysetItem.keyLength()); + key.getKey(keyPayload); + + Utils::dump(2U, "keyPayload", keyPayload, keysetItem.keyLength()); + + ::memcpy(data + (25U + (m_bodyOffset + offset)), keyPayload, keysetItem.keyLength()); + + offset += 5U + keyNameLen + keysetItem.keyLength(); + } + + offset += 4U; + } +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMRekeyCommand::toString() +{ + return std::string("KMM, REKEY_CMD (Rekey Command)"); +} + +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void KMMRekeyCommand::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + m_miSet = true; + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void KMMRekeyCommand::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void KMMRekeyCommand::copy(const KMMRekeyCommand& data) +{ + KMMFrame::copy(data); + + m_decryptInfoFmt = data.m_decryptInfoFmt; + m_algId = data.m_algId; + m_kId = data.m_kId; + + if (data.m_mi != nullptr) { + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); + } + + m_keysets = data.m_keysets; +} diff --git a/src/common/p25/kmm/KMMRekeyCommand.h b/src/common/p25/kmm/KMMRekeyCommand.h new file mode 100644 index 000000000..1879f292f --- /dev/null +++ b/src/common/p25/kmm/KMMRekeyCommand.h @@ -0,0 +1,125 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file KMMRekeyCommand.h + * @ingroup p25_kmm + * @file KMMRekeyCommand.cpp + * @ingroup p25_kmm + */ +#if !defined(__P25_KMM__KMM_REKEY_COMMAND_H__) +#define __P25_KMM__KMM_REKEY_COMMAND_H__ + +#include "common/Defines.h" +#include "common/p25/kmm/KMMFrame.h" +#include "common/p25/kmm/KeysetItem.h" +#include "common/Utils.h" + +#include +#include + +namespace p25 +{ + namespace kmm + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup p25_kmm + * @{ + */ + + const uint32_t KMM_BODY_REKEY_CMD_LENGTH = 4U; + + /** @} */ + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + class HOST_SW_API KMMRekeyCommand : public KMMFrame { + public: + /** + * @brief Initializes a new instance of the KMMRekeyCommand class. + */ + KMMRekeyCommand(); + /** + * @brief Finalizes a instance of the KMMRekeyCommand class. + */ + ~KMMRekeyCommand(); + + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + + /** + * @brief Decode a KMM rekey command. + * @param[in] data Buffer containing KMM frame data to decode. + * @returns bool True, if decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a KMM rekey command. + * @param[out] data Buffer to encode KMM frame data to. + */ + void encode(uint8_t* data) override; + + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + + /** @name Encryption data */ + /** + * @brief Sets the encryption message indicator. + * @param[in] mi Buffer containing the 9-byte Message Indicator. + */ + void setMI(const uint8_t* mi); + /** + * @brief Gets the encryption message indicator. + * @param[out] mi Buffer containing the 9-byte Message Indicator. + */ + void getMI(uint8_t* mi) const; + /** @} */ + + public: + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, decryptInfoFmt, DecryptInfoFmt); + /** + * @brief Encryption algorithm ID. + */ + DECLARE_PROPERTY(uint8_t, algId, AlgId); + /** + * @brief Encryption key ID. + */ + DECLARE_PROPERTY(uint16_t, kId, KId); + + /** + * @brief List of keysets. + */ + DECLARE_PROPERTY(std::vector, keysets, Keysets); + + DECLARE_COPY(KMMRekeyCommand); + + private: + // Encryption data + bool m_miSet; + uint8_t* m_mi; + }; + } // namespace kmm +} // namespace p25 + +#endif // __P25_KMM__KMM_REKEY_COMMAND_H__ diff --git a/src/common/p25/kmm/KMMUnableToDecrypt.cpp b/src/common/p25/kmm/KMMUnableToDecrypt.cpp new file mode 100644 index 000000000..9512d2e45 --- /dev/null +++ b/src/common/p25/kmm/KMMUnableToDecrypt.cpp @@ -0,0 +1,217 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/kmm/KMMUnableToDecrypt.h" +#include "Log.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::kmm; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the KMMUnableToDecrypt class. */ + +KMMUnableToDecrypt::KMMUnableToDecrypt() : KMMFrame(), + m_bodyFormat(0U), + m_algId(ALGO_UNENCRYPT), + m_kId(0U), + m_status(0U), + m_decryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE), + m_decryptAlgId(ALGO_UNENCRYPT), + m_decryptKId(0U), + m_key(), + m_miSet(false), + m_mi(nullptr) +{ + m_messageId = KMM_MessageType::UNABLE_TO_DECRYPT; + m_respKind = KMM_ResponseKind::NONE; + + m_mi = new uint8_t[MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); +} + +/* Finalizes a instance of the KMMUnableToDecrypt class. */ + +KMMUnableToDecrypt::~KMMUnableToDecrypt() +{ + if (m_mi != nullptr) { + delete[] m_mi; + m_mi = nullptr; + } +} + +/* Gets the byte length of this KMMUnableToDecrypt. */ + +uint32_t KMMUnableToDecrypt::length() const +{ + uint32_t len = KMMFrame::length() + KMM_BODY_UNABLE_TO_DECRYPT_LENGTH; + if ((m_bodyFormat & KEY_FORMAT_TEK) == KEY_FORMAT_TEK) { + len += 3U; + } + + if (m_miSet) { + len += MI_LENGTH_BYTES; + } + + len += 1U + m_key.getLength(); + + return len; +} + +/* Decode a KMM Unable-To-Decrypt. */ + +bool KMMUnableToDecrypt::decode(const uint8_t* data) +{ + assert(data != nullptr); + + KMMFrame::decodeHeader(data); + + m_bodyFormat = data[10U + m_bodyOffset]; // Body Format + m_algId = data[12U + m_bodyOffset]; // Algorithm ID + m_kId = GET_UINT16(data, 13U + m_bodyOffset); // Key ID + m_status = data[15U + m_bodyOffset]; // Status + + uint8_t offset = 0U; + if ((m_bodyFormat & KEY_FORMAT_TEK) == KEY_FORMAT_TEK) { + m_decryptInfoFmt = data[16U + m_bodyOffset]; // Decrypt Info Format + m_decryptAlgId = data[17U + m_bodyOffset]; // Decrypt Algorithm ID + m_decryptKId = GET_UINT16(data, 18U + m_bodyOffset); // Decrypt Key ID + offset += 4U; + + if ((m_decryptInfoFmt & KMM_DECRYPT_INSTRUCT_MI) == KMM_DECRYPT_INSTRUCT_MI) { + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, data + (m_bodyOffset + 20U), MI_LENGTH_BYTES); + offset += MI_LENGTH_BYTES; + } + } + + uint8_t keyLength = data[16U + (m_bodyOffset + offset)]; + m_key.keyFormat(data[18U + (m_bodyOffset + offset)]); + + uint16_t sln = GET_UINT16(data, 19U + (m_bodyOffset + offset)); + m_key.sln(sln); + + uint16_t kId = GET_UINT16(data, 21U + (m_bodyOffset + offset)); + m_key.kId(kId); + + DECLARE_UINT8_ARRAY(keyPayload, keyLength); + + ::memcpy(keyPayload, data + (23U + (m_bodyOffset + offset)), keyLength); + m_key.setKey(keyPayload, keyLength); + + return true; +} + +/* Encode a KMM Unable-To-Decrypt. */ + +void KMMUnableToDecrypt::encode(uint8_t* data) +{ + assert(data != nullptr); + m_messageLength = length(); + + KMMFrame::encodeHeader(data); + + if (!m_miSet && m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) { + m_decryptInfoFmt = KMM_DECRYPT_INSTRUCT_NONE; + } + + data[10U + m_bodyOffset] = m_bodyFormat; // Body Format + data[11U + m_bodyOffset] = m_algId; // Algorithm ID + SET_UINT16(m_kId, data, 13U + m_bodyOffset); // Key ID + data[15U + m_bodyOffset] = m_status; // Status + + uint8_t offset = 0U; + if ((m_bodyFormat & KEY_FORMAT_TEK) == KEY_FORMAT_TEK) { + data[16U + m_bodyOffset] = m_decryptInfoFmt; // Decrypt Info Format + data[17U + m_bodyOffset] = m_decryptAlgId; // Decrypt Algorithm ID + SET_UINT16(m_decryptKId, data, 18U + m_bodyOffset); // Decrypt Key ID + + if (m_miSet) { + ::memcpy(data + (m_bodyOffset + 20U), m_mi, MI_LENGTH_BYTES); + offset += MI_LENGTH_BYTES; + } + } + + data[16U + (m_bodyOffset + offset)] = m_key.getLength(); + + uint16_t sln = m_key.sln(); + SET_UINT16(sln, data, 19U + (m_bodyOffset + offset)); + + uint16_t kId = m_key.kId(); + SET_UINT16(kId, data, 21U + (m_bodyOffset + offset)); + + DECLARE_UINT8_ARRAY(keyPayload, m_key.getLength()); + m_key.getKey(keyPayload); + + ::memcpy(data + (23U + (m_bodyOffset + offset)), keyPayload, m_key.getLength()); +} + +/* Returns a string that represents the current KMM frame. */ + +std::string KMMUnableToDecrypt::toString() +{ + return std::string("KMM, UNABLE_TO_DECRYPT (Unable to Decrypt)"); +} + +/* +** Encryption data +*/ + +/* Sets the encryption message indicator. */ + +void KMMUnableToDecrypt::setMI(const uint8_t* mi) +{ + assert(mi != nullptr); + + m_miSet = true; + ::memcpy(m_mi, mi, MI_LENGTH_BYTES); +} + +/* Gets the encryption message indicator. */ + +void KMMUnableToDecrypt::getMI(uint8_t* mi) const +{ + assert(mi != nullptr); + + ::memcpy(mi, m_mi, MI_LENGTH_BYTES); +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- + +/* Internal helper to copy the the class. */ + +void KMMUnableToDecrypt::copy(const KMMUnableToDecrypt& data) +{ + KMMFrame::copy(data); + + m_bodyFormat = data.m_bodyFormat; + m_algId = data.m_algId; + m_kId = data.m_kId; + m_status = data.m_status; + + m_decryptInfoFmt = data.m_decryptInfoFmt; + m_decryptAlgId = data.m_decryptAlgId; + m_decryptKId = data.m_decryptKId; + + m_key = data.m_key; + + if (data.m_mi != nullptr) { + ::memset(m_mi, 0x00U, MI_LENGTH_BYTES); + ::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES); + } +} diff --git a/src/common/p25/kmm/KMMUnableToDecrypt.h b/src/common/p25/kmm/KMMUnableToDecrypt.h new file mode 100644 index 000000000..9aa08a819 --- /dev/null +++ b/src/common/p25/kmm/KMMUnableToDecrypt.h @@ -0,0 +1,142 @@ +// 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. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file KMMUnableToDecrypt.h + * @ingroup p25_kmm + * @file KMMUnableToDecrypt.cpp + * @ingroup p25_kmm + */ +#if !defined(__P25_KMM__KMM_UNABLE_TO_DECRYPT_H__) +#define __P25_KMM__KMM_UNABLE_TO_DECRYPT_H__ + +#include "common/Defines.h" +#include "common/p25/kmm/KMMFrame.h" +#include "common/p25/kmm/KeysetItem.h" +#include "common/Utils.h" + +#include +#include + +namespace p25 +{ + namespace kmm + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + /** + * @addtogroup p25_kmm + * @{ + */ + + const uint32_t KMM_BODY_UNABLE_TO_DECRYPT_LENGTH = 7U; + + /** @} */ + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + class HOST_SW_API KMMUnableToDecrypt : public KMMFrame { + public: + /** + * @brief Initializes a new instance of the KMMUnableToDecrypt class. + */ + KMMUnableToDecrypt(); + /** + * @brief Finalizes a instance of the KMMUnableToDecrypt class. + */ + ~KMMUnableToDecrypt(); + + /** + * @brief Gets the byte length of this KMMFrame. + * @return uint32_t Length of KMMFrame. + */ + uint32_t length() const override; + + /** + * @brief Decode a KMM Unable-To-Decrypt. + * @param[in] data Buffer containing KMM frame data to decode. + * @returns bool True, if decoded, otherwise false. + */ + bool decode(const uint8_t* data) override; + /** + * @brief Encode a KMM Unable-To-Decrypt. + * @param[out] data Buffer to encode KMM frame data to. + */ + void encode(uint8_t* data) override; + + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + + /** @name Encryption data */ + /** + * @brief Sets the encryption message indicator. + * @param[in] mi Buffer containing the 9-byte Message Indicator. + */ + void setMI(const uint8_t* mi); + /** + * @brief Gets the encryption message indicator. + * @param[out] mi Buffer containing the 9-byte Message Indicator. + */ + void getMI(uint8_t* mi) const; + /** @} */ + + public: + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, bodyFormat, BodyFormat); + /** + * @brief Encryption algorithm ID. + */ + DECLARE_PROPERTY(uint8_t, algId, AlgId); + /** + * @brief Encryption key ID. + */ + DECLARE_PROPERTY(uint16_t, kId, KId); + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, status, Status); + + /** + * @brief + */ + DECLARE_PROPERTY(uint8_t, decryptInfoFmt, DecryptInfoFmt); + /** + * @brief Encryption algorithm ID. + */ + DECLARE_PROPERTY(uint8_t, decryptAlgId, DecryptAlgId); + /** + * @brief Encryption key ID. + */ + DECLARE_PROPERTY(uint16_t, decryptKId, DecryptKId); + + /** + * @brief List of keys. + */ + DECLARE_PROPERTY(KeyItem, key, Key); + + DECLARE_COPY(KMMUnableToDecrypt); + + private: + // Encryption data + bool m_miSet; + uint8_t* m_mi; + }; + } // namespace kmm +} // namespace p25 + +#endif // __P25_KMM__KMM_UNABLE_TO_DECRYPT_H__ diff --git a/src/common/p25/kmm/KMMZeroize.cpp b/src/common/p25/kmm/KMMZeroize.cpp index ac31089b9..08711d1e7 100644 --- a/src/common/p25/kmm/KMMZeroize.cpp +++ b/src/common/p25/kmm/KMMZeroize.cpp @@ -50,11 +50,18 @@ bool KMMZeroize::decode(const uint8_t* data) void KMMZeroize::encode(uint8_t* data) { assert(data != nullptr); - m_messageLength = KMM_ZEROIZE_LENGTH; + m_messageLength = length(); KMMFrame::encodeHeader(data); } +/* Returns a string that represents the current KMM frame. */ + +std::string KMMZeroize::toString() +{ + return std::string("KMM, ZEROIZE_CMD (Zeroize Command)"); +} + // --------------------------------------------------------------------------- // Protected Class Members // --------------------------------------------------------------------------- diff --git a/src/common/p25/kmm/KMMZeroize.h b/src/common/p25/kmm/KMMZeroize.h index a405a69e0..a48eb16a5 100644 --- a/src/common/p25/kmm/KMMZeroize.h +++ b/src/common/p25/kmm/KMMZeroize.h @@ -27,19 +27,6 @@ namespace p25 { namespace kmm { - // --------------------------------------------------------------------------- - // Constants - // --------------------------------------------------------------------------- - - /** - * @addtogroup p25_kmm - * @{ - */ - - const uint32_t KMM_ZEROIZE_LENGTH = KMM_FRAME_LENGTH; - - /** @} */ - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -67,6 +54,12 @@ namespace p25 */ void encode(uint8_t* data) override; + /** + * @brief Returns a string that represents the current KMM frame. + * @returns std::string String representation of the KMM frame. + */ + std::string toString() override; + public: DECLARE_COPY(KMMZeroize); }; diff --git a/src/common/p25/kmm/KeysetItem.h b/src/common/p25/kmm/KeysetItem.h index 07391b9a4..7c0d6ca88 100644 --- a/src/common/p25/kmm/KeysetItem.h +++ b/src/common/p25/kmm/KeysetItem.h @@ -33,7 +33,7 @@ namespace p25 * @{ */ - const uint8_t MAX_ENC_KEY_LENGTH_BYTES = 32U; + const uint8_t MAX_ENC_KEY_LENGTH_BYTES = 40U; /** @} */ @@ -82,6 +82,15 @@ namespace p25 return *this; } + /** + * @brief Returns the length of this key item in bytes. + * @returns uint32_t Length of key item. + */ + uint32_t getLength() const + { + return 5U + m_keyLength; + } + /** * @brief Set the key material. * @param key @@ -210,6 +219,68 @@ namespace p25 */ DECLARE_PROPERTY_PLAIN(std::vector, keys); }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents a key status within a KMM frame packet. + * @ingroup p25_kmm + */ + class HOST_SW_API KeyStatus { + public: + /** + * @brief Initializes a new instance of the KeyStatus class. + */ + KeyStatus() : + m_algId(0U), + m_kId(0U), + m_status(0U) + { + /* stub */ + } + + /** + * @brief Equals operator. Copies this KeyStatus to another KeyStatus. + * @param data Instance of KeyStatus to copy. + */ + virtual KeyStatus& operator= (const KeyStatus& data) + { + if (this != &data) { + m_algId = data.m_algId; + m_kId = data.m_kId; + m_status = data.m_status; + } + + return *this; + } + + /** + * @brief Gets the byte length of this keyset item. + * @return uint32_t Length of keyset item. + */ + uint32_t length() const + { + uint32_t len = 3U; + return len; + } + + public: + /** + * @brief Encryption algorithm ID. + */ + DECLARE_PROPERTY_PLAIN(uint8_t, algId); + /** + * @brief Key ID. + */ + DECLARE_PROPERTY_PLAIN(uint16_t, kId); + + /** + * @brief Key ID. + */ + DECLARE_PROPERTY_PLAIN(uint8_t, status); + }; } // namespace kmm } // namespace p25 diff --git a/src/common/p25/lc/AMBT.cpp b/src/common/p25/lc/AMBT.cpp index 54cd15734..96f122205 100644 --- a/src/common/p25/lc/AMBT.cpp +++ b/src/common/p25/lc/AMBT.cpp @@ -113,7 +113,7 @@ bool AMBT::decode(const data::DataHeader& dataHeader, const data::DataBlock* blo dataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; } - if (m_verbose) { + if (s_verbose) { LogDebugEx(LOG_P25, "AMBT::decode()", "mfId = $%02X, lco = $%02X, ambt8 = $%02X, ambt9 = $%02X", m_mfId, m_lco, dataHeader.getAMBTField8(), dataHeader.getAMBTField9()); Utils::dump(2U, "P25, AMBT::decode(), pduUserData", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * dataHeader.getBlocksToFollow()); } diff --git a/src/common/p25/lc/LC.cpp b/src/common/p25/lc/LC.cpp index a0003e05a..931f7e0e6 100644 --- a/src/common/p25/lc/LC.cpp +++ b/src/common/p25/lc/LC.cpp @@ -29,7 +29,7 @@ using namespace p25::lc; // Static Class Members // --------------------------------------------------------------------------- -SiteData LC::m_siteData = SiteData(); +SiteData LC::s_siteData = SiteData(); // --------------------------------------------------------------------------- // Public Class Members @@ -62,6 +62,7 @@ LC::LC() : m_group(true), m_algId(ALGO_UNENCRYPT), m_kId(0U), + m_slotNo(0U), m_rsValue(0U), m_rs(), m_encryptOverride(false), @@ -673,10 +674,10 @@ void LC::encodeLC(uint8_t* rs) break; case LCO::GROUP_UPDT: rs[0U] |= 0x40U; // Implicit Operation - rsValue = m_siteData.channelId(); // Group A - Channel ID + rsValue = s_siteData.channelId(); // Group A - Channel ID rsValue = (rsValue << 12) + m_grpVchNo; // Group A - Channel Number rsValue = (rsValue << 16) + m_dstId; // Group A - Talkgroup Address - rsValue = (rsValue << 4) + m_siteData.channelId(); // Group B - Channel ID + rsValue = (rsValue << 4) + s_siteData.channelId(); // Group B - Channel ID rsValue = (rsValue << 12) + m_grpVchNoB; // Group B - Channel Number rsValue = (rsValue << 16) + m_dstIdB; // Group B - Talkgroup Address break; @@ -716,13 +717,13 @@ void LC::encodeLC(uint8_t* rs) break; case LCO::RFSS_STS_BCAST: rs[0U] |= 0x40U; // Implicit Operation - rsValue = m_siteData.lra(); // Location Registration Area - rsValue = (rsValue << 12) + m_siteData.sysId(); // System ID - rsValue = (rsValue << 8) + m_siteData.rfssId(); // RF Sub-System ID - rsValue = (rsValue << 8) + m_siteData.siteId(); // Site ID - rsValue = (rsValue << 4) + m_siteData.channelId(); // Channel ID - rsValue = (rsValue << 12) + m_siteData.channelNo(); // Channel Number - rsValue = (rsValue << 8) + m_siteData.serviceClass(); // System Service Class + rsValue = s_siteData.lra(); // Location Registration Area + rsValue = (rsValue << 12) + s_siteData.sysId(); // System ID + rsValue = (rsValue << 8) + s_siteData.rfssId(); // RF Sub-System ID + rsValue = (rsValue << 8) + s_siteData.siteId(); // Site ID + rsValue = (rsValue << 4) + s_siteData.channelId(); // Channel ID + rsValue = (rsValue << 12) + s_siteData.channelNo(); // Channel Number + rsValue = (rsValue << 8) + s_siteData.serviceClass(); // System Service Class break; default: LogError(LOG_P25, "LC::encodeLC(), unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); @@ -838,6 +839,8 @@ void LC::copy(const LC& data) m_callTimer = data.m_callTimer; + m_slotNo = data.m_slotNo; + m_rsValue = data.m_rsValue; m_algId = data.m_algId; @@ -881,7 +884,7 @@ void LC::copy(const LC& data) m_gotUserAlias = false; } - m_siteData = data.m_siteData; + s_siteData = data.s_siteData; } /* Decode LDU hamming FEC. */ diff --git a/src/common/p25/lc/LC.h b/src/common/p25/lc/LC.h index 1af45054d..d92d9dcb3 100644 --- a/src/common/p25/lc/LC.h +++ b/src/common/p25/lc/LC.h @@ -158,12 +158,12 @@ namespace p25 * @brief Gets the local site data. * @returns SiteData Currently set site data for the LC class. */ - static SiteData getSiteData() { return m_siteData; } + static SiteData getSiteData() { return s_siteData; } /** * @brief Sets the local site data. * @param siteData Site data to set for the LC class. */ - static void setSiteData(SiteData siteData) { m_siteData = siteData; } + static void setSiteData(SiteData siteData) { s_siteData = siteData; } /** @} */ public: @@ -249,6 +249,13 @@ namespace p25 DECLARE_PROPERTY(uint32_t, kId, KId); /** @} */ + /** @name Phase 2 Data */ + /** + * @brief Slot Number. + */ + DECLARE_PROPERTY(uint8_t, slotNo, SlotNo); + /** @} */ + /** @name Packed RS Data */ /** * @brief Packed RS Data. @@ -274,7 +281,7 @@ namespace p25 bool m_gotUserAlias; // Local Site data - static SiteData m_siteData; + static SiteData s_siteData; /** * @brief Internal helper to copy the class. diff --git a/src/common/p25/lc/TDULC.cpp b/src/common/p25/lc/TDULC.cpp index 94025eb06..87f18606d 100644 --- a/src/common/p25/lc/TDULC.cpp +++ b/src/common/p25/lc/TDULC.cpp @@ -26,9 +26,9 @@ using namespace p25::lc; // Static Class Members // --------------------------------------------------------------------------- -bool TDULC::m_verbose = false; +bool TDULC::s_verbose = false; -SiteData TDULC::m_siteData = SiteData(); +SiteData TDULC::s_siteData = SiteData(); // --------------------------------------------------------------------------- // Public Class Members @@ -85,7 +85,7 @@ TDULC::TDULC() : m_callTimer(0U), m_raw(nullptr) { - m_grpVchNo = m_siteData.channelNo(); + m_grpVchNo = s_siteData.channelNo(); } /* Finalizes a instance of TDULC class. */ @@ -186,7 +186,7 @@ bool TDULC::decode(const uint8_t* data, uint8_t* payload, bool rawTDULC) return false; } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "P25, TDULC::decode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); } @@ -219,7 +219,7 @@ void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) if (m_implicit) rs[0U] |= 0x40U; // Implicit Operation - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "P25, TDULC::encode(), TDULC Value", rs, P25_TDULC_LENGTH_BYTES); } @@ -249,7 +249,7 @@ void TDULC::encode(uint8_t* data, const uint8_t* payload, bool rawTDULC) void TDULC::copy(const TDULC& data) { - m_verbose = data.m_verbose; + s_verbose = data.s_verbose; m_protect = data.m_protect; m_lco = data.m_lco; m_mfId = data.m_mfId; @@ -272,6 +272,6 @@ void TDULC::copy(const TDULC& data) m_callTimer = data.m_callTimer; - m_siteData = data.m_siteData; + s_siteData = data.s_siteData; m_siteIdenEntry = data.m_siteIdenEntry; } diff --git a/src/common/p25/lc/TDULC.h b/src/common/p25/lc/TDULC.h index 9e1ccf4d8..217883dc6 100644 --- a/src/common/p25/lc/TDULC.h +++ b/src/common/p25/lc/TDULC.h @@ -90,19 +90,19 @@ namespace p25 * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. */ - static void setVerbose(bool verbose) { m_verbose = verbose; } + static void setVerbose(bool verbose) { s_verbose = verbose; } /** @name Local Site data */ /** * @brief Gets the local site data. * @returns SiteData Currently set site data for the TDULC class. */ - static SiteData getSiteData() { return m_siteData; } + static SiteData getSiteData() { return s_siteData; } /** * @brief Sets the local site data. * @param siteData Site data to set for the TDULC class. */ - static void setSiteData(SiteData siteData) { m_siteData = siteData; } + static void setSiteData(SiteData siteData) { s_siteData = siteData; } /** @} */ public: @@ -183,10 +183,10 @@ namespace p25 bool m_implicit; uint32_t m_callTimer; - static bool m_verbose; + static bool s_verbose; // Local Site data - static SiteData m_siteData; + static SiteData s_siteData; /** * @brief Internal helper to convert payload bytes to a 64-bit long value. diff --git a/src/common/p25/lc/TSBK.cpp b/src/common/p25/lc/TSBK.cpp index da39bc4fa..52e49cfad 100644 --- a/src/common/p25/lc/TSBK.cpp +++ b/src/common/p25/lc/TSBK.cpp @@ -25,15 +25,15 @@ using namespace p25::lc; // Static Class Members // --------------------------------------------------------------------------- -bool TSBK::m_verbose = false; +bool TSBK::s_verbose = false; #if FORCE_TSBK_CRC_WARN -bool TSBK::m_warnCRC = true; +bool TSBK::s_warnCRC = true; #else -bool TSBK::m_warnCRC = false; +bool TSBK::s_warnCRC = false; #endif -uint8_t* TSBK::m_siteCallsign = nullptr; -SiteData TSBK::m_siteData = SiteData(); +uint8_t* TSBK::s_siteCallsign = nullptr; +SiteData TSBK::s_siteData = SiteData(); // --------------------------------------------------------------------------- // Public Class Members @@ -90,9 +90,9 @@ TSBK::TSBK() : m_trellis(), m_raw(nullptr) { - if (m_siteCallsign == nullptr) { - m_siteCallsign = new uint8_t[MOT_CALLSIGN_LENGTH_BYTES]; - ::memset(m_siteCallsign, 0x00U, MOT_CALLSIGN_LENGTH_BYTES); + if (s_siteCallsign == nullptr) { + s_siteCallsign = new uint8_t[MOT_CALLSIGN_LENGTH_BYTES]; + ::memset(s_siteCallsign, 0x00U, MOT_CALLSIGN_LENGTH_BYTES); } #if FORCE_TSBK_CRC_WARN @@ -126,19 +126,19 @@ uint8_t* TSBK::getDecodedRaw() const void TSBK::setCallsign(std::string callsign) { - if (m_siteCallsign == nullptr) { - m_siteCallsign = new uint8_t[MOT_CALLSIGN_LENGTH_BYTES]; - ::memset(m_siteCallsign, 0x00U, MOT_CALLSIGN_LENGTH_BYTES); + if (s_siteCallsign == nullptr) { + s_siteCallsign = new uint8_t[MOT_CALLSIGN_LENGTH_BYTES]; + ::memset(s_siteCallsign, 0x00U, MOT_CALLSIGN_LENGTH_BYTES); } uint32_t idLength = callsign.length(); if (idLength > 0) { - ::memset(m_siteCallsign, 0x20U, MOT_CALLSIGN_LENGTH_BYTES); + ::memset(s_siteCallsign, 0x20U, MOT_CALLSIGN_LENGTH_BYTES); if (idLength > MOT_CALLSIGN_LENGTH_BYTES) idLength = MOT_CALLSIGN_LENGTH_BYTES; for (uint32_t i = 0; i < idLength; i++) - m_siteCallsign[i] = callsign[i]; + s_siteCallsign[i] = callsign[i]; } } @@ -201,7 +201,7 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) bool ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (!ret) { - if (m_warnCRC) { + if (s_warnCRC) { // if we're already warning instead of erroring CRC, don't announce invalid CRC in the // case where no CRC is defined if ((tsbk[P25_TSBK_LENGTH_BYTES - 2U] != 0x00U) && (tsbk[P25_TSBK_LENGTH_BYTES - 1U] != 0x00U)) { @@ -228,7 +228,7 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) if (ret) { ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (!ret) { - if (m_warnCRC) { + if (s_warnCRC) { LogWarning(LOG_P25, "TSBK::decode(), failed CRC CCITT-162 check"); ret = true; // ignore CRC error } @@ -247,7 +247,7 @@ bool TSBK::decode(const uint8_t* data, uint8_t* payload, bool rawTSBK) } } - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "P25, TSBK::decode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } @@ -282,7 +282,7 @@ void TSBK::encode(uint8_t* data, const uint8_t* payload, bool rawTSBK, bool noTr // compute CRC-CCITT 16 edac::CRC::addCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); - if (m_verbose) { + if (s_verbose) { Utils::dump(2U, "P25, TSBK::encode(), TSBK Value", tsbk, P25_TSBK_LENGTH_BYTES); } diff --git a/src/common/p25/lc/TSBK.h b/src/common/p25/lc/TSBK.h index 8b7453a33..b869ded78 100644 --- a/src/common/p25/lc/TSBK.h +++ b/src/common/p25/lc/TSBK.h @@ -108,17 +108,17 @@ namespace p25 * @brief Gets the flag indicating verbose log output. * @returns bool True, if the TSBK is verbose logging, otherwise false. */ - static bool getVerbose() { return m_verbose; } + static bool getVerbose() { return s_verbose; } /** * @brief Sets the flag indicating verbose log output. * @param verbose Flag indicating verbose log output. */ - static void setVerbose(bool verbose) { m_verbose = verbose; } + static void setVerbose(bool verbose) { s_verbose = verbose; } /** * @brief Sets the flag indicating CRC-errors should be warnings and not errors. * @param warnCRC Flag indicating CRC-errors should be treated as warnings. */ - static void setWarnCRC(bool warnCRC) { m_warnCRC = warnCRC; } + static void setWarnCRC(bool warnCRC) { s_warnCRC = warnCRC; } /** @name Local Site data */ /** @@ -131,12 +131,12 @@ namespace p25 * @brief Gets the local site data. * @returns SiteData Currently set site data for the TSBK class. */ - static SiteData getSiteData() { return m_siteData; } + static SiteData getSiteData() { return s_siteData; } /** * @brief Sets the local site data. * @param siteData Site data to set for the TSBK class. */ - static void setSiteData(SiteData siteData) { m_siteData = siteData; } + static void setSiteData(SiteData siteData) { s_siteData = siteData; } /** @} */ public: @@ -237,12 +237,12 @@ namespace p25 edac::RS634717 m_rs; edac::Trellis m_trellis; - static bool m_verbose; - static bool m_warnCRC; + static bool s_verbose; + static bool s_warnCRC; // Local Site data - static uint8_t* m_siteCallsign; - static SiteData m_siteData; + static uint8_t* s_siteCallsign; + static SiteData s_siteData; /** * @brief Internal helper to convert payload bytes to a 64-bit long value. diff --git a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.cpp b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.cpp index 0d59cb458..ec851d885 100644 --- a/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.cpp +++ b/src/common/p25/lc/tdulc/LC_ADJ_STS_BCAST.cpp @@ -59,10 +59,10 @@ void LC_ADJ_STS_BCAST::encode(uint8_t* data) if ((m_adjRfssId != 0U) && (m_adjSiteId != 0U) && (m_adjChannelNo != 0U)) { if (m_adjSysId == 0U) { - m_adjSysId = m_siteData.sysId(); + m_adjSysId = s_siteData.sysId(); } - rsValue = m_siteData.lra(); // Location Registration Area + rsValue = s_siteData.lra(); // Location Registration Area rsValue = (rsValue << 12) + m_adjSysId; // System ID rsValue = (rsValue << 8) + m_adjRfssId; // RF Sub-System ID rsValue = (rsValue << 8) + m_adjSiteId; // Site ID diff --git a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.cpp b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.cpp index c89242b4e..9cf025135 100644 --- a/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.cpp +++ b/src/common/p25/lc/tdulc/LC_CONV_FALLBACK.cpp @@ -47,12 +47,12 @@ void LC_CONV_FALLBACK::encode(uint8_t* data) ulong64_t rsValue = 0U; - rsValue = (rsValue << 48) + m_siteData.channelId(); // Channel ID 6 - rsValue = (rsValue << 40) + m_siteData.channelId(); // Channel ID 5 - rsValue = (rsValue << 32) + m_siteData.channelId(); // Channel ID 4 - rsValue = (rsValue << 24) + m_siteData.channelId(); // Channel ID 3 - rsValue = (rsValue << 16) + m_siteData.channelId(); // Channel ID 2 - rsValue = (rsValue << 8) + m_siteData.channelId(); // Channel ID 1 + rsValue = (rsValue << 48) + s_siteData.channelId(); // Channel ID 6 + rsValue = (rsValue << 40) + s_siteData.channelId(); // Channel ID 5 + rsValue = (rsValue << 32) + s_siteData.channelId(); // Channel ID 4 + rsValue = (rsValue << 24) + s_siteData.channelId(); // Channel ID 3 + rsValue = (rsValue << 16) + s_siteData.channelId(); // Channel ID 2 + rsValue = (rsValue << 8) + s_siteData.channelId(); // Channel ID 1 std::unique_ptr rs = TDULC::fromValue(rsValue); TDULC::encode(data, rs.get()); diff --git a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.cpp b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.cpp index aa8406628..e0fa5862f 100644 --- a/src/common/p25/lc/tdulc/LC_GROUP_UPDT.cpp +++ b/src/common/p25/lc/tdulc/LC_GROUP_UPDT.cpp @@ -49,10 +49,10 @@ void LC_GROUP_UPDT::encode(uint8_t* data) m_implicit = true; - rsValue = m_siteData.channelId(); // Group A - Channel ID + rsValue = s_siteData.channelId(); // Group A - Channel ID rsValue = (rsValue << 12) + m_grpVchNo; // Group A - Channel Number rsValue = (rsValue << 16) + m_dstId; // Group A - Talkgroup Address - rsValue = (rsValue << 4) + m_siteData.channelId(); // Group B - Channel ID + rsValue = (rsValue << 4) + s_siteData.channelId(); // Group B - Channel ID rsValue = (rsValue << 12) + m_grpVchNo; // Group B - Channel Number rsValue = (rsValue << 16) + m_dstId; // Group B - Talkgroup Address diff --git a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.cpp b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.cpp index 05214f0bd..0c782dca0 100644 --- a/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.cpp +++ b/src/common/p25/lc/tdulc/LC_NET_STS_BCAST.cpp @@ -49,11 +49,11 @@ void LC_NET_STS_BCAST::encode(uint8_t* data) m_implicit = true; - rsValue = (rsValue << 20) + m_siteData.netId(); // Network ID - rsValue = (rsValue << 12) + m_siteData.sysId(); // System ID - rsValue = (rsValue << 4) + m_siteData.channelId(); // Channel ID - rsValue = (rsValue << 12) + m_siteData.channelNo(); // Channel Number - rsValue = (rsValue << 8) + m_siteData.serviceClass(); // System Service Class + rsValue = (rsValue << 20) + s_siteData.netId(); // Network ID + rsValue = (rsValue << 12) + s_siteData.sysId(); // System ID + rsValue = (rsValue << 4) + s_siteData.channelId(); // Channel ID + rsValue = (rsValue << 12) + s_siteData.channelNo(); // Channel Number + rsValue = (rsValue << 8) + s_siteData.serviceClass(); // System Service Class std::unique_ptr rs = TDULC::fromValue(rsValue); TDULC::encode(data, rs.get()); diff --git a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.cpp b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.cpp index 449a79af7..0600fc4ab 100644 --- a/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.cpp +++ b/src/common/p25/lc/tdulc/LC_RFSS_STS_BCAST.cpp @@ -49,13 +49,13 @@ void LC_RFSS_STS_BCAST::encode(uint8_t* data) m_implicit = true; - rsValue = m_siteData.lra(); // Location Registration Area - rsValue = (rsValue << 12) + m_siteData.sysId(); // System ID - rsValue = (rsValue << 8) + m_siteData.rfssId(); // RF Sub-System ID - rsValue = (rsValue << 8) + m_siteData.siteId(); // Site ID - rsValue = (rsValue << 4) + m_siteData.channelId(); // Channel ID - rsValue = (rsValue << 12) + m_siteData.channelNo(); // Channel Number - rsValue = (rsValue << 8) + m_siteData.serviceClass(); // System Service Class + rsValue = s_siteData.lra(); // Location Registration Area + rsValue = (rsValue << 12) + s_siteData.sysId(); // System ID + rsValue = (rsValue << 8) + s_siteData.rfssId(); // RF Sub-System ID + rsValue = (rsValue << 8) + s_siteData.siteId(); // Site ID + rsValue = (rsValue << 4) + s_siteData.channelId(); // Channel ID + rsValue = (rsValue << 12) + s_siteData.channelNo(); // Channel Number + rsValue = (rsValue << 8) + s_siteData.serviceClass(); // System Service Class std::unique_ptr rs = TDULC::fromValue(rsValue); TDULC::encode(data, rs.get()); diff --git a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp index 15ea3c0c0..0ede0a2a9 100644 --- a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp +++ b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp @@ -45,7 +45,7 @@ void LC_SYS_SRV_BCAST::encode(uint8_t* data) { assert(data != nullptr); - const uint32_t services = (m_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; + const uint32_t services = (s_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; ulong64_t rsValue = 0U; diff --git a/src/common/p25/lc/tdulc/TDULCFactory.cpp b/src/common/p25/lc/tdulc/TDULCFactory.cpp index e8a050881..4dcb80822 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.cpp +++ b/src/common/p25/lc/tdulc/TDULCFactory.cpp @@ -24,7 +24,7 @@ using namespace p25::lc::tdulc; // Static Class Members // --------------------------------------------------------------------------- -::edac::RS634717 TDULCFactory::m_rs = ::edac::RS634717(); +::edac::RS634717 TDULCFactory::s_rs = ::edac::RS634717(); // --------------------------------------------------------------------------- // Public Class Members @@ -58,7 +58,7 @@ std::unique_ptr TDULCFactory::createTDULC(const uint8_t* data) // decode RS (24,12,13) FEC try { - bool ret = m_rs.decode241213(rs); + bool ret = s_rs.decode241213(rs); if (!ret) { LogError(LOG_P25, "TDULCFactory::createTDULC(), failed to decode RS (24,12,13) FEC"); return nullptr; diff --git a/src/common/p25/lc/tdulc/TDULCFactory.h b/src/common/p25/lc/tdulc/TDULCFactory.h index 632ecd175..60c33298e 100644 --- a/src/common/p25/lc/tdulc/TDULCFactory.h +++ b/src/common/p25/lc/tdulc/TDULCFactory.h @@ -69,7 +69,7 @@ namespace p25 static std::unique_ptr createTDULC(const uint8_t* data); private: - static edac::RS634717 m_rs; + static edac::RS634717 s_rs; /** * @brief Decode a TDULC. diff --git a/src/common/p25/lc/tsbk/IOSP_ACK_RSP.cpp b/src/common/p25/lc/tsbk/IOSP_ACK_RSP.cpp index 1973e6a6c..f8c78dfc6 100644 --- a/src/common/p25/lc/tsbk/IOSP_ACK_RSP.cpp +++ b/src/common/p25/lc/tsbk/IOSP_ACK_RSP.cpp @@ -63,8 +63,8 @@ void IOSP_ACK_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue |= (m_aivFlag) ? 0x80U : 0x00U; // Additional Info. Valid Flag tsbkValue |= (m_extendedAddrFlag) ? 0x40U : 0x00U; // Extended Addressing Flag if (m_aivFlag && m_extendedAddrFlag) { - tsbkValue = (tsbkValue << 20) + m_siteData.netId(); // Network ID - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 20) + s_siteData.netId(); // Network ID + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID } else { tsbkValue = (tsbkValue << 32) + m_dstId; // Target Radio Address diff --git a/src/common/p25/lc/tsbk/IOSP_GRP_VCH.cpp b/src/common/p25/lc/tsbk/IOSP_GRP_VCH.cpp index 84d9427a2..d60be652a 100644 --- a/src/common/p25/lc/tsbk/IOSP_GRP_VCH.cpp +++ b/src/common/p25/lc/tsbk/IOSP_GRP_VCH.cpp @@ -71,7 +71,7 @@ void IOSP_GRP_VCH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 4) + m_grpVchId; // Channel ID } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID } tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number tsbkValue = (tsbkValue << 16) + m_dstId; // Talkgroup Address diff --git a/src/common/p25/lc/tsbk/IOSP_UU_VCH.cpp b/src/common/p25/lc/tsbk/IOSP_UU_VCH.cpp index f91e2b6c5..ccce24a4c 100644 --- a/src/common/p25/lc/tsbk/IOSP_UU_VCH.cpp +++ b/src/common/p25/lc/tsbk/IOSP_UU_VCH.cpp @@ -70,7 +70,7 @@ void IOSP_UU_VCH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 4) + m_grpVchId; // Channel ID } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID } tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number tsbkValue = (tsbkValue << 24) + m_dstId; // Target ID diff --git a/src/common/p25/lc/tsbk/IOSP_U_REG.cpp b/src/common/p25/lc/tsbk/IOSP_U_REG.cpp index 2c25b7ee5..f0d12714a 100644 --- a/src/common/p25/lc/tsbk/IOSP_U_REG.cpp +++ b/src/common/p25/lc/tsbk/IOSP_U_REG.cpp @@ -59,7 +59,7 @@ void IOSP_U_REG::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; tsbkValue = (tsbkValue << 2) + (m_response & 0x3U); // Unit Registration Response - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID tsbkValue = (tsbkValue << 24) + m_dstId; // Source ID tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address diff --git a/src/common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp index f845e9ede..51019e288 100644 --- a/src/common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_ADJ_STS_BCAST.cpp @@ -68,9 +68,9 @@ void OSP_ADJ_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = m_siteData.lra(); // Location Registration Area + tsbkValue = s_siteData.lra(); // Location Registration Area tsbkValue = (tsbkValue << 4) + m_adjCFVA; // CFVA - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID tsbkValue = (tsbkValue << 8) + m_adjRfssId; // RF Sub-System ID tsbkValue = (tsbkValue << 8) + m_adjSiteId; // Site ID tsbkValue = (tsbkValue << 4) + m_adjChannelId; // Channel ID diff --git a/src/common/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp b/src/common/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp index 4f2c01e6b..d7a983488 100644 --- a/src/common/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp +++ b/src/common/p25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cpp @@ -61,7 +61,7 @@ void OSP_DVM_LC_CALL_TERM::encode(uint8_t* data, bool rawTSBK, bool noTrellis) m_mfId = MFG_DVM_OCS; - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number tsbkValue = (tsbkValue << 16) + m_dstId; // Talkgroup Address tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address diff --git a/src/common/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp b/src/common/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp index cdc0f34f5..c3ff75ebf 100644 --- a/src/common/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp +++ b/src/common/p25/lc/tsbk/OSP_GRP_VCH_GRANT_UPD.cpp @@ -68,7 +68,7 @@ void OSP_GRP_VCH_GRANT_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = m_grpVchId; // Channel ID (A) } else { - tsbkValue = m_siteData.channelId(); // Channel ID (Site) + tsbkValue = s_siteData.channelId(); // Channel ID (Site) } tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number (A) tsbkValue = (tsbkValue << 16) + m_dstId; // Talkgroup Address (A) @@ -77,7 +77,7 @@ void OSP_GRP_VCH_GRANT_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 4) + m_grpVchIdB; // Channel ID (B) } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID (Site) + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID (Site) } tsbkValue = (tsbkValue << 12) + m_grpVchNoB; // Channel Number (A) tsbkValue = (tsbkValue << 16) + m_dstIdB; // Talkgroup Address (B) diff --git a/src/common/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp b/src/common/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp index b7723dcf5..a4f427d33 100644 --- a/src/common/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp +++ b/src/common/p25/lc/tsbk/OSP_LOC_REG_RSP.cpp @@ -49,8 +49,8 @@ void OSP_LOC_REG_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 6) + (m_response & 0x3U); // Registration Response tsbkValue = (tsbkValue << 16) + (m_dstId & 0xFFFFU); // Talkgroup Address - tsbkValue = (tsbkValue << 8) + m_siteData.rfssId(); // RF Sub-System ID - tsbkValue = (tsbkValue << 8) + m_siteData.sysId(); // Site ID + tsbkValue = (tsbkValue << 8) + s_siteData.rfssId(); // RF Sub-System ID + tsbkValue = (tsbkValue << 8) + s_siteData.sysId(); // Site ID tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); diff --git a/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp b/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp index 585615ee7..ff0552b24 100644 --- a/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp +++ b/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp @@ -49,12 +49,12 @@ void OSP_MOT_CC_BSI::encode(uint8_t* data, bool rawTSBK, bool noTrellis) m_mfId = MFG_MOT; - tsbkValue = (m_siteCallsign[0] - 43U) & 0x3F; // Character 0 + tsbkValue = (s_siteCallsign[0] - 43U) & 0x3F; // Character 0 for (uint8_t i = 1; i < MOT_CALLSIGN_LENGTH_BYTES; i++) { - tsbkValue = (tsbkValue << 6) + ((m_siteCallsign[i] - 43U) & 0x3F); // Character 1 - 7 + tsbkValue = (tsbkValue << 6) + ((s_siteCallsign[i] - 43U) & 0x3F); // Character 1 - 7 } - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp b/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp index ef051a1c5..ca4ebbb32 100644 --- a/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp +++ b/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_GRANT.cpp @@ -53,8 +53,8 @@ void OSP_MOT_GRG_VCH_GRANT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) if (m_patchSuperGroupId != 0U) { tsbkValue = 0U; // Priority - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number tsbkValue = (tsbkValue << 16) + m_patchSuperGroupId; // Patch Supergroup Address tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address } diff --git a/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp b/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp index c3c23f1ba..bf95dac4a 100644 --- a/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp +++ b/src/common/p25/lc/tsbk/OSP_MOT_GRG_VCH_UPD.cpp @@ -51,11 +51,11 @@ void OSP_MOT_GRG_VCH_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) m_mfId = MFG_MOT; - tsbkValue = m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 4) + m_siteData.channelNo(); // Channel Number + tsbkValue = s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelNo(); // Channel Number tsbkValue = (tsbkValue << 12) + m_patchGroup1Id; // Patch Group 1 - tsbkValue = (tsbkValue << 16) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 4) + m_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 16) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelNo(); // Channel Number tsbkValue = (tsbkValue << 12) + m_patchGroup2Id; // Patch Group 2 std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); diff --git a/src/common/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp index a49b441f4..b73b0199d 100644 --- a/src/common/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_NET_STS_BCAST.cpp @@ -47,12 +47,12 @@ void OSP_NET_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = m_siteData.lra(); // Location Registration Area - tsbkValue = (tsbkValue << 20) + m_siteData.netId(); // Network ID - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number - tsbkValue = (tsbkValue << 8) + m_siteData.serviceClass(); // System Service Class + tsbkValue = s_siteData.lra(); // Location Registration Area + tsbkValue = (tsbkValue << 20) + s_siteData.netId(); // Network ID + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 8) + s_siteData.serviceClass(); // System Service Class std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/common/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp index db151e89a..975024cd8 100644 --- a/src/common/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_RFSS_STS_BCAST.cpp @@ -48,16 +48,16 @@ void OSP_RFSS_STS_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = m_siteData.lra(); // Location Registration Area + tsbkValue = s_siteData.lra(); // Location Registration Area tsbkValue = (tsbkValue << 4) + ((m_roamerReaccess) ? 0x02U : 0x00U) + // Roamer Reaccess Method - ((m_siteData.netActive()) ? 0x01U : 0x00U); // Network Active - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID - tsbkValue = (tsbkValue << 8) + m_siteData.rfssId(); // RF Sub-System ID - tsbkValue = (tsbkValue << 8) + m_siteData.siteId(); // Site ID - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number - tsbkValue = (tsbkValue << 8) + m_siteData.serviceClass(); // System Service Class + ((s_siteData.netActive()) ? 0x01U : 0x00U); // Network Active + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 8) + s_siteData.rfssId(); // RF Sub-System ID + tsbkValue = (tsbkValue << 8) + s_siteData.siteId(); // Site ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 8) + s_siteData.serviceClass(); // System Service Class std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/common/p25/lc/tsbk/OSP_SCCB.cpp b/src/common/p25/lc/tsbk/OSP_SCCB.cpp index b8f9e9ad2..c962bc3d2 100644 --- a/src/common/p25/lc/tsbk/OSP_SCCB.cpp +++ b/src/common/p25/lc/tsbk/OSP_SCCB.cpp @@ -49,18 +49,18 @@ void OSP_SCCB::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = m_siteData.rfssId(); // RF Sub-System ID - tsbkValue = (tsbkValue << 8) + m_siteData.siteId(); // Site ID + tsbkValue = s_siteData.rfssId(); // RF Sub-System ID + tsbkValue = (tsbkValue << 8) + s_siteData.siteId(); // Site ID tsbkValue = (tsbkValue << 16) + m_sccbChannelId1; // SCCB Channel ID 1 if (m_sccbChannelId1 > 0) { - tsbkValue = (tsbkValue << 8) + m_siteData.serviceClass(); // System Service Class + tsbkValue = (tsbkValue << 8) + s_siteData.serviceClass(); // System Service Class } else { tsbkValue = (tsbkValue << 8) + (ServiceClass::INVALID); // System Service Class } tsbkValue = (tsbkValue << 16) + m_sccbChannelId2; // SCCB Channel ID 2 if (m_sccbChannelId2 > 0) { - tsbkValue = (tsbkValue << 8) + m_siteData.serviceClass(); // System Service Class + tsbkValue = (tsbkValue << 8) + s_siteData.serviceClass(); // System Service Class } else { tsbkValue = (tsbkValue << 8) + (ServiceClass::INVALID); // System Service Class diff --git a/src/common/p25/lc/tsbk/OSP_SCCB_EXP.cpp b/src/common/p25/lc/tsbk/OSP_SCCB_EXP.cpp index 84e5b4604..93680c236 100644 --- a/src/common/p25/lc/tsbk/OSP_SCCB_EXP.cpp +++ b/src/common/p25/lc/tsbk/OSP_SCCB_EXP.cpp @@ -49,8 +49,8 @@ void OSP_SCCB_EXP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = m_siteData.rfssId(); // RF Sub-System ID - tsbkValue = (tsbkValue << 8) + m_siteData.siteId(); // Site ID + tsbkValue = s_siteData.rfssId(); // RF Sub-System ID + tsbkValue = (tsbkValue << 8) + s_siteData.siteId(); // Site ID tsbkValue = (tsbkValue << 4) + m_sccbChannelId1; // Channel (T) ID tsbkValue = (tsbkValue << 12) + m_sccbChannelNo; // Channel (T) Number @@ -58,7 +58,7 @@ void OSP_SCCB_EXP::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 12) + m_sccbChannelNo; // Channel (R) Number if (m_sccbChannelId1 > 0) { - tsbkValue = (tsbkValue << 8) + m_siteData.serviceClass(); // System Service Class + tsbkValue = (tsbkValue << 8) + s_siteData.serviceClass(); // System Service Class } else { tsbkValue = (tsbkValue << 8) + (ServiceClass::INVALID); // System Service Class diff --git a/src/common/p25/lc/tsbk/OSP_SCCB_EXP.h b/src/common/p25/lc/tsbk/OSP_SCCB_EXP.h index 39301ae0b..53fe20039 100644 --- a/src/common/p25/lc/tsbk/OSP_SCCB_EXP.h +++ b/src/common/p25/lc/tsbk/OSP_SCCB_EXP.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp index 630502187..9d6b18fb2 100644 --- a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp +++ b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.cpp @@ -54,7 +54,7 @@ void OSP_SNDCP_CH_ANN::encode(uint8_t* data, bool rawTSBK, bool noTrellis) uint32_t calcSpace = (uint32_t)(m_siteIdenEntry.chSpaceKhz() / 0.125); float calcTxOffset = m_siteIdenEntry.txOffsetMhz() * 1000000.0; - uint32_t txFrequency = (uint32_t)((m_siteIdenEntry.baseFrequency() + ((calcSpace * 125) * m_siteData.channelNo()))); + uint32_t txFrequency = (uint32_t)((m_siteIdenEntry.baseFrequency() + ((calcSpace * 125) * s_siteData.channelNo()))); uint32_t rxFrequency = (uint32_t)(txFrequency + (int32_t)calcTxOffset); uint32_t rootFreq = rxFrequency - m_siteIdenEntry.baseFrequency(); @@ -70,14 +70,14 @@ void OSP_SNDCP_CH_ANN::encode(uint8_t* data, bool rawTSBK, bool noTrellis) if (m_implicitChannel) { tsbkValue = (tsbkValue << 16) + 0xFFFFU; } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (T) ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel (T) Number + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel (T) ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel (T) Number } if (m_implicitChannel) { tsbkValue = (tsbkValue << 16) + 0xFFFFU; } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (R) ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel (R) ID tsbkValue = (tsbkValue << 12) + (rxChNo & 0xFFFU); // Channel (R) Number } diff --git a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h index c3a2a111c..bc3a17c4e 100644 --- a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h +++ b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_ANN.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp index fe0f13ca9..cfa81f3df 100644 --- a/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp +++ b/src/common/p25/lc/tsbk/OSP_SNDCP_CH_GNT.cpp @@ -64,14 +64,14 @@ void OSP_SNDCP_CH_GNT::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 4) + m_grpVchId; // Channel (T) ID } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (T) ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel (T) ID } tsbkValue = (tsbkValue << 12) + m_dataChannelNo; // Channel (T) Number if (m_grpVchId != 0U) { tsbkValue = (tsbkValue << 4) + m_grpVchId; // Channel (R) ID } else { - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (R) ID + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel (R) ID } tsbkValue = (tsbkValue << 12) + (rxChNo & 0xFFFU); // Channel (R) Number tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address diff --git a/src/common/p25/lc/tsbk/OSP_SYNC_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_SYNC_BCAST.cpp index c4b1b6bda..215849e36 100644 --- a/src/common/p25/lc/tsbk/OSP_SYNC_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_SYNC_BCAST.cpp @@ -64,8 +64,8 @@ void OSP_SYNC_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) // determine LTO and direction (positive or negative) bool negativeLTO = false; - uint8_t lto = fabs(m_siteData.lto()) * 2U; // this will cause a bug for half-hour timezone intervals... - if (m_siteData.lto() < 0) + uint8_t lto = fabs(s_siteData.lto()) * 2U; // this will cause a bug for half-hour timezone intervals... + if (s_siteData.lto() < 0) negativeLTO = true; // mark the LTO as valid if its non-zero diff --git a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp index 24d00b0c8..dee517395 100644 --- a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp @@ -45,7 +45,7 @@ void OSP_SYS_SRV_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) { assert(data != nullptr); - const uint32_t services = (m_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; + const uint32_t services = (s_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; ulong64_t tsbkValue = 0U; diff --git a/src/common/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp b/src/common/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp index 3f585623e..7ca726135 100644 --- a/src/common/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp +++ b/src/common/p25/lc/tsbk/OSP_TIME_DATE_ANN.cpp @@ -70,7 +70,7 @@ void OSP_TIME_DATE_ANN::encode(uint8_t* data, bool rawTSBK, bool noTrellis) LogDebug(LOG_P25, "TSBKO, OSP_TIME_DATE_ANN, tmM = %u / %u, tmY = %u / %u", local_tm.tm_mon, tmM, local_tm.tm_year, tmY); #endif - uint16_t lto = abs(m_siteData.lto()) * 2U; // this will cause a bug for half-hour timezone intervals... + uint16_t lto = abs(s_siteData.lto()) * 2U; // this will cause a bug for half-hour timezone intervals... // mark the LTO as valid if its non-zero bool vl = false; diff --git a/src/common/p25/lc/tsbk/OSP_UU_VCH_GRANT_UPD.cpp b/src/common/p25/lc/tsbk/OSP_UU_VCH_GRANT_UPD.cpp index f7b188c2c..370b56dcf 100644 --- a/src/common/p25/lc/tsbk/OSP_UU_VCH_GRANT_UPD.cpp +++ b/src/common/p25/lc/tsbk/OSP_UU_VCH_GRANT_UPD.cpp @@ -63,7 +63,7 @@ void OSP_UU_VCH_GRANT_UPD::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = m_grpVchId; // Channel ID } else { - tsbkValue = m_siteData.channelId(); // Channel ID + tsbkValue = s_siteData.channelId(); // Channel ID } tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number tsbkValue = (tsbkValue << 24) + m_dstId; // Target Address diff --git a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp index 5cd92136a..b8df17b6e 100644 --- a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp +++ b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.cpp @@ -58,8 +58,8 @@ void OSP_U_DEREG_ACK::encode(uint8_t* data, bool rawTSBK, bool noTrellis) ulong64_t tsbkValue = 0U; - tsbkValue = (tsbkValue << 8) + m_siteData.netId(); // Network ID - tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 8) + s_siteData.netId(); // Network ID + tsbkValue = (tsbkValue << 12) + s_siteData.sysId(); // System ID tsbkValue = (tsbkValue << 24) + m_dstId; // Destination Radio Address std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); diff --git a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.h b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.h index 7b8b0096a..3bdb6b872 100644 --- a/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.h +++ b/src/common/p25/lc/tsbk/OSP_U_DEREG_ACK.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/p25/lc/tsbk/OSP_U_REG_CMD.cpp b/src/common/p25/lc/tsbk/OSP_U_REG_CMD.cpp index 803f1cb8f..bdd7b27ac 100644 --- a/src/common/p25/lc/tsbk/OSP_U_REG_CMD.cpp +++ b/src/common/p25/lc/tsbk/OSP_U_REG_CMD.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/p25/lc/tsbk/OSP_U_REG_CMD.h b/src/common/p25/lc/tsbk/OSP_U_REG_CMD.h index b82f0d6ae..d474e7fc0 100644 --- a/src/common/p25/lc/tsbk/OSP_U_REG_CMD.h +++ b/src/common/p25/lc/tsbk/OSP_U_REG_CMD.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Common Library - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022 Bryan Biedenkapp, N2PLL * */ diff --git a/src/common/p25/lc/tsbk/TSBKFactory.cpp b/src/common/p25/lc/tsbk/TSBKFactory.cpp index 742d7d491..e8700473d 100644 --- a/src/common/p25/lc/tsbk/TSBKFactory.cpp +++ b/src/common/p25/lc/tsbk/TSBKFactory.cpp @@ -25,9 +25,9 @@ using namespace p25::lc::tsbk; // --------------------------------------------------------------------------- #if FORCE_TSBK_CRC_WARN -bool TSBKFactory::m_warnCRC = true; +bool TSBKFactory::s_warnCRC = true; #else -bool TSBKFactory::m_warnCRC = false; +bool TSBKFactory::s_warnCRC = false; #endif // --------------------------------------------------------------------------- @@ -58,7 +58,7 @@ std::unique_ptr TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK) bool ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (!ret) { - if (m_warnCRC) { + if (s_warnCRC) { // if we're already warning instead of erroring CRC, don't announce invalid CRC in the // case where no CRC is defined if ((tsbk[P25_TSBK_LENGTH_BYTES - 2U] != 0x00U) && (tsbk[P25_TSBK_LENGTH_BYTES - 1U] != 0x00U)) { @@ -87,7 +87,7 @@ std::unique_ptr TSBKFactory::createTSBK(const uint8_t* data, bool rawTSBK) if (ret) { ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); if (!ret) { - if (m_warnCRC) { + if (s_warnCRC) { LogWarning(LOG_P25, "TSBKFactory::createTSBK(), failed CRC CCITT-162 check"); ret = true; // ignore CRC error } diff --git a/src/common/p25/lc/tsbk/TSBKFactory.h b/src/common/p25/lc/tsbk/TSBKFactory.h index b86728d7f..a9e885852 100644 --- a/src/common/p25/lc/tsbk/TSBKFactory.h +++ b/src/common/p25/lc/tsbk/TSBKFactory.h @@ -144,10 +144,10 @@ namespace p25 * @brief Sets the flag indicating CRC-errors should be warnings and not errors. * @param warnCRC Flag indicating CRC-errors should be treated as warnings. */ - static void setWarnCRC(bool warnCRC) { m_warnCRC = warnCRC; } + static void setWarnCRC(bool warnCRC) { s_warnCRC = warnCRC; } private: - static bool m_warnCRC; + static bool s_warnCRC; /** * @brief Decode a TSBK. diff --git a/src/common/p25/lc/tsbk/mbt/MBT_IOSP_GRP_AFF.cpp b/src/common/p25/lc/tsbk/mbt/MBT_IOSP_GRP_AFF.cpp index 591b25c67..7be4cd60c 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_IOSP_GRP_AFF.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_IOSP_GRP_AFF.cpp @@ -59,13 +59,13 @@ void MBT_IOSP_GRP_AFF::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserD dataHeader.setBlocksToFollow(2U); - dataHeader.setAMBTField8((m_siteData.netId() >> 12) & 0xFFU); // Network ID (b19-12) - dataHeader.setAMBTField9((m_siteData.netId() >> 4) & 0xFFU); // Network ID (b11-b4) + dataHeader.setAMBTField8((s_siteData.netId() >> 12) & 0xFFU); // Network ID (b19-12) + dataHeader.setAMBTField9((s_siteData.netId() >> 4) & 0xFFU); // Network ID (b11-b4) /** Block 1 */ - pduUserData[0U] = ((m_siteData.netId() & 0x0FU) << 4) + // Network ID (b3-b0) - ((m_siteData.sysId() >> 8) & 0xFFU); // System ID (b11-b8) - pduUserData[1U] = (m_siteData.sysId() & 0xFFU); // System ID (b7-b0) + pduUserData[0U] = ((s_siteData.netId() & 0x0FU) << 4) + // Network ID (b3-b0) + ((s_siteData.sysId() >> 8) & 0xFFU); // System ID (b11-b8) + pduUserData[1U] = (s_siteData.sysId() & 0xFFU); // System ID (b7-b0) SET_UINT16(m_dstId, pduUserData, 2U); // Group ID SET_UINT16(m_announceGroup, pduUserData, 4U); // Announcement Group diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_ADJ_STS_BCAST.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_ADJ_STS_BCAST.cpp index 4db5bc927..45e1287c8 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_ADJ_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_ADJ_STS_BCAST.cpp @@ -55,13 +55,13 @@ void MBT_OSP_ADJ_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu if ((m_adjRfssId != 0U) && (m_adjSiteId != 0U) && (m_adjChannelNo != 0U)) { if (m_adjSysId == 0U) { - m_adjSysId = m_siteData.sysId(); + m_adjSysId = s_siteData.sysId(); } // pack LRA, CFVA and system ID into LLID - uint32_t llId = m_siteData.lra(); // Location Registration Area + uint32_t llId = s_siteData.lra(); // Location Registration Area llId = (llId << 8) + m_adjCFVA; // CFVA - llId = (llId << 4) + m_siteData.siteId(); // System ID + llId = (llId << 4) + s_siteData.siteId(); // System ID dataHeader.setLLId(llId); dataHeader.setAMBTField8(m_adjRfssId); // RF Sub-System ID @@ -75,9 +75,9 @@ void MBT_OSP_ADJ_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu ((m_adjChannelNo >> 8) & 0xFFU); pduUserData[3U] = (m_adjChannelNo >> 0) & 0xFFU; // Receive Channel Number LSB pduUserData[4U] = m_adjServiceClass; // System Service Class - pduUserData[5U] = (m_siteData.netId() >> 12) & 0xFFU; // Network ID (b19-12) - pduUserData[6U] = (m_siteData.netId() >> 4) & 0xFFU; // Network ID (b11-b4) - pduUserData[7U] = (m_siteData.netId() & 0x0FU) << 4; // Network ID (b3-b0) + pduUserData[5U] = (s_siteData.netId() >> 12) & 0xFFU; // Network ID (b19-12) + pduUserData[6U] = (s_siteData.netId() >> 4) & 0xFFU; // Network ID (b11-b4) + pduUserData[7U] = (s_siteData.netId() & 0x0FU) << 4; // Network ID (b3-b0) } else { LogError(LOG_P25, "MBT_OSP_ADJ_STS_BCAST::encodeMBT(), invalid values for OSP_ADJ_STS_BCAST, adjRfssId = $%02X, adjSiteId = $%02X, adjChannelId = %u, adjChannelNo = $%02X, adjSvcClass = $%02X", diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_AUTH_DMD.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_AUTH_DMD.cpp index eb23b1bf6..1fa9b2617 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_AUTH_DMD.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_AUTH_DMD.cpp @@ -68,13 +68,13 @@ void MBT_OSP_AUTH_DMD::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduUserD dataHeader.setBlocksToFollow(2U); - dataHeader.setAMBTField8((m_siteData.netId() >> 12) & 0xFFU); // Network ID (b19-12) - dataHeader.setAMBTField9((m_siteData.netId() >> 4) & 0xFFU); // Network ID (b11-b4) + dataHeader.setAMBTField8((s_siteData.netId() >> 12) & 0xFFU); // Network ID (b19-12) + dataHeader.setAMBTField9((s_siteData.netId() >> 4) & 0xFFU); // Network ID (b11-b4) /** Block 1 */ - pduUserData[0U] = ((m_siteData.netId() & 0x0FU) << 4) + // Network ID (b3-b0) - ((m_siteData.sysId() >> 8) & 0xFFU); // System ID (b11-b8) - pduUserData[1U] = (m_siteData.sysId() & 0xFFU); // System ID (b7-b0) + pduUserData[0U] = ((s_siteData.netId() & 0x0FU) << 4) + // Network ID (b3-b0) + ((s_siteData.sysId() >> 8) & 0xFFU); // System ID (b11-b8) + pduUserData[1U] = (s_siteData.sysId() & 0xFFU); // System ID (b7-b0) SET_UINT24(m_dstId, pduUserData, 2U); // Target Radio Address diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_GRP_VCH_GRANT.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_GRP_VCH_GRANT.cpp index 848605040..99a8fc59b 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_GRP_VCH_GRANT.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_GRP_VCH_GRANT.cpp @@ -60,7 +60,7 @@ void MBT_OSP_GRP_VCH_GRANT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu txFrequency = (txFrequency << 4) + m_grpVchId; // Tx Channel ID } else { - txFrequency = (txFrequency << 4) + m_siteData.channelId(); // Tx Channel ID + txFrequency = (txFrequency << 4) + s_siteData.channelId(); // Tx Channel ID } txFrequency = (txFrequency << 12) + m_grpVchNo; // Tx Channel Number @@ -70,7 +70,7 @@ void MBT_OSP_GRP_VCH_GRANT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu rxFrequency = (rxFrequency << 4) + m_rxGrpVchId; // Rx Channel ID } else { - rxFrequency = (rxFrequency << 4) + m_siteData.channelId(); // Rx Channel ID + rxFrequency = (rxFrequency << 4) + s_siteData.channelId(); // Rx Channel ID } rxFrequency = (rxFrequency << 12) + m_rxGrpVchNo; // Rx Channel Number diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_NET_STS_BCAST.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_NET_STS_BCAST.cpp index ae97ac7ce..aa95c9183 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_NET_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_NET_STS_BCAST.cpp @@ -46,21 +46,21 @@ void MBT_OSP_NET_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pdu assert(pduUserData != nullptr); // pack LRA and system ID into LLID - uint32_t llId = m_siteData.lra(); // Location Registration Area - llId = (llId << 12) + m_siteData.siteId(); // System ID + uint32_t llId = s_siteData.lra(); // Location Registration Area + llId = (llId << 12) + s_siteData.siteId(); // System ID dataHeader.setLLId(llId); /** Block 1 */ - pduUserData[0U] = (m_siteData.netId() >> 12) & 0xFFU; // Network ID (b19-12) - pduUserData[1U] = (m_siteData.netId() >> 4) & 0xFFU; // Network ID (b11-b4) - pduUserData[2U] = (m_siteData.netId() & 0x0FU) << 4; // Network ID (b3-b0) - pduUserData[3U] = ((m_siteData.channelId() & 0x0FU) << 4) + // Transmit Channel ID & Channel Number MSB - ((m_siteData.channelNo() >> 8) & 0xFFU); - pduUserData[4U] = (m_siteData.channelNo() >> 0) & 0xFFU; // Transmit Channel Number LSB - pduUserData[5U] = ((m_siteData.channelId() & 0x0FU) << 4) + // Receive Channel ID & Channel Number MSB - ((m_siteData.channelNo() >> 8) & 0xFFU); - pduUserData[6U] = (m_siteData.channelNo() >> 0) & 0xFFU; // Receive Channel Number LSB - pduUserData[7U] = m_siteData.serviceClass(); // System Service Class + pduUserData[0U] = (s_siteData.netId() >> 12) & 0xFFU; // Network ID (b19-12) + pduUserData[1U] = (s_siteData.netId() >> 4) & 0xFFU; // Network ID (b11-b4) + pduUserData[2U] = (s_siteData.netId() & 0x0FU) << 4; // Network ID (b3-b0) + pduUserData[3U] = ((s_siteData.channelId() & 0x0FU) << 4) + // Transmit Channel ID & Channel Number MSB + ((s_siteData.channelNo() >> 8) & 0xFFU); + pduUserData[4U] = (s_siteData.channelNo() >> 0) & 0xFFU; // Transmit Channel Number LSB + pduUserData[5U] = ((s_siteData.channelId() & 0x0FU) << 4) + // Receive Channel ID & Channel Number MSB + ((s_siteData.channelNo() >> 8) & 0xFFU); + pduUserData[6U] = (s_siteData.channelNo() >> 0) & 0xFFU; // Receive Channel Number LSB + pduUserData[7U] = s_siteData.serviceClass(); // System Service Class AMBT::encode(dataHeader, pduUserData); } diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_RFSS_STS_BCAST.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_RFSS_STS_BCAST.cpp index 9fd9e7215..7856e6c42 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_RFSS_STS_BCAST.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_RFSS_STS_BCAST.cpp @@ -46,23 +46,23 @@ void MBT_OSP_RFSS_STS_BCAST::encodeMBT(data::DataHeader& dataHeader, uint8_t* pd assert(pduUserData != nullptr); // pack LRA and system ID into LLID - uint32_t llId = m_siteData.lra(); // Location Registration Area - llId = (llId << 12) + m_siteData.siteId(); // System ID - if (m_siteData.netActive()) { + uint32_t llId = s_siteData.lra(); // Location Registration Area + llId = (llId << 12) + s_siteData.siteId(); // System ID + if (s_siteData.netActive()) { llId |= 0x1000U; // Network Active Flag } dataHeader.setLLId(llId); /** Block 1 */ - pduUserData[0U] = (m_siteData.rfssId()) & 0xFFU; // RF Sub-System ID - pduUserData[1U] = (m_siteData.siteId()) & 0xFFU; // Site ID - pduUserData[2U] = ((m_siteData.channelId() & 0x0FU) << 4) + // Transmit Channel ID & Channel Number MSB - ((m_siteData.channelNo() >> 8) & 0xFFU); - pduUserData[3U] = (m_siteData.channelNo() >> 0) & 0xFFU; // Transmit Channel Number LSB - pduUserData[4U] = ((m_siteData.channelId() & 0x0FU) << 4) + // Receive Channel ID & Channel Number MSB - ((m_siteData.channelNo() >> 8) & 0xFFU); - pduUserData[5U] = (m_siteData.channelNo() >> 0) & 0xFFU; // Receive Channel Number LSB - pduUserData[6U] = m_siteData.serviceClass(); // System Service Class + pduUserData[0U] = (s_siteData.rfssId()) & 0xFFU; // RF Sub-System ID + pduUserData[1U] = (s_siteData.siteId()) & 0xFFU; // Site ID + pduUserData[2U] = ((s_siteData.channelId() & 0x0FU) << 4) + // Transmit Channel ID & Channel Number MSB + ((s_siteData.channelNo() >> 8) & 0xFFU); + pduUserData[3U] = (s_siteData.channelNo() >> 0) & 0xFFU; // Transmit Channel Number LSB + pduUserData[4U] = ((s_siteData.channelId() & 0x0FU) << 4) + // Receive Channel ID & Channel Number MSB + ((s_siteData.channelNo() >> 8) & 0xFFU); + pduUserData[5U] = (s_siteData.channelNo() >> 0) & 0xFFU; // Receive Channel Number LSB + pduUserData[6U] = s_siteData.serviceClass(); // System Service Class AMBT::encode(dataHeader, pduUserData); } diff --git a/src/common/p25/lc/tsbk/mbt/MBT_OSP_UU_VCH_GRANT.cpp b/src/common/p25/lc/tsbk/mbt/MBT_OSP_UU_VCH_GRANT.cpp index 8c6278c85..0e7c06369 100644 --- a/src/common/p25/lc/tsbk/mbt/MBT_OSP_UU_VCH_GRANT.cpp +++ b/src/common/p25/lc/tsbk/mbt/MBT_OSP_UU_VCH_GRANT.cpp @@ -56,14 +56,14 @@ void MBT_OSP_UU_VCH_GRANT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduU (m_priority & 0x07U); // Priority dataHeader.setAMBTField8(serviceOptions); - dataHeader.setAMBTField9((m_siteData.netId() >> 12) & 0xFFU); // Target Network ID (b19-12) + dataHeader.setAMBTField9((s_siteData.netId() >> 12) & 0xFFU); // Target Network ID (b19-12) uint16_t txFrequency = 0U; if ((m_grpVchId != 0U) || m_forceChannelId) { txFrequency = (txFrequency << 4) + m_grpVchId; // Tx Channel ID } else { - txFrequency = (txFrequency << 4) + m_siteData.channelId(); // Tx Channel ID + txFrequency = (txFrequency << 4) + s_siteData.channelId(); // Tx Channel ID } txFrequency = (txFrequency << 12) + m_grpVchNo; // Tx Channel Number @@ -73,26 +73,26 @@ void MBT_OSP_UU_VCH_GRANT::encodeMBT(data::DataHeader& dataHeader, uint8_t* pduU rxFrequency = (rxFrequency << 4) + m_rxGrpVchId; // Rx Channel ID } else { - rxFrequency = (rxFrequency << 4) + m_siteData.channelId(); // Rx Channel ID + rxFrequency = (rxFrequency << 4) + s_siteData.channelId(); // Rx Channel ID } rxFrequency = (rxFrequency << 12) + m_rxGrpVchNo; // Rx Channel Number /** Block 1 */ - pduUserData[0U] = ((m_siteData.netId() >> 12) & 0xFFU); // Source Network ID (b19-12) - pduUserData[1U] = ((m_siteData.netId() >> 4) & 0xFFU); // Source Network ID (b11-b4) - pduUserData[2U] = ((m_siteData.netId() & 0x0FU) << 4) + // Source Network ID (b3-b0) - ((m_siteData.sysId() >> 8) & 0xFFU); // Source System ID (b11-b8) - pduUserData[3U] = (m_siteData.sysId() & 0xFFU); // Source System ID (b7-b0) + pduUserData[0U] = ((s_siteData.netId() >> 12) & 0xFFU); // Source Network ID (b19-12) + pduUserData[1U] = ((s_siteData.netId() >> 4) & 0xFFU); // Source Network ID (b11-b4) + pduUserData[2U] = ((s_siteData.netId() & 0x0FU) << 4) + // Source Network ID (b3-b0) + ((s_siteData.sysId() >> 8) & 0xFFU); // Source System ID (b11-b8) + pduUserData[3U] = (s_siteData.sysId() & 0xFFU); // Source System ID (b7-b0) SET_UINT24(m_srcId, pduUserData, 4U); // Source Radio Address SET_UINT24(m_dstId, pduUserData, 7U); // Target Radio Address SET_UINT16(txFrequency, pduUserData, 10U); // Transmit Frequency /** Block 2 */ SET_UINT16(rxFrequency, pduUserData, 12U); // Receive Frequency - pduUserData[14U] = ((m_siteData.netId() >> 4) & 0xFFU); // Target Network ID (b11-b4) - pduUserData[15U] = ((m_siteData.netId() & 0x0FU) << 4) + // Target Network ID (b3-b0) - ((m_siteData.sysId() >> 8) & 0xFFU); // Target System ID (b11-b8) - pduUserData[16U] = (m_siteData.sysId() & 0xFFU); // Target System ID (b7-b0) + pduUserData[14U] = ((s_siteData.netId() >> 4) & 0xFFU); // Target Network ID (b11-b4) + pduUserData[15U] = ((s_siteData.netId() & 0x0FU) << 4) + // Target Network ID (b3-b0) + ((s_siteData.sysId() >> 8) & 0xFFU); // Target System ID (b11-b8) + pduUserData[16U] = (s_siteData.sysId() & 0xFFU); // Target System ID (b7-b0) SET_UINT24(m_dstId, pduUserData, 17U); // Target Radio Address AMBT::encode(dataHeader, pduUserData); diff --git a/src/common/restapi/RequestDispatcher.h b/src/common/restapi/RequestDispatcher.h new file mode 100644 index 000000000..1470be322 --- /dev/null +++ b/src/common/restapi/RequestDispatcher.h @@ -0,0 +1,331 @@ +// 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. + * + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup rest REST Services + * @brief Implementation for REST services. + * @defgroup http Embedded HTTP Core + * @brief Implementation for basic HTTP services. + * @ingroup rest + * + * @file RequestDispatcher.h + * @ingroup rest + */ +#if !defined(__REST__DISPATCHER_H__) +#define __REST__DISPATCHER_H__ + +#include "common/Defines.h" +#include "common/restapi/http/HTTPPayload.h" +#include "common/Log.h" + +#include +#include +#include +#include +#include + +namespace restapi +{ + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Structure representing a REST API request match. + * @ingroup rest + */ + struct RequestMatch : std::smatch { + /** + * @brief Initializes a new instance of the RequestMatch structure. + * @param m String matcher. + * @param c Content. + */ + RequestMatch(const std::smatch& m, const std::string& c) : std::smatch(m), content(c) { /* stub */ } + + std::string content; + }; + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Structure representing a request matcher. + * @ingroup rest + */ + template + struct RequestMatcher { + typedef std::function RequestHandlerType; + + /** + * @brief Initializes a new instance of the RequestMatcher structure. + * @param expression Matching expression. + */ + explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ } + + /** + * @brief Handler for GET requests. + * @param handler GET request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& get(RequestHandlerType handler) { + m_handlers[HTTP_GET] = handler; + return *this; + } + /** + * @brief Handler for POST requests. + * @param handler POST request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& post(RequestHandlerType handler) { + m_handlers[HTTP_POST] = handler; + return *this; + } + /** + * @brief Handler for PUT requests. + * @param handler PUT request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& put(RequestHandlerType handler) { + m_handlers[HTTP_PUT] = handler; + return *this; + } + /** + * @brief Handler for DELETE requests. + * @param handler DELETE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& del(RequestHandlerType handler) { + m_handlers[HTTP_DELETE] = handler; + return *this; + } + /** + * @brief Handler for OPTIONS requests. + * @param handler OPTIONS request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& options(RequestHandlerType handler) { + m_handlers[HTTP_OPTIONS] = handler; + return *this; + } + + /** + * @brief Helper to determine if the request matcher is a regular expression. + * @returns bool True, if request matcher is a regular expression, otherwise false. + */ + bool regex() const { return m_isRegEx; } + /** + * @brief Helper to set the regular expression flag. + * @param regEx Flag indicating whether or not the request matcher is a regular expression. + */ + void setRegEx(bool regEx) { m_isRegEx = regEx; } + + /** + * @brief Helper to handle the actual request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param what What matched. + */ + void handleRequest(const Request& request, Reply& reply, const std::smatch &what) { + // dispatching to matching based on handler + RequestMatch match(what, request.content); + auto& handler = m_handlers[request.method]; + if (handler) { + handler(request, reply, match); + } + } + + private: + std::string m_expression; + bool m_isRegEx; + std::map m_handlers; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements RESTful web request dispatching. + * @tparam Request HTTP request. + * @tparam Reply HTTP reply. + */ + template + class RequestDispatcher { + typedef RequestMatcher MatcherType; + public: + /** + * @brief Initializes a new instance of the RequestDispatcher class. + */ + RequestDispatcher() : m_basePath(), m_debug(false) { /* stub */ } + /** + * @brief Initializes a new instance of the RequestDispatcher class. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + RequestDispatcher(bool debug) : m_basePath(), m_debug(debug) { /* stub */ } + /** + * @brief Initializes a new instance of the RequestDispatcher class. + * @param basePath + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + RequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ } + + /** + * @brief Helper to match a request patch. + * @param expression Matching expression. + * @param regex Flag indicating whether or not this match is a regular expression. + * @returns MatcherType Instance of a request matcher. + */ + MatcherType& match(const std::string& expression, bool regex = false) + { + MatcherTypePtr& p = m_matchers[expression]; + if (!p) { + if (m_debug) { + ::LogDebug(LOG_REST, "creating RequestDispatcher, expression = %s", expression.c_str()); + } + p = std::make_shared(expression); + } else { + if (m_debug) { + ::LogDebug(LOG_REST, "fetching RequestDispatcher, expression = %s", expression.c_str()); + } + } + + p->setRegEx(regex); + return *p; + } + + /** + * @brief Helper to handle HTTP request. + * @param request HTTP request. + * @param reply HTTP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + for (const auto& matcher : m_matchers) { + std::smatch what; + if (!matcher.second->regex()) { + if (request.uri.find(matcher.first) != std::string::npos) { + if (m_debug) { + ::LogDebug(LOG_REST, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); + } + + //what = matcher.first; + + // ensure CORS headers are added + reply.headers.add("Access-Control-Allow-Origin", "*"); + reply.headers.add("Access-Control-Allow-Methods", "*"); + reply.headers.add("Access-Control-Allow-Headers", "*"); + + if (request.method == HTTP_OPTIONS) { + reply.status = http::HTTPPayload::OK; + } + + matcher.second->handleRequest(request, reply, what); + return; + } + } else { + if (std::regex_match(request.uri, what, std::regex(matcher.first))) { + if (m_debug) { + ::LogDebug(LOG_REST, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); + } + + matcher.second->handleRequest(request, reply, what); + return; + } + } + } + + ::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str()); + reply = http::HTTPPayload::statusPayload(http::HTTPPayload::BAD_REQUEST, "application/json"); + } + + private: + typedef std::shared_ptr MatcherTypePtr; + + std::string m_basePath; + std::map m_matchers; + + bool m_debug; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements a generic basic request dispatcher. + * @tparam Request HTTP request. + * @tparam Reply HTTP reply. + */ + template + class BasicRequestDispatcher { + public: + typedef std::function RequestHandlerType; + + /** + * @brief Initializes a new instance of the BasicRequestDispatcher class. + */ + BasicRequestDispatcher() { /* stub */ } + /** + * @brief Initializes a new instance of the BasicRequestDispatcher class. + * @param handler Instance of a RequestHandlerType for this dispatcher. + */ + BasicRequestDispatcher(RequestHandlerType handler) : m_handler(handler) { /* stub */ } + + /** + * @brief Helper to handle HTTP request. + * @param request HTTP request. + * @param reply HTTP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + if (m_handler) { + m_handler(request, reply); + } + } + + private: + RequestHandlerType m_handler; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements a generic debug request dispatcher. + * @tparam Request HTTP request. + * @tparam Reply HTTP reply. + */ + template + class DebugRequestDispatcher { + public: + /** + * @brief Initializes a new instance of the DebugRequestDispatcher class. + */ + DebugRequestDispatcher() { /* stub */ } + + /** + * @brief Helper to handle HTTP request. + * @param request HTTP request. + * @param reply HTTP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + for (auto header : request.headers.headers()) + ::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); + + ::LogDebugEx(LOG_REST, "DebugRequestDispatcher::handleRequest()", "content = %s", request.content.c_str()); + } + }; + + typedef RequestDispatcher DefaultRequestDispatcher; +} // namespace restapi + +#endif // __REST__DISPATCHER_H__ diff --git a/src/common/restapi/http/ClientConnection.h b/src/common/restapi/http/ClientConnection.h new file mode 100644 index 000000000..0b370cdf4 --- /dev/null +++ b/src/common/restapi/http/ClientConnection.h @@ -0,0 +1,239 @@ +// 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. + * + * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file ClientConnection.h + * @ingroup http + */ +#if !defined(__REST_HTTP__CLIENT_CONNECTION_H__) +#define __REST_HTTP__CLIENT_CONNECTION_H__ + +#include "common/Defines.h" +#include "common/restapi/http/HTTPLexer.h" +#include "common/restapi/http/HTTPPayload.h" +#include "common/Log.h" + +#include +#include +#include +#include + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class represents a single connection from a client. + * @tparam RequestHandlerType Type representing a request handler. + * @ingroup http + */ + template + class ClientConnection { + public: + auto operator=(ClientConnection&) -> ClientConnection& = delete; + auto operator=(ClientConnection&&) -> ClientConnection& = delete; + ClientConnection(ClientConnection&) = delete; + + /** + * @brief Initializes a new instance of the ClientConnection class. + * @param socket TCP socket for this connection. + * @param handler Request handler for this connection. + */ + explicit ClientConnection(asio::ip::tcp::socket socket, RequestHandlerType& handler) : + m_socket(std::move(socket)), + m_requestHandler(handler), + m_lexer(HTTPLexer(true)) + { + /* stub */ + } + + /** + * @brief Start the first asynchronous operation for the connection. + */ + void start() { read(); } + /** + * @brief Stop all asynchronous operations associated with the connection. + */ + void stop() + { + try + { + ensureNoLinger(); + if (m_socket.is_open()) { + m_socket.close(); + } + } + catch(const std::exception&) { /* ignore */ } + } + + /** + * @brief Helper to enable the SO_LINGER socket option during shutdown. + */ + void ensureNoLinger() + { + try + { + // enable SO_LINGER timeout 0 + asio::socket_base::linger linger(true, 0); + m_socket.set_option(linger); + } + catch(const asio::system_error& e) + { + asio::error_code ec = e.code(); + if (ec) { + ::LogError(LOG_REST, "ClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value()); + } + } + } + + /** + * @brief Perform an synchronous write operation. + * @param request HTTP request payload. + */ + void send(HTTPPayload request) + { + m_sizeToTransfer = m_bytesTransferred = 0U; + request.attachHostHeader(m_socket.remote_endpoint()); + write(request); + } + private: + /** + * @brief Perform an asynchronous read operation. + */ + void read() + { + m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { + if (!ec) { + HTTPLexer::ResultType result; + char* content; + + try + { + if (m_sizeToTransfer > 0U && (m_bytesTransferred + bytes_transferred) < m_sizeToTransfer) { + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + read(); + } + else { + if (m_sizeToTransfer > 0U) { + // final copy + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + m_sizeToTransfer = 0U; + bytes_transferred = m_bytesTransferred; + + // reset lexer and re-parse the full content + m_lexer.reset(); + std::tie(result, content) = m_lexer.parse(m_request, m_fullBuffer.data(), m_fullBuffer.data() + bytes_transferred); + } else { + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); + } + + // determine content length + std::string contentLength = m_request.headers.find("Content-Length"); + if (contentLength != "") { + size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); + + // setup a full read if necessary + if (length > bytes_transferred && m_sizeToTransfer == 0U) { + m_sizeToTransfer = length; + } + + if (m_sizeToTransfer > 0U) { + result = HTTPLexer::CONTINUE; + } else { + m_request.content = std::string(content, length); + } + } + + m_request.headers.add("RemoteHost", m_socket.remote_endpoint().address().to_string()); + + if (result == HTTPLexer::GOOD) { + m_sizeToTransfer = m_bytesTransferred = 0U; + m_requestHandler.handleRequest(m_request, m_reply); + } + else if (result == HTTPLexer::BAD) { + m_sizeToTransfer = m_bytesTransferred = 0U; + return; + } + else { + read(); + } + } + } + catch(const std::exception& e) { ::LogError(LOG_REST, "ClientConnection::read(), %s", ec.message().c_str()); } + } + else if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "ClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); + } + stop(); + } + }); + } + + /** + * @brief Perform an synchronous write operation. + * @param request HTTP request payload. + */ + void write(HTTPPayload request) + { + try + { + auto buffers = request.toBuffers(); + asio::write(m_socket, buffers); + } + catch(const asio::system_error& e) + { + asio::error_code ec = e.code(); + if (ec) { + ::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + + try + { + // initiate graceful connection closure + asio::error_code ignored_ec; + m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + catch(const std::exception& e) { + ::LogError(LOG_REST, "ClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + } + } + } + } + + asio::ip::tcp::socket m_socket; + + RequestHandlerType& m_requestHandler; + + std::size_t m_sizeToTransfer; + std::size_t m_bytesTransferred; + std::array m_fullBuffer; + + std::array m_buffer; + + HTTPPayload m_request; + HTTPLexer m_lexer; + HTTPPayload m_reply; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__CLIENT_CONNECTION_H__ diff --git a/src/common/restapi/http/HTTPClient.h b/src/common/restapi/http/HTTPClient.h new file mode 100644 index 000000000..7f2f76e82 --- /dev/null +++ b/src/common/restapi/http/HTTPClient.h @@ -0,0 +1,196 @@ +// 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. + * + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HTTPClient.h + * @ingroup http + */ +#if !defined(__REST_HTTP__HTTP_CLIENT_H__) +#define __REST_HTTP__HTTP_CLIENT_H__ + +#include "common/Defines.h" +#include "common/restapi/http/ClientConnection.h" +#include "common/restapi/http/HTTPRequestHandler.h" +#include "common/Thread.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements top-level routines of the HTTP client. + * @tparam RequestHandlerType Type representing a request handler. + * @tparam ConnectionImpl Type representing the connection implementation. + * @ingroup http + */ + template class ConnectionImpl = ClientConnection> + class HTTPClient : private Thread { + public: + auto operator=(HTTPClient&) -> HTTPClient& = delete; + auto operator=(HTTPClient&&) -> HTTPClient& = delete; + HTTPClient(HTTPClient&) = delete; + + /** + * @brief Initializes a new instance of the HTTPClient class. + * @param address Hostname/IP Address. + * @param port Port. + */ + HTTPClient(const std::string& address, uint16_t port) : + m_address(address), + m_port(port), + m_connection(nullptr), + m_ioContext(), + m_socket(m_ioContext), + m_requestHandler() + { + /* stub */ + } + /** + * @brief Finalizes a instance of the HTTPClient class. + */ + ~HTTPClient() override + { + if (m_connection != nullptr) { + close(); + } + } + + /** + * @brief Helper to set the HTTP request handlers. + * @tparam Handler Type representing the request handler. + * @param handler Request handler. + */ + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /** + * @brief Send HTTP request to HTTP server. + * @param request HTTP request. + * @returns True, if request was completed, otherwise false. + */ + bool request(HTTPPayload& request) + { + if (m_completed) { + return false; + } + + asio::post(m_ioContext, [this, request]() { + std::lock_guard guard(m_lock); + { + if (m_connection != nullptr) { + m_connection->send(request); + } + } + }); + + return true; + } + + /** + * @brief Opens connection to the network. + */ + bool open() + { + if (m_completed) { + return false; + } + + return run(); + } + + /** + * @brief Closes connection to the network. + */ + void close() + { + if (m_completed) { + return; + } + + m_completed = true; + m_ioContext.stop(); + + wait(); + } + + private: + /** + * @brief Internal entry point for the ASIO IO context thread. + */ + void entry() override + { + if (m_completed) { + return; + } + + asio::ip::tcp::resolver resolver(m_ioContext); + auto endpoints = resolver.resolve(m_address, std::to_string(m_port)); + + try { + connect(endpoints); + + // the entry() call will block until all asynchronous operations + // have finished + m_ioContext.run(); + } + catch (std::exception&) { /* stub */ } + + if (m_connection != nullptr) { + m_connection->stop(); + } + } + + /** + * @brief Perform an asynchronous connect operation. + * @param endpoints TCP endpoint to connect to. + */ + void connect(asio::ip::basic_resolver_results& endpoints) + { + asio::connect(m_socket, endpoints); + + m_connection = std::make_unique(std::move(m_socket), m_requestHandler); + m_connection->start(); + } + + std::string m_address; + uint16_t m_port; + + typedef ConnectionImpl ConnectionType; + + std::unique_ptr m_connection; + + bool m_completed = false; + asio::io_context m_ioContext; + + asio::ip::tcp::socket m_socket; + + RequestHandlerType m_requestHandler; + + std::mutex m_lock; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__HTTP_CLIENT_H__ diff --git a/src/common/network/sip/SIPHeaders.h b/src/common/restapi/http/HTTPHeaders.h similarity index 75% rename from src/common/network/sip/SIPHeaders.h rename to src/common/restapi/http/HTTPHeaders.h index df1b43df6..7dc6daf00 100644 --- a/src/common/network/sip/SIPHeaders.h +++ b/src/common/restapi/http/HTTPHeaders.h @@ -5,15 +5,15 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL * */ /** - * @file SIPClient.h - * @ingroup sip + * @file HTTPClient.h + * @ingroup http */ -#if !defined(__SIP__SIP_HEADERS_H__) -#define __SIP__SIP_HEADERS_H__ +#if !defined(__REST_HTTP__HTTP_HEADERS_H__) +#define __REST_HTTP__HTTP_HEADERS_H__ #include "common/Defines.h" #include "common/Log.h" @@ -22,28 +22,28 @@ #include #include -namespace network +namespace restapi { - namespace sip + namespace http { // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- - struct SIPPayload; + struct HTTPPayload; // --------------------------------------------------------------------------- // Structure Declaration // --------------------------------------------------------------------------- /** - * @brief Represents SIP headers. - * @ingroup sip + * @brief Represents HTTP headers. + * @ingroup http */ - struct SIPHeaders { + struct HTTPHeaders { /** - * @brief Structure representing an individual SIP header. - * @ingroup sip + * @brief Structure representing an individual HTTP header. + * @ingroup http */ struct Header { @@ -69,13 +69,13 @@ namespace network }; /** - * @brief Gets the list of SIP headers. - * @returns std::vector
List of SIP headers. + * @brief Gets the list of HTTP headers. + * @returns std::vector
List of HTTP headers. */ std::vector
headers() const { return m_headers; } /** * @brief Returns true if the headers are empty. - * @returns bool True, if no SIP headers are present, otherwise false. + * @returns bool True, if no HTTP headers are present, otherwise false. */ bool empty() const { return m_headers.empty(); } /** @@ -84,17 +84,17 @@ namespace network */ std::size_t size() const { return m_headers.size(); } /** - * @brief Clears the list of SIP headers. + * @brief Clears the list of HTTP headers. */ void clearHeaders() { m_headers = std::vector
(); } /** - * @brief Helper to add a SIP header. + * @brief Helper to add a HTTP header. * @param name Header name. * @param value Header value. */ void add(const std::string& name, const std::string& value) { - //::LogDebugEx(LOG_SIP, "SIPHeaders::add()", "header = %s, value = %s", name.c_str(), value.c_str()); + //::LogDebugEx(LOG_REST, "HTTPHeaders::add()", "header = %s, value = %s", name.c_str(), value.c_str()); for (auto& header : m_headers) { if (::strtolower(header.name) == ::strtolower(name)) { header.value = value; @@ -104,10 +104,10 @@ namespace network m_headers.push_back(Header(name, value)); //for (auto header : m_headers) - // ::LogDebugEx(LOG_SIP, "SIPHeaders::add()", "m_headers.header = %s, m_headers.value = %s", header.name.c_str(), header.value.c_str()); + // ::LogDebugEx(LOG_REST, "HTTPHeaders::add()", "m_headers.header = %s, m_headers.value = %s", header.name.c_str(), header.value.c_str()); } /** - * @brief Helper to remove a SIP header. + * @brief Helper to remove a HTTP header. * @param headerName Header name. */ void remove(const std::string headerName) @@ -121,7 +121,7 @@ namespace network } } /** - * @brief Helper to find the named SIP header. + * @brief Helper to find the named HTTP header. * @param headerName Header name. * @returns std::string Value of named header (if any). */ @@ -140,10 +140,10 @@ namespace network } private: - friend struct SIPPayload; + friend struct HTTPPayload; std::vector
m_headers; }; - } // namespace sip -} // namespace network + } // namespace http +} // namespace restapi -#endif // __SIP__SIP_HEADERS_H__ +#endif // __REST_HTTP__HTTP_HEADERS_H__ diff --git a/src/common/network/rest/http/HTTPLexer.cpp b/src/common/restapi/http/HTTPLexer.cpp similarity index 98% rename from src/common/network/rest/http/HTTPLexer.cpp rename to src/common/restapi/http/HTTPLexer.cpp index 668d1820f..63594410e 100644 --- a/src/common/network/rest/http/HTTPLexer.cpp +++ b/src/common/restapi/http/HTTPLexer.cpp @@ -9,11 +9,11 @@ * */ #include "Defines.h" -#include "network/rest/http/HTTPLexer.h" -#include "network/rest/http/HTTPPayload.h" +#include "restapi/http/HTTPLexer.h" +#include "restapi/http/HTTPPayload.h" #include "Log.h" -using namespace network::rest::http; +using namespace restapi::http; #include diff --git a/src/common/network/sip/SIPLexer.h b/src/common/restapi/http/HTTPLexer.h similarity index 58% rename from src/common/network/sip/SIPLexer.h rename to src/common/restapi/http/HTTPLexer.h index ec897ebb5..8f62d63cf 100644 --- a/src/common/network/sip/SIPLexer.h +++ b/src/common/restapi/http/HTTPLexer.h @@ -5,32 +5,32 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright (c) 2003-2013 Christopher M. Kohlhoff - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL * */ /** - * @file SIPLexer.h - * @ingroup sip - * @file SIPLexer.cpp - * @ingroup sip + * @file HTTPLexer.h + * @ingroup http + * @file HTTPLexer.cpp + * @ingroup http */ -#if !defined(__SIP__SIP_LEXER_H__) -#define __SIP__SIP_LEXER_H__ +#if !defined(__REST_HTTP__HTTP_LEXER_H__) +#define __REST_HTTP__HTTP_LEXER_H__ #include "common/Defines.h" #include #include -namespace network +namespace restapi { - namespace sip + namespace http { // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- - struct SIPPayload; + struct HTTPPayload; // --------------------------------------------------------------------------- // Class Declaration @@ -39,7 +39,7 @@ namespace network /** * @brief This class implements the lexer for incoming payloads. */ - class SIPLexer { + class HTTPLexer { public: /** * @brief Lexing result. @@ -47,10 +47,10 @@ namespace network enum ResultType { GOOD, BAD, INDETERMINATE, CONTINUE }; /** - * @brief Initializes a new instance of the SIPLexer class. - * @param clientLexer Flag indicating this lexer is used for a SIP client. + * @brief Initializes a new instance of the HTTPLexer class. + * @param clientLexer Flag indicating this lexer is used for a HTTP client. */ - SIPLexer(bool clientLexer); + HTTPLexer(bool clientLexer); /** * @brief Reset to initial parser state. @@ -63,13 +63,13 @@ namespace network * required. The InputIterator return value indicates how much of the input * has been consumed. * @tparam InputIterator - * @param payload SIP request payload. + * @param payload HTTP request payload. * @param begin * @param end * @returns std::tuple */ template - std::tuple parse(SIPPayload& payload, InputIterator begin, InputIterator end) + std::tuple parse(HTTPPayload& payload, InputIterator begin, InputIterator end) { while (begin != end) { ResultType result = consume(payload, *begin++); @@ -88,27 +88,27 @@ namespace network private: /** * @brief Handle the next character of input. - * @param payload SIP request payload. + * @param payload HTTP request payload. * @param input Character. */ - ResultType consume(SIPPayload& payload, char input); + ResultType consume(HTTPPayload& payload, char input); /** - * @brief Check if a byte is an SIP character. + * @brief Check if a byte is an HTTP character. * @param c Character. - * @returns bool True, if character is an SIP character, otherwise false. + * @returns bool True, if character is an HTTP character, otherwise false. */ static bool isChar(int c); /** - * @brief Check if a byte is an SIP control character. + * @brief Check if a byte is an HTTP control character. * @param c Character. - * @returns bool True, if character is an SIP control character, otherwise false. + * @returns bool True, if character is an HTTP control character, otherwise false. */ static bool isControl(int c); /** - * @brief Check if a byte is an SIP special character. + * @brief Check if a byte is an HTTP special character. * @param c Character. - * @returns bool True, if character is an SIP special character, otherwise false. + * @returns bool True, if character is an HTTP special character, otherwise false. */ static bool isSpecial(int c); /** @@ -119,7 +119,7 @@ namespace network static bool isDigit(int c); /** - * @brief Structure representing lexed SIP headers. + * @brief Structure representing lexed HTTP headers. */ struct LexedHeader { @@ -154,39 +154,40 @@ namespace network */ enum state { - METHOD_START, //! SIP Method Start - METHOD, //! SIP Method - URI, //! SIP URI - - SIP_VERSION_S, //! SIP Version: S - SIP_VERSION_I, //! SIP Version: I - SIP_VERSION_P, //! SIP Version: P - SIP_VERSION_SLASH, //! SIP Version: / - SIP_VERSION_MAJOR_START, //! SIP Version Major Start - SIP_VERSION_MAJOR, //! SIP Version Major - SIP_VERSION_MINOR_START, //! SIP Version Minor Start - SIP_VERSION_MINOR, //! SIP Version Minor - - SIP_STATUS_1, //! Status Number 1 - SIP_STATUS_2, //! Status Number 2 - SIP_STATUS_3, //! Status Number 3 - SIP_STATUS_END, //! Status End - SIP_STATUS_MESSAGE_START, //! Status Message Start - SIP_STATUS_MESSAGE, //! Status Message End - - EXPECTING_NEWLINE_1, //! - - HEADER_LINE_START, //! Header Line Start - HEADER_LWS, //! - HEADER_NAME, //! Header Name - SPACE_BEFORE_HEADER_VALUE, //! - HEADER_VALUE, //! Header Value - - EXPECTING_NEWLINE_2, //! - EXPECTING_NEWLINE_3 //! + METHOD_START, //!< HTTP Method Start + METHOD, //!< HTTP Method + URI, //!< HTTP URI + + HTTP_VERSION_H, //!< HTTP Version: H + HTTP_VERSION_T_1, //!< HTTP Version: T + HTTP_VERSION_T_2, //!< HTTP Version: T + HTTP_VERSION_P, //!< HTTP Version: P + HTTP_VERSION_SLASH, //!< HTTP Version: / + HTTP_VERSION_MAJOR_START, //!< HTTP Version Major Start + HTTP_VERSION_MAJOR, //!< HTTP Version Major + HTTP_VERSION_MINOR_START, //!< HTTP Version Minor Start + HTTP_VERSION_MINOR, //!< HTTP Version Minor + + HTTP_STATUS_1, //!< Status Number 1 + HTTP_STATUS_2, //!< Status Number 2 + HTTP_STATUS_3, //!< Status Number 3 + HTTP_STATUS_END, //!< Status End + HTTP_STATUS_MESSAGE_START, //!< Status Message Start + HTTP_STATUS_MESSAGE, //!< Status Message End + + EXPECTING_NEWLINE_1, //!< + + HEADER_LINE_START, //!< Header Line Start + HEADER_LWS, //!< + HEADER_NAME, //!< Header Name + SPACE_BEFORE_HEADER_VALUE, //!< + HEADER_VALUE, //!< Header Value + + EXPECTING_NEWLINE_2, //!< + EXPECTING_NEWLINE_3 //!< } m_state; }; - } // namespace sip -} // namespace network + } // namespace http +} // namespace restapi -#endif // __SIP__SIP_LEXER_H__ +#endif // __REST_HTTP__HTTP_LEXER_H__ diff --git a/src/common/network/rest/http/HTTPPayload.cpp b/src/common/restapi/http/HTTPPayload.cpp similarity index 99% rename from src/common/network/rest/http/HTTPPayload.cpp rename to src/common/restapi/http/HTTPPayload.cpp index 088db94b2..5107a601f 100644 --- a/src/common/network/rest/http/HTTPPayload.cpp +++ b/src/common/restapi/http/HTTPPayload.cpp @@ -9,11 +9,11 @@ * */ #include "Defines.h" -#include "network/rest/http/HTTPPayload.h" +#include "restapi/http/HTTPPayload.h" #include "Log.h" #include "Utils.h" -using namespace network::rest::http; +using namespace restapi::http; #include diff --git a/src/common/restapi/http/HTTPPayload.h b/src/common/restapi/http/HTTPPayload.h new file mode 100644 index 000000000..ba493a1cc --- /dev/null +++ b/src/common/restapi/http/HTTPPayload.h @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HTTPPayload.h + * @ingroup http + * @file HTTPPayload.cpp + * @ingroup http + */ +#if !defined(__REST_HTTP__HTTP_PAYLOAD_H__) +#define __REST_HTTP__HTTP_PAYLOAD_H__ + +#include "common/Defines.h" +#include "common/json/json.h" +#include "common/restapi/http/HTTPHeaders.h" + +#include +#include + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + #define HTTP_GET "GET" + #define HTTP_POST "POST" + #define HTTP_PUT "PUT" + #define HTTP_DELETE "DELETE" + #define HTTP_OPTIONS "OPTIONS" + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This struct implements a model of a payload to be sent to a + * HTTP client/server. + * @ingroup http + */ + struct HTTPPayload { + /** + * @brief HTTP Status/Response Codes + */ + enum StatusType { + OK = 200, //!< HTTP OK 200 + CREATED = 201, //!< HTTP Created 201 + ACCEPTED = 202, //!< HTTP Accepted 202 + NO_CONTENT = 204, //!< HTTP No Content 204 + + MULTIPLE_CHOICES = 300, //!< HTTP Multiple Choices 300 + MOVED_PERMANENTLY = 301, //!< HTTP Moved Permenantly 301 + MOVED_TEMPORARILY = 302, //!< HTTP Moved Temporarily 302 + NOT_MODIFIED = 304, //!< HTTP Not Modified 304 + + BAD_REQUEST = 400, //!< HTTP Bad Request 400 + UNAUTHORIZED = 401, //!< HTTP Unauthorized 401 + FORBIDDEN = 403, //!< HTTP Forbidden 403 + NOT_FOUND = 404, //!< HTTP Not Found 404 + + INTERNAL_SERVER_ERROR = 500, //!< HTTP Internal Server Error 500 + NOT_IMPLEMENTED = 501, //!< HTTP Not Implemented 501 + BAD_GATEWAY = 502, //!< HTTP Bad Gateway 502 + SERVICE_UNAVAILABLE = 503 //!< HTTP Service Unavailable 503 + } status; + + HTTPHeaders headers; + std::string content; + size_t contentLength; + + std::string method; + std::string uri; + + int httpVersionMajor; + int httpVersionMinor; + + bool isClientPayload = false; + + /** + * @brief Convert the payload into a vector of buffers. The buffers do not own the + * underlying memory blocks, therefore the payload object must remain valid and + * not be changed until the write operation has completed. + * @returns std::vector List of buffers representing the HTTP payload. + */ + std::vector toBuffers(); + + /** + * @brief Prepares payload for transmission by finalizing status and content type. + * @param obj + * @param status HTTP status. + */ + void payload(json::object& obj, StatusType status = OK); + /** + * @brief Prepares payload for transmission by finalizing status and content type. + * @param content + * @param status HTTP status. + * @param contentType HTTP content type. + */ + void payload(std::string& content, StatusType status = OK, const std::string& contentType = "text/html"); + + /** + * @brief Get a request payload. + * @param method HTTP method. + * @param uri HTTP uri. + */ + static HTTPPayload requestPayload(std::string method, std::string uri); + /** + * @brief Get a status payload. + * @param status HTTP status. + * @param contentType HTTP content type. + */ + static HTTPPayload statusPayload(StatusType status, const std::string& contentType = "text/html"); + + /** + * @brief Helper to attach a host TCP stream reader. + * @param remoteEndpoint Endpoint. + */ + void attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint); + + private: + /** + * @brief Internal helper to ensure the headers are of a default for the given content type. + * @param contentType HTTP content type. + */ + void ensureDefaultHeaders(const std::string& contentType = "text/html"); + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__HTTP_PAYLOAD_H__ diff --git a/src/common/network/rest/http/HTTPRequestHandler.cpp b/src/common/restapi/http/HTTPRequestHandler.cpp similarity index 96% rename from src/common/network/rest/http/HTTPRequestHandler.cpp rename to src/common/restapi/http/HTTPRequestHandler.cpp index 9b04fac7e..2bf96be50 100644 --- a/src/common/network/rest/http/HTTPRequestHandler.cpp +++ b/src/common/restapi/http/HTTPRequestHandler.cpp @@ -9,10 +9,10 @@ * */ #include "Defines.h" -#include "network/rest/http/HTTPRequestHandler.h" -#include "network/rest/http/HTTPPayload.h" +#include "restapi/http/HTTPRequestHandler.h" +#include "restapi/http/HTTPPayload.h" -using namespace network::rest::http; +using namespace restapi::http; #include #include diff --git a/src/common/restapi/http/HTTPRequestHandler.h b/src/common/restapi/http/HTTPRequestHandler.h new file mode 100644 index 000000000..6bbc73a9c --- /dev/null +++ b/src/common/restapi/http/HTTPRequestHandler.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HTTPRequestHandler.h + * @ingroup http + * @file HTTPRequestHandler.cpp + * @ingroup http + */ +#if !defined(__REST_HTTP__HTTP_REQUEST_HANDLER_H__) +#define __REST_HTTP__HTTP_REQUEST_HANDLER_H__ + +#include "common/Defines.h" + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + struct HTTPPayload; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements the common handler for all incoming requests. + * @ingroup http + */ + class HTTPRequestHandler { + public: + auto operator=(HTTPRequestHandler&) -> HTTPRequestHandler& = delete; + HTTPRequestHandler(HTTPRequestHandler&) = delete; + + /** + * @brief Initializes a new instance of the HTTPRequestHandler class. + * @param docRoot Path to the document root to serve. + */ + explicit HTTPRequestHandler(const std::string& docRoot); + /** + * @brief + */ + HTTPRequestHandler(HTTPRequestHandler&&) = default; + + /** + * @brief + */ + HTTPRequestHandler& operator=(HTTPRequestHandler&&) = default; + + /** + * @brief Handle a request and produce a reply. + * @param req HTTP request. + * @param reply HTTP reply. + */ + void handleRequest(const HTTPPayload& req, HTTPPayload& reply); + + private: + /** + * @brief Internal helper to decode an incoming URL. + * @param[in] in Incoming URL string. + * @param[out] out Decoded URL string. + * @returns bool True, if URL decoded, otherwise false. + */ + static bool urlDecode(const std::string& in, std::string& out); + + std::string m_docRoot; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__HTTP_REQUEST_HANDLER_H__ diff --git a/src/common/restapi/http/HTTPServer.h b/src/common/restapi/http/HTTPServer.h new file mode 100644 index 000000000..b47c392b8 --- /dev/null +++ b/src/common/restapi/http/HTTPServer.h @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HTTPServer.h + * @ingroup http + */ +#if !defined(__REST_HTTP__HTTP_SERVER_H__) +#define __REST_HTTP__HTTP_SERVER_H__ + +#include "common/Defines.h" +#include "common/restapi/http/ServerConnection.h" +#include "common/restapi/http/ServerConnectionManager.h" +#include "common/restapi/http/HTTPRequestHandler.h" + +#include +#include +#include +#include +#include + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements top-level routines of the HTTP server. + * @tparam RequestHandlerType Type representing a request handler. + * @tparam ConnectionImpl Type representing the connection implementation. + * @ingroup http + */ + template class ConnectionImpl = ServerConnection> + class HTTPServer { + public: + auto operator=(HTTPServer&) -> HTTPServer& = delete; + auto operator=(HTTPServer&&) -> HTTPServer& = delete; + HTTPServer(HTTPServer&) = delete; + + /** + * @brief Initializes a new instance of the HTTPServer class. + * @param address Hostname/IP Address. + * @param port Port. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + explicit HTTPServer(const std::string& address, uint16_t port, bool debug) : + m_ioService(), + m_acceptor(m_ioService), + m_connectionManager(), + m_socket(m_ioService), + m_requestHandler(), + m_debug(debug) + { + // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) + asio::ip::address ipAddress = asio::ip::address::from_string(address); + m_endpoint = asio::ip::tcp::endpoint(ipAddress, port); + } + + /** + * @brief Helper to set the HTTP request handlers. + * @tparam Handler Type representing the request handler. + * @param handler Request handler. + */ + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /** + * @brief Open TCP acceptor. + */ + void open() + { + // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) + m_acceptor.open(m_endpoint.protocol()); + m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + m_acceptor.set_option(asio::socket_base::keep_alive(true)); + m_acceptor.bind(m_endpoint); + m_acceptor.listen(); + + accept(); + } + + /** + * @brief Run the servers ASIO IO service loop. + */ + void run() + { + // the run() call will block until all asynchronous operations + // have finished; while the server is running, there is always at least one + // asynchronous operation outstanding: the asynchronous accept call waiting + // for new incoming connections + m_ioService.run(); + } + + /** + * @brief Helper to stop running ASIO IO services. + */ + void stop() + { + // the server is stopped by cancelling all outstanding asynchronous + // operations; once all operations have finished the m_ioService::run() + // call will exit + m_acceptor.close(); + m_connectionManager.stopAll(); + } + + private: + /** + * @brief Perform an asynchronous accept operation. + */ + void accept() + { + m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { + // check whether the server was stopped by a signal before this + // completion handler had a chance to run + if (!m_acceptor.is_open()) { + return; + } + + if (!ec) { + m_connectionManager.start(std::make_shared(std::move(m_socket), m_connectionManager, m_requestHandler, false, m_debug)); + } + + accept(); + }); + } + + typedef ConnectionImpl ConnectionType; + typedef std::shared_ptr ConnectionTypePtr; + + asio::io_service m_ioService; + asio::ip::tcp::acceptor m_acceptor; + + asio::ip::tcp::endpoint m_endpoint; + + ServerConnectionManager m_connectionManager; + + asio::ip::tcp::socket m_socket; + + RequestHandlerType m_requestHandler; + bool m_debug; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__HTTP_SERVER_H__ diff --git a/src/common/restapi/http/SecureClientConnection.h b/src/common/restapi/http/SecureClientConnection.h new file mode 100644 index 000000000..16d24341f --- /dev/null +++ b/src/common/restapi/http/SecureClientConnection.h @@ -0,0 +1,261 @@ +// 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. + * + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SecureClientConnection.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SECURE_CLIENT_CONNECTION_H__) +#define __REST_HTTP__SECURE_CLIENT_CONNECTION_H__ + +#if defined(ENABLE_SSL) + +#include "common/Defines.h" +#include "common/restapi/http/HTTPLexer.h" +#include "common/restapi/http/HTTPPayload.h" +#include "common/Log.h" + +#include +#include +#include +#include + +#include +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class represents a single connection from a client. + * @tparam RequestHandlerType Type representing a request handler. + * @ingroup http + */ + template + class SecureClientConnection { + public: + auto operator=(SecureClientConnection&) -> SecureClientConnection& = delete; + auto operator=(SecureClientConnection&&) -> SecureClientConnection& = delete; + SecureClientConnection(SecureClientConnection&) = delete; + + /** + * @brief Initializes a new instance of the SecureClientConnection class. + * @param socket TCP socket for this connection. + * @param context SSL context for this connection. + * @param handler Request handler for this connection. + */ + explicit SecureClientConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, RequestHandlerType& handler) : + m_socket(std::move(socket), context), + m_requestHandler(handler), + m_lexer(HTTPLexer(true)) + { + m_socket.set_verify_mode(asio::ssl::verify_none); + m_socket.set_verify_callback(std::bind(&SecureClientConnection::verify_certificate, this, std::placeholders::_1, std::placeholders::_2)); + } + + /** + * @brief Start the first asynchronous operation for the connection. + */ + void start() + { + m_socket.handshake(asio::ssl::stream_base::client); + read(); + } + /** + * @brief Stop all asynchronous operations associated with the connection. + */ + void stop() + { + try + { + ensureNoLinger(); + if (m_socket.lowest_layer().is_open()) { + m_socket.lowest_layer().close(); + } + } + catch(const std::exception&) { /* ignore */ } + } + + /** + * @brief Helper to enable the SO_LINGER socket option during shutdown. + */ + void ensureNoLinger() + { + try + { + // enable SO_LINGER timeout 0 + asio::socket_base::linger linger(true, 0); + m_socket.lowest_layer().set_option(linger); + } + catch(const asio::system_error& e) + { + asio::error_code ec = e.code(); + if (ec) { + ::LogError(LOG_REST, "SecureClientConnection::ensureNoLinger(), %s, code = %u", ec.message().c_str(), ec.value()); + } + } + } + + /** + * @brief Perform an synchronous write operation. + * @param request HTTP request. + */ + void send(HTTPPayload request) + { + request.attachHostHeader(m_socket.lowest_layer().remote_endpoint()); + write(request); + } + private: + /** + * @brief Perform an SSL certificate verification. + * @param preverified Flag indicating the SSL certificate was preverified. + * @param context SSL verification context. + * @returns True, if SSL certificate is valid, otherwise false. + */ + bool verify_certificate(bool preverified, asio::ssl::verify_context& context) + { + return true; // ignore always valid + } + + /** + * @brief Perform an asynchronous read operation. + */ + void read() + { + m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t bytes_transferred) { + if (!ec) { + HTTPLexer::ResultType result; + char* content; + + try + { + if (m_sizeToTransfer > 0U && (m_bytesTransferred + bytes_transferred) < m_sizeToTransfer) { + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + read(); + } + else { + if (m_sizeToTransfer > 0U) { + // final copy + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + m_sizeToTransfer = 0U; + bytes_transferred = m_bytesTransferred; + + // reset lexer and re-parse the full content + m_lexer.reset(); + std::tie(result, content) = m_lexer.parse(m_request, m_fullBuffer.data(), m_fullBuffer.data() + bytes_transferred); + } else { + ::memcpy(m_fullBuffer.data() + m_bytesTransferred, m_buffer.data(), bytes_transferred); + m_bytesTransferred += bytes_transferred; + + std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + bytes_transferred); + } + + // determine content length + std::string contentLength = m_request.headers.find("Content-Length"); + if (contentLength != "") { + size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); + + // setup a full read if necessary + if (length > bytes_transferred && m_sizeToTransfer == 0U) { + m_sizeToTransfer = length; + } + + if (m_sizeToTransfer > 0U) { + result = HTTPLexer::CONTINUE; + } else { + m_request.content = std::string(content, length); + } + } + + m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string()); + if (result == HTTPLexer::GOOD) { + m_sizeToTransfer = m_bytesTransferred = 0U; + m_requestHandler.handleRequest(m_request, m_reply); + } + else if (result == HTTPLexer::BAD) { + m_sizeToTransfer = m_bytesTransferred = 0U; + return; + } + else { + read(); + } + } + } + catch(const std::exception& e) { ::LogError(LOG_REST, "SecureClientConnection::read(), %s", ec.message().c_str()); } + } + else if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "SecureClientConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); + } + stop(); + } + }); + } + + /** + * @brief Perform an synchronous write operation. + * @param request HTTP request. + */ + void write(HTTPPayload request) + { + try + { + m_socket.handshake(asio::ssl::stream_base::client); + + auto buffers = request.toBuffers(); + asio::write(m_socket, buffers); + } + catch(const asio::system_error& e) + { + asio::error_code ec = e.code(); + if (ec) { + ::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + + try + { + // initiate graceful connection closure + asio::error_code ignored_ec; + m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + catch(const std::exception& e) { + ::LogError(LOG_REST, "SecureClientConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + } + } + } + } + + asio::ssl::stream m_socket; + + RequestHandlerType& m_requestHandler; + + std::size_t m_sizeToTransfer; + std::size_t m_bytesTransferred; + std::array m_fullBuffer; + + std::array m_buffer; + + HTTPPayload m_request; + HTTPLexer m_lexer; + HTTPPayload m_reply; + }; + } // namespace http +} // namespace restapi + +#endif // ENABLE_SSL + +#endif // __REST_HTTP__SECURE_CLIENT_CONNECTION_H__ diff --git a/src/common/restapi/http/SecureHTTPClient.h b/src/common/restapi/http/SecureHTTPClient.h new file mode 100644 index 000000000..e4a42bf97 --- /dev/null +++ b/src/common/restapi/http/SecureHTTPClient.h @@ -0,0 +1,203 @@ +// 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. +* +* Copyright (C) 2024 Bryan Biedenkapp, N2PLL +* +*/ +/** + * @file SecureHTTPClient.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SECURE_HTTP_CLIENT_H__) +#define __REST_HTTP__SECURE_HTTP_CLIENT_H__ + +#if defined(ENABLE_SSL) + +#include "common/Defines.h" +#include "common/restapi/http/SecureClientConnection.h" +#include "common/restapi/http/HTTPRequestHandler.h" +#include "common/Thread.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements top-level routines of the secure HTTP client. + * @tparam RequestHandlerType Type representing a request handler. + * @tparam ConnectionImpl Type representing the connection implementation. + * @ingroup http + */ + template class ConnectionImpl = SecureClientConnection> + class SecureHTTPClient : private Thread { + public: + auto operator=(SecureHTTPClient&) -> SecureHTTPClient& = delete; + auto operator=(SecureHTTPClient&&) -> SecureHTTPClient& = delete; + SecureHTTPClient(SecureHTTPClient&) = delete; + + /** + * @brief Initializes a new instance of the SecureHTTPClient class. + * @param address Hostname/IP Address. + * @param port Port. + */ + SecureHTTPClient(const std::string& address, uint16_t port) : + m_address(address), + m_port(port), + m_connection(nullptr), + m_ioContext(), + m_context(asio::ssl::context::tlsv12), + m_socket(m_ioContext), + m_requestHandler() + { + /* stub */ + } + /** + * @brief Finalizes a instance of the SecureHTTPClient class. + */ + ~SecureHTTPClient() override + { + if (m_connection != nullptr) { + close(); + } + } + + /** + * @brief Helper to set the HTTP request handlers. + * @tparam Handler Type representing the request handler. + * @param handler Request handler. + */ + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /** + * @brief Send HTTP request to HTTP server. + * @param request HTTP request. + * @returns True, if request was completed, otherwise false. + */ + bool request(HTTPPayload& request) + { + if (m_completed) { + return false; + } + + asio::post(m_ioContext, [this, request]() { + std::lock_guard guard(m_lock); + { + if (m_connection != nullptr) { + m_connection->send(request); + } + } + }); + + return true; + } + + /** + * @brief Opens connection to the network. + */ + bool open() + { + if (m_completed) { + return false; + } + + return run(); + } + + /** + * @brief Closes connection to the network. + */ + void close() + { + if (m_completed) { + return; + } + + m_completed = true; + m_ioContext.stop(); + + wait(); + } + + private: + /** + * @brief Internal entry point for the ASIO IO context thread. + */ + void entry() override + { + if (m_completed) { + return; + } + + asio::ip::tcp::resolver resolver(m_ioContext); + auto endpoints = resolver.resolve(m_address, std::to_string(m_port)); + + try { + connect(endpoints); + + // the entry() call will block until all asynchronous operations + // have finished + m_ioContext.run(); + } + catch (std::exception&) { /* stub */ } + + if (m_connection != nullptr) { + m_connection->stop(); + } + } + + /** + * @brief Perform an asynchronous connect operation. + * @param endpoints TCP endpoint to connect to. + */ + void connect(asio::ip::basic_resolver_results& endpoints) + { + asio::connect(m_socket, endpoints); + + m_connection = std::make_unique(std::move(m_socket), m_context, m_requestHandler); + m_connection->start(); + } + + std::string m_address; + uint16_t m_port; + + typedef ConnectionImpl ConnectionType; + + std::unique_ptr m_connection; + + bool m_completed = false; + asio::io_context m_ioContext; + + asio::ssl::context m_context; + asio::ip::tcp::socket m_socket; + + RequestHandlerType m_requestHandler; + + std::mutex m_lock; + }; + } // namespace http +} // namespace restapi + +#endif // ENABLE_SSL + +#endif // __REST_HTTP__SECURE_HTTP_CLIENT_H__ diff --git a/src/common/restapi/http/SecureHTTPServer.h b/src/common/restapi/http/SecureHTTPServer.h new file mode 100644 index 000000000..25ff73d1c --- /dev/null +++ b/src/common/restapi/http/SecureHTTPServer.h @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HTTPServer.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SECURE_HTTP_SERVER_H__) +#define __REST_HTTP__SECURE_HTTP_SERVER_H__ + +#if defined(ENABLE_SSL) + +#include "common/Defines.h" +#include "common/restapi/http/SecureServerConnection.h" +#include "common/restapi/http/ServerConnectionManager.h" +#include "common/restapi/http/HTTPRequestHandler.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements top-level routines of the secure HTTP server. + * @tparam RequestHandlerType Type representing a request handler. + * @tparam ConnectionImpl Type representing the connection implementation. + * @ingroup http + */ + template class ConnectionImpl = SecureServerConnection> + class SecureHTTPServer { + public: + auto operator=(SecureHTTPServer&) -> SecureHTTPServer& = delete; + auto operator=(SecureHTTPServer&&) -> SecureHTTPServer& = delete; + SecureHTTPServer(SecureHTTPServer&) = delete; + + /** + * @brief Initializes a new instance of the SecureHTTPServer class. + * @param address Hostname/IP Address. + * @param port Port. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + explicit SecureHTTPServer(const std::string& address, uint16_t port, bool debug) : + m_ioService(), + m_acceptor(m_ioService), + m_connectionManager(), + m_context(asio::ssl::context::tlsv12), + m_socket(m_ioService), + m_requestHandler(), + m_debug(debug) + { + asio::ip::address ipAddress = asio::ip::address::from_string(address); + m_endpoint = asio::ip::tcp::endpoint(ipAddress, port); + } + + /** + * @brief Helper to set the SSL certificate and private key. + * @param keyFile SSL certificate private key. + * @param certFile SSL certificate. + */ + bool setCertAndKey(const std::string& keyFile, const std::string& certFile) + { + try + { + m_context.use_certificate_chain_file(certFile); + m_context.use_private_key_file(keyFile, asio::ssl::context::pem); + return true; + } + catch(const std::exception& e) { + ::LogError(LOG_REST, "%s", e.what()); + return false; + } + } + + /** + * @brief Helper to set the HTTP request handlers. + * @tparam Handler Type representing the request handler. + * @param handler Request handler. + */ + template + void setHandler(Handler&& handler) + { + m_requestHandler = RequestHandlerType(std::forward(handler)); + } + + /** + * @brief Open TCP acceptor. + */ + void open() + { + // open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR) + m_acceptor.open(m_endpoint.protocol()); + m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + m_acceptor.set_option(asio::socket_base::keep_alive(true)); + m_acceptor.bind(m_endpoint); + m_acceptor.listen(); + + accept(); + } + + /** + * @brief Run the servers ASIO IO service loop. + */ + void run() + { + // the run() call will block until all asynchronous operations + // have finished; while the server is running, there is always at least one + // asynchronous operation outstanding: the asynchronous accept call waiting + // for new incoming connections + m_ioService.run(); + } + + /** + * @brief Helper to stop running ASIO IO services. + */ + void stop() + { + // the server is stopped by cancelling all outstanding asynchronous + // operations; once all operations have finished the m_ioService::run() + // call will exit + m_acceptor.close(); + m_connectionManager.stopAll(); + } + + private: + /** + * @brief Perform an asynchronous accept operation. + */ + void accept() + { + m_acceptor.async_accept(m_socket, [this](asio::error_code ec) { + // check whether the server was stopped by a signal before this + // completion handler had a chance to run + if (!m_acceptor.is_open()) { + return; + } + + if (!ec) { + m_connectionManager.start(std::make_shared(std::move(m_socket), m_context, m_connectionManager, m_requestHandler, false, m_debug)); + } + + accept(); + }); + } + + typedef ConnectionImpl ConnectionType; + typedef std::shared_ptr ConnectionTypePtr; + + asio::io_service m_ioService; + asio::ip::tcp::acceptor m_acceptor; + + asio::ip::tcp::endpoint m_endpoint; + + ServerConnectionManager m_connectionManager; + + asio::ssl::context m_context; + asio::ip::tcp::socket m_socket; + + std::string m_certFile; + std::string m_keyFile; + + RequestHandlerType m_requestHandler; + bool m_debug; + }; + } // namespace http +} // namespace restapi + +#endif // ENABLE_SSL + +#endif // __REST_HTTP__SECURE_HTTP_SERVER_H__ diff --git a/src/common/restapi/http/SecureServerConnection.h b/src/common/restapi/http/SecureServerConnection.h new file mode 100644 index 000000000..32dcd4a6c --- /dev/null +++ b/src/common/restapi/http/SecureServerConnection.h @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SecureServerConnection.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SECURE_SERVER_CONNECTION_H__) +#define __REST_HTTP__SECURE_SERVER_CONNECTION_H__ + +#if defined(ENABLE_SSL) + +#include "common/Defines.h" +#include "common/restapi/http/HTTPLexer.h" +#include "common/restapi/http/HTTPPayload.h" +#include "common/Log.h" + +#include +#include +#include +#include + +#include +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + template class ServerConnectionManager; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class represents a single connection from a client. + * @tparam RequestHandlerType Type representing a request handler. + * @ingroup http + */ + template + class SecureServerConnection : public std::enable_shared_from_this> { + typedef SecureServerConnection selfType; + typedef std::shared_ptr selfTypePtr; + typedef ServerConnectionManager ConnectionManagerType; + public: + auto operator=(SecureServerConnection&) -> SecureServerConnection& = delete; + auto operator=(SecureServerConnection&&) -> SecureServerConnection& = delete; + SecureServerConnection(SecureServerConnection&) = delete; + + /** + * @brief Initializes a new instance of the SecureServerConnection class. + * @param socket TCP socket for this connection. + * @param context SSL context. + * @param manager Connection manager for this connection. + * @param handler Request handler for this connection. + * @param persistent Flag indicating whether or not the connection is persistent. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + explicit SecureServerConnection(asio::ip::tcp::socket socket, asio::ssl::context& context, ConnectionManagerType& manager, RequestHandlerType& handler, + bool persistent = false, bool debug = false) : + m_socket(std::move(socket), context), + m_connectionManager(manager), + m_requestHandler(handler), + m_lexer(HTTPLexer(false)), + m_continue(false), + m_contResult(HTTPLexer::INDETERMINATE), + m_persistent(persistent), + m_debug(debug) + { + /* stub */ + } + + /** + * @brief Start the first asynchronous operation for the connection. + */ + void start() { handshake(); } + /** + * @brief Stop all asynchronous operations associated with the connection. + */ + void stop() + { + try + { + if (m_socket.lowest_layer().is_open()) { + m_socket.lowest_layer().close(); + } + } + catch(const std::exception&) { /* ignore */ } + } + + private: + /** + * @brief Perform an asynchronous SSL handshake. + */ + void handshake() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } + + m_socket.async_handshake(asio::ssl::stream_base::server, [this](asio::error_code ec) { + if (!ec) { + read(); + } + }); + } + + /** + * @brief Perform an asynchronous read operation. + */ + void read() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } + + m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t recvLength) { + if (!ec) { + HTTPLexer::ResultType result = HTTPLexer::GOOD; + char* content; + + // catch exceptions here so we don't blatently crash the system + try + { + if (!m_continue) { + std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + recvLength); + + m_request.content = std::string(); + std::string contentLength = m_request.headers.find("Content-Length"); + if (contentLength != "" && (::strlen(content) != 0)) { + size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); + m_request.contentLength = length; + m_request.content = std::string(content, length); + } + + m_request.headers.add("RemoteHost", m_socket.lowest_layer().remote_endpoint().address().to_string()); + + uint32_t consumed = m_lexer.consumed(); + if (result == HTTPLexer::GOOD && consumed == recvLength && + ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { + if (m_debug) { + LogDebug(LOG_REST, "HTTPS Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); + } + + result = HTTPLexer::INDETERMINATE; + m_continue = true; + } + } else { + if (m_debug) { + LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); + Utils::dump(1U, "SecureServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); + } + + if (m_contResult == HTTPLexer::INDETERMINATE) { + m_request.content = std::string(m_buffer.data(), recvLength); + } else { + m_request.content.append(std::string(m_buffer.data(), recvLength)); + } + + if (m_request.contentLength != 0 && recvLength < m_request.contentLength) { + m_contResult = result = HTTPLexer::CONTINUE; + m_continue = true; + } + } + + if (result == HTTPLexer::GOOD) { + if (m_debug) { + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + } + + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + m_requestHandler.handleRequest(m_request, m_reply); + + if (m_debug) { + Utils::dump(1U, "SecureServerConnection::read(), HTTPS Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + } + + write(); + } + else if (result == HTTPLexer::BAD) { + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); + write(); + } + else { + read(); + } + } + catch(const std::exception& e) { + ::LogError(LOG_REST, "SecureServerConnection::read(), %s", ec.message().c_str()); + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + } + } + else if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "SecureServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); + } + m_connectionManager.stop(this->shared_from_this()); + m_continue = false; + } + }); + } + + /** + * @brief Perform an asynchronous write operation. + */ + void write() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } else { + m_reply.headers.add("Connection", "keep-alive"); + } + + auto buffers = m_reply.toBuffers(); + asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) { + if (m_persistent) { + m_lexer.reset(); + m_reply.headers = HTTPHeaders(); + m_reply.status = HTTPPayload::OK; + m_reply.content = ""; + m_request = HTTPPayload(); + read(); + } + else { + if (!ec) { + try + { + // initiate graceful connection closure + asio::error_code ignored_ec; + m_socket.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + catch(const std::exception& e) { ::LogError(LOG_REST, "%s", ec.message().c_str()); } + } + + if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "SecureServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + } + m_connectionManager.stop(this->shared_from_this()); + } + } + }); + } + + asio::ssl::stream m_socket; + + ConnectionManagerType& m_connectionManager; + RequestHandlerType& m_requestHandler; + + std::array m_buffer; + + HTTPPayload m_request; + HTTPLexer m_lexer; + HTTPPayload m_reply; + + bool m_continue; + HTTPLexer::ResultType m_contResult; + + bool m_persistent; + bool m_debug; + }; + } // namespace http +} // namespace restapi + +#endif // ENABLE_SSL + +#endif // __REST_HTTP__SECURE_SERVER_CONNECTION_H__ diff --git a/src/common/restapi/http/ServerConnection.h b/src/common/restapi/http/ServerConnection.h new file mode 100644 index 000000000..d51953417 --- /dev/null +++ b/src/common/restapi/http/ServerConnection.h @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file ServerConnection.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SERVER_CONNECTION_H__) +#define __REST_HTTP__SERVER_CONNECTION_H__ + +#include "common/Defines.h" +#include "common/restapi/http/HTTPLexer.h" +#include "common/restapi/http/HTTPPayload.h" +#include "common/Log.h" +#include "common/Utils.h" + +#include +#include +#include +#include + +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + template class ServerConnectionManager; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class represents a single connection from a client. + * @tparam RequestHandlerType Type representing a request handler. + * @ingroup http + */ + template + class ServerConnection : public std::enable_shared_from_this> { + typedef ServerConnection selfType; + typedef std::shared_ptr selfTypePtr; + typedef ServerConnectionManager ConnectionManagerType; + public: + auto operator=(ServerConnection&) -> ServerConnection& = delete; + auto operator=(ServerConnection&&) -> ServerConnection& = delete; + ServerConnection(ServerConnection&) = delete; + + /** + * @brief Initializes a new instance of the ServerConnection class. + * @param socket TCP socket for this connection. + * @param manager Connection manager for this connection. + * @param handler Request handler for this connection. + * @param persistent Flag indicating whether or not the connection is persistent. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + explicit ServerConnection(asio::ip::tcp::socket socket, ConnectionManagerType& manager, RequestHandlerType& handler, + bool persistent = false, bool debug = false) : + m_socket(std::move(socket)), + m_connectionManager(manager), + m_requestHandler(handler), + m_lexer(HTTPLexer(false)), + m_continue(false), + m_contResult(HTTPLexer::INDETERMINATE), + m_persistent(persistent), + m_debug(debug) + { + /* stub */ + } + + /** + * @brief Start the first asynchronous operation for the connection. + */ + void start() { read(); } + /** + * @brief Stop all asynchronous operations associated with the connection. + */ + void stop() + { + try + { + if (m_socket.is_open()) { + m_socket.close(); + } + } + catch(const std::exception&) { /* ignore */ } + } + + private: + /** + * @brief Perform an asynchronous read operation. + */ + void read() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } + + m_socket.async_read_some(asio::buffer(m_buffer), [=](asio::error_code ec, std::size_t recvLength) { + if (!ec) { + HTTPLexer::ResultType result = HTTPLexer::GOOD; + char* content; + + // catch exceptions here so we don't blatently crash the system + try + { + if (!m_continue) { + std::tie(result, content) = m_lexer.parse(m_request, m_buffer.data(), m_buffer.data() + recvLength); + + m_request.content = std::string(); + std::string contentLength = m_request.headers.find("Content-Length"); + if (contentLength != "" && (::strlen(content) != 0)) { + size_t length = (size_t)::strtoul(contentLength.c_str(), NULL, 10); + m_request.contentLength = length; + m_request.content = std::string(content, length); + } + + m_request.headers.add("RemoteHost", m_socket.remote_endpoint().address().to_string()); + + uint32_t consumed = m_lexer.consumed(); + if (result == HTTPLexer::GOOD && consumed == recvLength && + ((m_request.method == HTTP_POST) || (m_request.method == HTTP_PUT))) { + if (m_debug) { + LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, consumed = %u, result = %u", recvLength, consumed, result); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); + } + + m_contResult = result = HTTPLexer::INDETERMINATE; + m_continue = true; + } + } else { + if (m_debug) { + LogDebug(LOG_REST, "HTTP Partial Request, recvLength = %u, result = %u", recvLength, result); + Utils::dump(1U, "ServerConnection::read(), m_buffer", (uint8_t*)m_buffer.data(), recvLength); + } + + if (m_contResult == HTTPLexer::INDETERMINATE) { + m_request.content = std::string(m_buffer.data(), recvLength); + } else { + m_request.content.append(std::string(m_buffer.data(), recvLength)); + } + + if (m_request.contentLength != 0 && recvLength < m_request.contentLength) { + m_contResult = result = HTTPLexer::CONTINUE; + m_continue = true; + } + } + + if (result == HTTPLexer::GOOD) { + if (m_debug) { + Utils::dump(1U, "ServerConnection::read(), HTTP Request Content", (uint8_t*)m_request.content.c_str(), m_request.content.length()); + } + + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + m_requestHandler.handleRequest(m_request, m_reply); + + if (m_debug) { + Utils::dump(1U, "ServerConnection::read(), HTTP Reply Content", (uint8_t*)m_reply.content.c_str(), m_reply.content.length()); + } + + write(); + } + else if (result == HTTPLexer::BAD) { + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + m_reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST); + write(); + } + else { + read(); + } + } + catch(const std::exception& e) { + ::LogError(LOG_REST, "ServerConnection::read(), %s", ec.message().c_str()); + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + } + } + else if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "ServerConnection::read(), %s, code = %u", ec.message().c_str(), ec.value()); + } + m_connectionManager.stop(this->shared_from_this()); + m_continue = false; + m_contResult = HTTPLexer::INDETERMINATE; + } + }); + } + + /** + * @brief Perform an asynchronous write operation. + */ + void write() + { + if (!m_persistent) { + auto self(this->shared_from_this()); + } else { + m_reply.headers.add("Connection", "keep-alive"); + } + + auto buffers = m_reply.toBuffers(); + asio::async_write(m_socket, buffers, [=](asio::error_code ec, std::size_t) { + if (m_persistent) { + m_lexer.reset(); + m_reply.headers = HTTPHeaders(); + m_reply.status = HTTPPayload::OK; + m_reply.content = ""; + m_request = HTTPPayload(); + read(); + } + else { + if (!ec) { + try + { + // initiate graceful connection closure + asio::error_code ignored_ec; + m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } + catch(const std::exception& e) { ::LogError(LOG_REST, "ServerConnection::write(), %s", ec.message().c_str()); } + } + + if (ec != asio::error::operation_aborted) { + if (ec) { + ::LogError(LOG_REST, "ServerConnection::write(), %s, code = %u", ec.message().c_str(), ec.value()); + } + m_connectionManager.stop(this->shared_from_this()); + } + } + }); + } + + asio::ip::tcp::socket m_socket; + + ConnectionManagerType& m_connectionManager; + RequestHandlerType& m_requestHandler; + + std::array m_buffer; + + HTTPPayload m_request; + HTTPLexer m_lexer; + HTTPPayload m_reply; + + bool m_continue; + HTTPLexer::ResultType m_contResult; + + bool m_persistent; + bool m_debug; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__SERVER_CONNECTION_H__ diff --git a/src/common/restapi/http/ServerConnectionManager.h b/src/common/restapi/http/ServerConnectionManager.h new file mode 100644 index 000000000..fe4e2e9fd --- /dev/null +++ b/src/common/restapi/http/ServerConnectionManager.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file ServerConnection.h + * @ingroup http + */ +#if !defined(__REST_HTTP__SERVER_CONNECTION_MANAGER_H__) +#define __REST_HTTP__SERVER_CONNECTION_MANAGER_H__ + +#include "common/Defines.h" + +#include +#include + +namespace restapi +{ + namespace http + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Manages open connections so that they may be cleanly stopped when the server + * needs to shut down. + * @tparam ConnectionPtr + * @ingroup http + */ + template + class ServerConnectionManager { + public: + auto operator=(ServerConnectionManager&) -> ServerConnectionManager& = delete; + auto operator=(ServerConnectionManager&&) -> ServerConnectionManager& = delete; + ServerConnectionManager(ServerConnectionManager&) = delete; + + /** + * @brief Initializes a new instance of the ServerConnectionManager class. + */ + ServerConnectionManager() = default; + + /** + * @brief Add the specified connection to the manager and start it. + * @param c + */ + void start(ConnectionPtr c) + { + std::lock_guard guard(m_lock); + { + m_connections.insert(c); + } + c->start(); + } + + /** + * @brief Stop the specified connection. + * @param c + */ + void stop(ConnectionPtr c) + { + std::lock_guard guard(m_lock); + { + m_connections.erase(c); + } + c->stop(); + } + + /** + * @brief Stop all connections. + */ + void stopAll() + { + for (auto c : m_connections) + c->stop(); + + std::lock_guard guard(m_lock); + m_connections.clear(); + } + + private: + std::set m_connections; + std::mutex m_lock; + }; + } // namespace http +} // namespace restapi + +#endif // __REST_HTTP__SERVER_CONNECTION_MANAGER_H__ diff --git a/src/fne/ActivityLog.cpp b/src/fne/ActivityLog.cpp index d7a974f4b..518190e52 100644 --- a/src/fne/ActivityLog.cpp +++ b/src/fne/ActivityLog.cpp @@ -31,12 +31,12 @@ const uint32_t ACT_LOG_BUFFER_LEN = 501U; // Global Variables // --------------------------------------------------------------------------- -static std::string m_actFilePath; -static std::string m_actFileRoot; +static std::string g_actFilePath; +static std::string g_aclFileRoot; -static FILE* m_actFpLog = nullptr; +static FILE* g_actFpLog = nullptr; -static struct tm m_actTm; +static struct tm g_actTm; // --------------------------------------------------------------------------- // Global Functions @@ -54,22 +54,22 @@ static bool ActivityLogOpen() struct tm* tm = ::localtime(&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) + if (tm->tm_mday == g_actTm.tm_mday && tm->tm_mon == g_actTm.tm_mon && tm->tm_year == g_actTm.tm_year) { + if (g_actFpLog != nullptr) return true; } else { - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } char filename[200U]; - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", g_actFilePath.c_str(), g_aclFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - m_actFpLog = ::fopen(filename, "a+t"); - m_actTm = *tm; + g_actFpLog = ::fopen(filename, "a+t"); + g_actTm = *tm; - return m_actFpLog != nullptr; + return g_actFpLog != nullptr; } /* Initializes the activity log. */ @@ -79,8 +79,8 @@ bool ActivityLogInitialise(const std::string& filePath, const std::string& fileR #if defined(CATCH2_TEST_COMPILATION) return true; #endif - m_actFilePath = filePath; - m_actFileRoot = fileRoot; + g_actFilePath = filePath; + g_aclFileRoot = fileRoot; return ::ActivityLogOpen(); } @@ -92,31 +92,17 @@ void ActivityLogFinalise() #if defined(CATCH2_TEST_COMPILATION) return; #endif - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } /* Writes a new entry to the activity log. */ -void ActivityLog(const char* msg, ...) +void log_internal::ActivityLogInternal(const std::string& log) { #if defined(CATCH2_TEST_COMPILATION) return; #endif - assert(msg != nullptr); - - char buffer[ACT_LOG_BUFFER_LEN]; - - 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, len + 1U, msg, vl); - - va_end(vl_len); - va_end(vl); - bool ret = ::ActivityLogOpen(); if (!ret) return; @@ -124,11 +110,11 @@ void ActivityLog(const char* msg, ...) if (CurrentLogFileLevel() == 0U) return; - ::fprintf(m_actFpLog, "%s\n", buffer); - ::fflush(m_actFpLog); + ::fprintf(g_actFpLog, "%s\n", log.c_str()); + ::fflush(g_actFpLog); if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { - ::fprintf(stdout, "%s" EOL, buffer); + ::fprintf(stdout, "%s" EOL, log.c_str()); ::fflush(stdout); } } diff --git a/src/fne/ActivityLog.h b/src/fne/ActivityLog.h index ca32863fb..5800f76c1 100644 --- a/src/fne/ActivityLog.h +++ b/src/fne/ActivityLog.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -24,6 +24,16 @@ // Global Functions // --------------------------------------------------------------------------- +namespace log_internal +{ + /** + * @brief Writes a new entry to the diagnostics log. + * @param level Log level for entry. + * @param log Fully formatted log message. + */ + extern HOST_SW_API void ActivityLogInternal(const std::string& log); +} // namespace log_internal + /** * @brief Initializes the activity log. * @param filePath File path for the log file. @@ -34,12 +44,31 @@ extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const * @brief Finalizes the activity log. */ extern HOST_SW_API void ActivityLogFinalise(); + /** - * @brief Writes a new entry to the activity log. - * @param msg String format. + * @brief Writes a new entry to the diagnostics log. + * @param fmt String format. * - * This is a variable argument function. + * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. */ -extern HOST_SW_API void ActivityLog(const char* msg, ...); +template +HOST_SW_API void ActivityLog(const std::string& fmt, Args... args) +{ + using namespace log_internal; + + int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + + std::snprintf(buf.get(), size, fmt.c_str(), args ...); + + std::string msg = std::string(buf.get(), buf.get() + size - 1); + + ActivityLogInternal(std::string(msg)); +} #endif // __ACTIVITY_LOG_H__ diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index 2425e6c0c..2319fc23e 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -18,6 +18,8 @@ file(GLOB dvmfne_SRC "src/fne/network/influxdb/*.cpp" "src/fne/network/*.h" "src/fne/network/*.cpp" + "src/fne/restapi/*.h" + "src/fne/restapi/*.cpp" "src/fne/xml/*.h" "src/fne/win32/*.h" "src/fne/*.h" diff --git a/src/fne/CryptoContainer.cpp b/src/fne/CryptoContainer.cpp index d6ae60995..7566894b0 100644 --- a/src/fne/CryptoContainer.cpp +++ b/src/fne/CryptoContainer.cpp @@ -133,7 +133,7 @@ int findLastChar(const uint8_t* buffer, uint32_t len, char target) // Static Class Members // --------------------------------------------------------------------------- -std::mutex CryptoContainer::m_mutex; +std::mutex CryptoContainer::s_mutex; // --------------------------------------------------------------------------- // Public Class Members @@ -220,24 +220,24 @@ bool CryptoContainer::read() void CryptoContainer::clear() { - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); m_keys.clear(); } /* Adds a new entry to the lookup table by the specified unique ID. */ -void CryptoContainer::addEntry(KeyItem key) +void CryptoContainer::addEntry(EKCKeyItem key) { if (key.isInvalid()) return; - KeyItem entry = key; + EKCKeyItem entry = key; uint32_t id = entry.id(); uint32_t kId = entry.kId(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); auto it = std::find_if(m_keys.begin(), m_keys.end(), - [&](KeyItem x) + [&](EKCKeyItem& x) { return x.id() == id && x.kId() == kId; }); @@ -253,8 +253,11 @@ void CryptoContainer::addEntry(KeyItem key) void CryptoContainer::eraseEntry(uint32_t id) { - std::lock_guard lock(m_mutex); - auto it = std::find_if(m_keys.begin(), m_keys.end(), [&](KeyItem x) { return x.id() == id; }); + std::lock_guard lock(s_mutex); + auto it = std::find_if(m_keys.begin(), m_keys.end(), + [&](EKCKeyItem& x) { + return x.id() == id; + }); if (it != m_keys.end()) { m_keys.erase(it); } @@ -262,25 +265,56 @@ void CryptoContainer::eraseEntry(uint32_t id) /* Finds a table entry in this lookup table. */ -KeyItem CryptoContainer::find(uint32_t kId) +EKCKeyItem CryptoContainer::find(uint32_t kId) { - KeyItem entry; + EKCKeyItem entry; - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); auto it = std::find_if(m_keys.begin(), m_keys.end(), - [&](KeyItem x) - { + [&](EKCKeyItem& x) { return x.kId() == kId; }); if (it != m_keys.end()) { entry = *it; } else { - entry = KeyItem(); + entry = EKCKeyItem(); } return entry; } +/* Finds a table entry in this lookup table. */ + +EKCKeyItem CryptoContainer::findUKEK(uint32_t rsi) +{ + EKCKeyItem entry; + + std::lock_guard lock(s_mutex); + + /* + ** TODO TODO TODO + */ + entry = EKCKeyItem(); + + return entry; +} + +/* Finds a table entry in this lookup table. */ + +EKCKeyItem CryptoContainer::findLLA(uint32_t rsi) +{ + EKCKeyItem entry; + + std::lock_guard lock(s_mutex); + + /* + ** TODO TODO TODO + */ + entry = EKCKeyItem(); + + return entry; +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -496,14 +530,14 @@ bool CryptoContainer::load() // clear table clear(); - std::lock_guard lock(m_mutex); + std::lock_guard lock(s_mutex); // get keys node rapidxml::xml_node<>* keys = innerRoot->first_node("Keys"); if (keys != nullptr) { uint32_t i = 0U; for (rapidxml::xml_node<>* keyNode = keys->first_node("KeyItem"); keyNode; keyNode = keyNode->next_sibling()) { - KeyItem key = KeyItem(); + EKCKeyItem key = EKCKeyItem(); key.id(i); // get name diff --git a/src/fne/CryptoContainer.h b/src/fne/CryptoContainer.h index d72a0256c..78a3cab75 100644 --- a/src/fne/CryptoContainer.h +++ b/src/fne/CryptoContainer.h @@ -31,15 +31,15 @@ // --------------------------------------------------------------------------- /** - * @brief Represents an key item. + * @brief Represents an EKC key item. * @ingroup fne */ -class HOST_SW_API KeyItem { +class HOST_SW_API EKCKeyItem { public: /** - * @brief Initializes a new instance of the KeyItem class. + * @brief Initializes a new instance of the EKCKeyItem class. */ - KeyItem() : + EKCKeyItem() : m_id(0U), m_name(), m_keysetId(0U), @@ -52,10 +52,10 @@ class HOST_SW_API KeyItem { } /** - * @brief Equals operator. Copies this KeyItem to another KeyItem. - * @param data Instance of KeyItem to copy. + * @brief Equals operator. Copies this EKCKeyItem to another EKCKeyItem. + * @param data Instance of EKCKeyItem to copy. */ - virtual KeyItem& operator= (const KeyItem& data) + virtual EKCKeyItem& operator= (const EKCKeyItem& data) { if (this != &data) { m_id = data.m_id; @@ -157,9 +157,9 @@ class HOST_SW_API KeyItem { // --------------------------------------------------------------------------- /** - * @brief Implements a threading lookup table class that contains routing - * rules information. - * @ingroup lookups_tgid + * @brief Implements a threading lookup class that contains encryption key container + * information from KFDtool EKC files. + * @ingroup fne */ class HOST_SW_API CryptoContainer : public Thread { public: @@ -207,7 +207,7 @@ class HOST_SW_API CryptoContainer : public Thread { * @brief Adds a new entry to the lookup table. * @param key Key Item. */ - void addEntry(KeyItem key); + void addEntry(EKCKeyItem key); /** * @brief Erases an existing entry from the lookup table by the specified unique ID. * @param id Unique ID to erase. @@ -218,7 +218,20 @@ class HOST_SW_API CryptoContainer : public Thread { * @param kId Unique identifier for table entry. * @returns KeyItem Table entry. */ - virtual KeyItem find(uint32_t kId); + virtual EKCKeyItem find(uint32_t kId); + + /** + * @brief Finds a table entry in this lookup table. + * @param rsi Unique radio set identifier (RID/LLID). + * @returns KeyItem Table entry. + */ + virtual EKCKeyItem findUKEK(uint32_t rsi); + /** + * @brief Finds a table entry in this lookup table. + * @param rsi Unique radio set identifier (RID/LLID). + * @returns KeyItem Table entry. + */ + virtual EKCKeyItem findLLA(uint32_t rsi); /** * @brief Helper to return the flag indicating whether or not the crypto container is enabled. @@ -240,7 +253,7 @@ class HOST_SW_API CryptoContainer : public Thread { bool m_enabled; bool m_stop; - static std::mutex m_mutex; + static std::mutex s_mutex; /** * @brief Loads the table from the passed lookup table file. @@ -252,7 +265,7 @@ class HOST_SW_API CryptoContainer : public Thread { /** * @brief List of keys. */ - DECLARE_PROPERTY_PLAIN(std::vector, keys); + DECLARE_PROPERTY_PLAIN(std::vector, keys); }; #endif // __CRYPTO_CONTAINER_H__ diff --git a/src/fne/Defines.h b/src/fne/Defines.h index f627a768b..56ec425eb 100644 --- a/src/fne/Defines.h +++ b/src/fne/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants @@ -38,4 +39,14 @@ #undef DEFAULT_LOCK_FILE #define DEFAULT_LOCK_FILE "/tmp/dvmfne.lock" +/** @cond */ + +#define LOG_MASTER "MSTR" +#define LOG_DIAG "DIAG" +#define LOG_PEER "PEER" +#define LOG_REPL "REPL" +#define LOG_STP "STP" + +/** @endcond */ + #endif // __DEFINES_H__ diff --git a/src/fne/FNEMain.cpp b/src/fne/FNEMain.cpp index 5b61eacc3..09444c0f7 100644 --- a/src/fne/FNEMain.cpp +++ b/src/fne/FNEMain.cpp @@ -49,6 +49,8 @@ std::string g_lockFile = std::string(DEFAULT_LOCK_FILE); bool g_foreground = false; bool g_killed = false; +bool g_promiscuousHub = false; + uint8_t* g_gitHashBytes = nullptr; // --------------------------------------------------------------------------- @@ -98,6 +100,7 @@ void usage(const char* message, const char* arg) ::fprintf(stdout, "usage: %s [-vhf]" + "[-p]" "[--syslog]" "[-c ]" "\n\n" @@ -105,6 +108,8 @@ void usage(const char* message, const char* arg) " -h show this screen\n" " -f foreground mode\n" "\n" + " -p promiscuous hub\n" + "\n" " --syslog force logging to syslog\n" "\n" " -c specifies the configuration file to use\n" @@ -137,6 +142,9 @@ int checkArgs(int argc, char* argv[]) else if (IS("-f")) { g_foreground = true; } + else if (IS("-p")) { + g_promiscuousHub = true; + } else if (IS("--syslog")) { g_useSyslog = true; } @@ -210,6 +218,8 @@ int main(int argc, char** argv) } } + log_stacktrace::SignalHandling sh(g_foreground); + ::signal(SIGINT, sigHandler); ::signal(SIGTERM, sigHandler); #if !defined(_WIN32) diff --git a/src/fne/FNEMain.h b/src/fne/FNEMain.h index b677d7757..9d2791602 100644 --- a/src/fne/FNEMain.h +++ b/src/fne/FNEMain.h @@ -38,6 +38,9 @@ extern bool g_foreground; /** @brief (Global) Flag indicating the FNE should stop immediately. */ extern bool g_killed; +/** @brief (Global) Flag indicating the FNE is a promiscuous hub, and will pass all TGs and RIDs. */ +extern bool g_promiscuousHub; + extern uint8_t* g_gitHashBytes; /** diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 9ea4126fc..50bb47fd3 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -78,7 +78,7 @@ HostFNE::HostFNE(const std::string& confFile) : m_pingTime(5U), m_maxMissedPings(5U), m_updateLookupTime(10U), - m_peerLinkSavesACL(false), + m_peerReplicaSavesACL(false), m_useAlternatePortForDiagnostics(false), m_allowActivityTransfer(false), m_allowDiagnosticTransfer(false), @@ -339,7 +339,7 @@ bool HostFNE::readParams() m_maxMissedPings = systemConf["maxMissedPings"].as(5U); m_updateLookupTime = systemConf["aclRuleUpdateTime"].as(10U); bool sendTalkgroups = systemConf["sendTalkgroups"].as(true); - m_peerLinkSavesACL = systemConf["peerLinkSaveACL"].as(false); + m_peerReplicaSavesACL = systemConf["peerReplicaSaveACL"].as(false); if (m_pingTime == 0U) { m_pingTime = 5U; @@ -372,13 +372,20 @@ bool HostFNE::readParams() LogWarning(LOG_HOST, "It is *not* recommended to disable the \"allowActivityTransfer\" option."); } + if (g_promiscuousHub) { + LogWarning(LOG_HOST, "This FNE was started as a promiscuous hub! In this mode, the FNE will not apply any TG or RID ACL rules and will transparently pass *all* traffic."); + LogWarning(LOG_HOST, "Promiscuous Hub is intended as a loop breaking measure *only*!"); + } + LogInfo("General Parameters"); + if (g_promiscuousHub) + LogInfo(" !! Promiscuous Hub: yes"); LogInfo(" Peer Ping Time: %us", m_pingTime); LogInfo(" Maximum Missed Pings: %u", m_maxMissedPings); LogInfo(" ACL Rule Update Time: %u mins", m_updateLookupTime); LogInfo(" Send Talkgroups: %s", sendTalkgroups ? "yes" : "no"); - LogInfo(" Peer Link ACL is retained: %s", m_peerLinkSavesACL ? "yes" : "no"); + LogInfo(" Peer Replication ACL is retained: %s", m_peerReplicaSavesACL ? "yes" : "no"); if (m_useAlternatePortForDiagnostics) LogInfo(" Use Alternate Port for Diagnostics: yes"); @@ -533,6 +540,9 @@ bool HostFNE::initializeRESTAPI() bool HostFNE::createMasterNetwork() { + yaml::Node systemConf = m_conf["system"]; + std::string identity = systemConf["identity"].as("CHANGEME"); + yaml::Node masterConf = m_conf["master"]; std::string address = masterConf["address"].as(); uint16_t port = (uint16_t)masterConf["port"].as(TRAFFIC_DEFAULT_PORT); @@ -540,6 +550,7 @@ bool HostFNE::createMasterNetwork() std::string password = masterConf["password"].as(); bool verbose = masterConf["verbose"].as(false); bool debug = masterConf["debug"].as(false); + bool kmfDebug = masterConf["kmfDebug"].as(false); uint16_t workerCnt = (uint16_t)masterConf["workers"].as(16U); // clamp worker thread count properly @@ -579,7 +590,7 @@ bool HostFNE::createMasterNetwork() } } else { - LogWarning(LOG_HOST, "Invalid master network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); + LogWarning(LOG_HOST, "Invalid master network preshared encryption key length, key should be 32 hex pairs, or 64 characters. Encryption disabled."); encrypted = false; } } @@ -602,6 +613,7 @@ bool HostFNE::createMasterNetwork() bool parrotGrantDemand = masterConf["parrotGrantDemand"].as(true); LogInfo("Network Parameters"); + LogInfo(" Identity: %s", identity.c_str()); LogInfo(" Peer ID: %u", id); LogInfo(" Address: %s", address.c_str()); LogInfo(" Port: %u", port); @@ -626,9 +638,15 @@ bool HostFNE::createMasterNetwork() LogInfo(" Debug: yes"); } + if (kmfDebug) { + LogInfo(" P25 OTAR KMF Services Debug: yes"); + } + // initialize networking - m_network = new FNENetwork(this, address, port, id, password, debug, verbose, reportPeerPing, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, - parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, m_pingTime, m_updateLookupTime, workerCnt); + m_network = new FNENetwork(this, address, port, id, password, identity, debug, kmfDebug, verbose, reportPeerPing, + m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, + parrotDelay, parrotGrantDemand, m_allowActivityTransfer, m_allowDiagnosticTransfer, + m_pingTime, m_updateLookupTime, workerCnt); m_network->setOptions(masterConf, true); m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup, m_adjSiteMapLookup); @@ -694,7 +712,7 @@ void* HostFNE::threadMasterNetwork(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -714,7 +732,7 @@ void* HostFNE::threadMasterNetwork(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -750,7 +768,7 @@ void* HostFNE::threadDiagNetwork(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -770,7 +788,7 @@ void* HostFNE::threadDiagNetwork(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -781,6 +799,12 @@ void* HostFNE::threadDiagNetwork(void* arg) bool HostFNE::createPeerNetworks() { + yaml::Node systemConf = m_conf["system"]; + std::string identity = systemConf["identity"].as("CHANGEME"); + + yaml::Node masterConf = m_conf["master"]; + uint32_t masterPeerId = masterConf["peerId"].as(1001U); + yaml::Node& peerList = m_conf["peers"]; if (peerList.size() > 0U) { if (peerList.size() > MAX_RECOMMENDED_PEER_NETWORKS) { @@ -829,14 +853,17 @@ bool HostFNE::createPeerNetworks() } } - std::string identity = peerConf["identity"].as(); - uint32_t rxFrequency = peerConf["rxFrequency"].as(0U); - uint32_t txFrequency = peerConf["txFrequency"].as(0U); float latitude = peerConf["latitude"].as(0.0F); float longitude = peerConf["longitude"].as(0.0F); std::string location = peerConf["location"].as(); - ::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Identity %s Enabled %u Encrypted %u", id, masterAddress.c_str(), masterPort, identity.c_str(), enabled, encrypted); + ::LogInfoEx(LOG_HOST, "Peer ID %u Master Address %s Master Port %u Enabled %u Encrypted %u", id, masterAddress.c_str(), masterPort, enabled, encrypted); + + std::string identOverride = peerConf["identity"].as(); + if (identOverride != "") { + identity = identOverride; + ::LogInfoEx(LOG_HOST, "Peer ID %u overrides global identity, Identity %s", id, identity.c_str()); + } if (id > 999999999U) { ::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999."); @@ -846,33 +873,30 @@ bool HostFNE::createPeerNetworks() // initialize networking network::PeerNetwork* network = new PeerNetwork(masterAddress, masterPort, 0U, id, password, true, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, m_analogEnabled, true, true, m_allowActivityTransfer, m_allowDiagnosticTransfer, false, false); - network->setMetadata(identity, rxFrequency, txFrequency, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); + network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); network->setLookups(m_ridLookup, m_tidLookup); + network->setMasterPeerId(masterPeerId); network->setPeerLookups(m_peerListLookup); - network->setPeerLinkSaveACL(m_peerLinkSavesACL); + network->setPeerReplicationSaveACL(m_peerReplicaSavesACL); if (encrypted) { network->setPresharedKey(presharedKey); } network->setDMRCallback(std::bind(&HostFNE::processPeerDMR, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slot, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processPeerDMRInCallCtrl(command, dstId, slot, peerId, ssrc, streamId); }); network->setP25Callback(std::bind(&HostFNE::processPeerP25, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processPeerP25InCallCtrl(command, dstId, peerId, ssrc, streamId); }); network->setNXDNCallback(std::bind(&HostFNE::processPeerNXDN, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setNXDNICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processPeerNXDNInCallCtrl(command, dstId, peerId, ssrc, streamId); }); network->setAnalogCallback(std::bind(&HostFNE::processPeerAnalog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + network->setAnalogICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processPeerAnalogInCallCtrl(command, dstId, peerId, ssrc, streamId); }); - /* - ** Block Traffic To Peers - */ - - yaml::Node& blockTrafficTo = peerConf["blockTrafficTo"]; - if (blockTrafficTo.size() > 0U) { - for (size_t i = 0; i < blockTrafficTo.size(); i++) { - uint32_t peerId = (uint32_t)::strtoul(blockTrafficTo[i].as("0").c_str(), NULL, 10); - if (peerId != 0U) { - ::LogInfoEx(LOG_HOST, "Peer ID %u Blocks Traffic To PEER %u", id, peerId); - network->addBlockedTrafficPeer(peerId); - } - } - } + network->setNetTreeDiscCallback(std::bind(&HostFNE::processNetworkTreeDisconnect, this, std::placeholders::_1, std::placeholders::_2)); + network->setNotifyPeerReplicaCallback(std::bind(&HostFNE::processPeerReplicaNotify, this, std::placeholders::_1)); network->enable(enabled); if (enabled) { @@ -961,7 +985,7 @@ void* HostFNE::threadVirtualNetworking(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -995,7 +1019,7 @@ void* HostFNE::threadVirtualNetworking(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -1025,6 +1049,21 @@ void HostFNE::processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* d } } +/* Helper to process an DMR In-Call Control message. */ + +void HostFNE::processPeerDMRInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slot, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, dstId, slot, peerId, ssrc, streamId); + break; + + default: + break; + } +} + /* Processes P25 peer network traffic. */ void HostFNE::processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, @@ -1047,6 +1086,21 @@ void HostFNE::processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* d } } +/* Helper to process an P25 In-Call Control message. */ + +void HostFNE::processPeerP25InCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, dstId, 0U, peerId, ssrc, streamId); + break; + + default: + break; + } +} + /* Processes NXDN peer network traffic. */ void HostFNE::processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, @@ -1069,6 +1123,21 @@ void HostFNE::processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* } } +/* Helper to process an NXDN In-Call Control message. */ + +void HostFNE::processPeerNXDNInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, dstId, 0U, peerId, ssrc, streamId); + break; + + default: + break; + } +} + /* Processes analog peer network traffic. */ void HostFNE::processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, @@ -1090,3 +1159,36 @@ void HostFNE::processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t m_network->analogTrafficHandler()->processFrame(data, length, peerId, rtpHeader.getSSRC(), rtpHeader.getSequence(), streamId, true); } } + +/* Helper to process an analog In-Call Control message. */ + +void HostFNE::processPeerAnalogInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, dstId, 0U, peerId, ssrc, streamId); + break; + + default: + break; + } +} + +/* Processes network tree disconnect notification. */ + +void HostFNE::processNetworkTreeDisconnect(network::PeerNetwork* peerNetwork, const uint32_t offendingPeerId) +{ + if (m_network != nullptr) { + m_network->processNetworkTreeDisconnect(peerNetwork->getPeerId(), offendingPeerId); + } +} + +/* Processes network tree disconnect notification. */ + +void HostFNE::processPeerReplicaNotify(network::PeerNetwork* peerNetwork) +{ + if (m_network != nullptr) { + m_network->setPeerReplica(true); + } +} diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index 4c76c47e6..9537ed0a0 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -27,7 +27,7 @@ #include "network/FNENetwork.h" #include "network/DiagNetwork.h" #include "network/PeerNetwork.h" -#include "network/RESTAPI.h" +#include "restapi/RESTAPI.h" #include "CryptoContainer.h" #include @@ -59,8 +59,8 @@ class HOST_SW_API HostFNE { * @brief Virtual Network Packet Data Digital Mode */ enum PacketDataMode { - DMR, //! Digital Mobile Radio - PROJECT25 //! Project 25 + DMR, //!< Digital Mobile Radio + PROJECT25 //!< Project 25 }; /** @@ -118,7 +118,7 @@ class HOST_SW_API HostFNE { uint32_t m_maxMissedPings; uint32_t m_updateLookupTime; - bool m_peerLinkSavesACL; + bool m_peerReplicaSavesACL; bool m_useAlternatePortForDiagnostics; @@ -184,6 +184,17 @@ class HOST_SW_API HostFNE { */ void processPeerDMR(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Helper to process an DMR In-Call Control message. + * @param command In-Call Control Command. + * @param dstId Destination ID. + * @param slot Slot Number. + * @param peerId Peer ID. + * @param ssrc Synchronization Source. + * @param streamId Stream ID. + */ + void processPeerDMRInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slot, + uint32_t peerId, uint32_t ssrc, uint32_t streamId); /** * @brief Processes P25 peer network traffic. * @param data Buffer containing P25 data. @@ -194,6 +205,16 @@ class HOST_SW_API HostFNE { */ void processPeerP25(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Helper to process an P25 In-Call Control message. + * @param command In-Call Control Command. + * @param dstId Destination ID. + * @param peerId Peer ID. + * @param ssrc Synchronization Source. + * @param streamId Stream ID. + */ + void processPeerP25InCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId); /** * @brief Processes NXDN peer network traffic. * @param data Buffer containing NXDN data. @@ -204,6 +225,16 @@ class HOST_SW_API HostFNE { */ void processPeerNXDN(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Helper to process an NXDN In-Call Control message. + * @param command In-Call Control Command. + * @param dstId Destination ID. + * @param peerId Peer ID. + * @param ssrc Synchronization Source. + * @param streamId Stream ID. + */ + void processPeerNXDNInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId); /** * @brief Processes analog peer network traffic. * @param data Buffer containing analog data. @@ -214,6 +245,28 @@ class HOST_SW_API HostFNE { */ void processPeerAnalog(network::PeerNetwork* peerNetwork, const uint8_t* data, uint32_t length, uint32_t streamId, const network::frame::RTPFNEHeader& fneHeader, const network::frame::RTPHeader& rtpHeader); + /** + * @brief Helper to process an analog In-Call Control message. + * @param command In-Call Control Command. + * @param dstId Destination ID. + * @param peerId Peer ID. + * @param ssrc Synchronization Source. + * @param streamId Stream ID. + */ + void processPeerAnalogInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Processes network tree disconnect notification. + * @param peerNetwork Peer network instance. + * @param offendingPeerId Offending peer ID. + */ + void processNetworkTreeDisconnect(network::PeerNetwork* peerNetwork, const uint32_t offendingPeerId); + /** + * @brief Processes peer replica notification. + * @param peerNetwork Peer network instance. + */ + void processPeerReplicaNotify(network::PeerNetwork* peerNetwork); }; #endif // __HOST_FNE_H__ diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index fb5e260ee..73f0eb013 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -33,6 +33,9 @@ DiagNetwork::DiagNetwork(HostFNE* host, FNENetwork* fneNetwork, const std::strin m_host(host), m_address(address), m_port(port), + m_status(NET_STAT_INVALID), + m_peerReplicaActPkt(), + m_peerTreeListPkt(), m_threadPool(workerCnt, "diag") { assert(fneNetwork != nullptr); @@ -76,6 +79,7 @@ void DiagNetwork::processNetwork() NetPacketRequest* req = new NetPacketRequest(); req->obj = m_fneNetwork; + req->diagObj = this; req->peerId = peerId; req->address = address; @@ -113,7 +117,7 @@ void DiagNetwork::clock(uint32_t ms) bool DiagNetwork::open() { if (m_debug) - LogMessage(LOG_NET, "Opening Network"); + LogInfoEx(LOG_DIAG, "Opening Network"); m_threadPool.start(); @@ -129,6 +133,8 @@ bool DiagNetwork::open() bool ret = m_socket->open(); if (!ret) { + m_socket->recvBufSize(524288U); // 512K recv buffer + m_socket->sendBufSize(524288U); // 512K send buffer m_status = NET_STAT_INVALID; } @@ -140,7 +146,7 @@ bool DiagNetwork::open() void DiagNetwork::close() { if (m_debug) - LogMessage(LOG_NET, "Closing Network"); + LogInfoEx(LOG_DIAG, "Closing Network"); m_threadPool.stop(); m_threadPool.wait(); @@ -170,6 +176,17 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) return; } + DiagNetwork* diagNetwork = static_cast(req->diagObj); + if (diagNetwork == nullptr) { + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + + return; + } + if (req == nullptr) return; @@ -189,11 +206,11 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) pktPeerId = peerId; } else { if (peerId > 0) { - // this could be a peer-link transfer -- in which case, we need to check the SSRC of the packet not the peer ID + // this could be a replica transfer -- in which case, we need to check the SSRC of the packet not the peer ID if (network->m_peers.find(req->rtpHeader.getSSRC()) != network->m_peers.end()) { FNEPeerConnection* connection = network->m_peers[req->rtpHeader.getSSRC()]; if (connection != nullptr) { - if (connection->isExternalPeer() && connection->isPeerLink()) { + if (connection->isNeighborFNEPeer() && connection->isReplica()) { validPeerId = true; pktPeerId = req->rtpHeader.getSSRC(); } @@ -218,7 +235,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); std::string payload(rawPayload, rawPayload + (req->length - 11U)); - ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identity().c_str(), payload.c_str()); + ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identWithQualifier().c_str(), payload.c_str()); // report activity log to InfluxDB if (network->m_enableInfluxDB) { @@ -248,13 +265,13 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } } - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); } } } @@ -285,7 +302,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) bool currState = g_disableTimeDisplay; g_disableTimeDisplay = true; - ::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); + ::Log(9999U, {nullptr, nullptr, 0U, nullptr}, "%.9u (%8s) %s", peerId, connection->identWithQualifier().c_str(), payload.c_str()); g_disableTimeDisplay = currState; // report diagnostic log to InfluxDB @@ -326,7 +343,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) uint32_t addrLen = peer.second->sockStorageLen(); if (network->m_debug) { - LogDebug(LOG_NET, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", + LogDebug(LOG_DIAG, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", pktPeerId, peer.first, req->length); } network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, @@ -337,13 +354,13 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } } - // attempt to repeat status traffic to Peer-Link masters + // attempt to repeat status traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, true, pktPeerId); } } } @@ -366,33 +383,34 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } break; - case NET_FUNC::PEER_LINK: - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PL_ACT_PEER_LIST) { // Peer-Link Active Peer List + case NET_FUNC::REPL: + if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_ACT_PEER_LIST) { // Peer Replication Active Peer List if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; if (connection != nullptr) { std::string ip = udp::Socket::address(req->address); // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && connection->isExternalPeer() && - connection->isPeerLink()) { + if (connection->connected() && connection->address() == ip && connection->isNeighborFNEPeer() && + connection->isReplica()) { DECLARE_UINT8_ARRAY(rawPayload, req->length); ::memcpy(rawPayload, req->buffer, req->length); - // Utils::dump(1U, "DiagNetwork::taskNetworkRx(), PEER_LINK, Raw Payload", rawPayload, req->length); + // Utils::dump(1U, "DiagNetwork::taskNetworkRx(), REPL_ACT_PEER_LIST, Raw Payload", rawPayload, req->length); - if (network->m_peerLinkActPkt.find(peerId) == network->m_peerLinkActPkt.end()) { - network->m_peerLinkActPkt.insert(peerId, FNENetwork::PacketBufferEntry()); + if (diagNetwork->m_peerReplicaActPkt.find(peerId) == diagNetwork->m_peerReplicaActPkt.end()) { + diagNetwork->m_peerReplicaActPkt.insert(peerId, DiagNetwork::PacketBufferEntry()); - FNENetwork::PacketBufferEntry& pkt = network->m_peerLinkActPkt[peerId]; - pkt.buffer = new PacketBuffer(true, "Peer-Link, Active Peer List"); + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerReplicaActPkt[peerId]; + pkt.buffer = new PacketBuffer(true, "Peer Replication, Active Peer List"); pkt.streamId = streamId; pkt.locked = false; } else { - FNENetwork::PacketBufferEntry& pkt = network->m_peerLinkActPkt[peerId]; + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerReplicaActPkt[peerId]; if (!pkt.locked && pkt.streamId != streamId) { - LogError(LOG_NET, "PEER %u Peer-Link, Active Peer List, stream ID mismatch, expected %u, got %u", peerId, pkt.streamId, streamId); + LogError(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, stream ID mismatch, expected %u, got %u", peerId, + connection->identWithQualifier().c_str(), pkt.streamId, streamId); pkt.buffer->clear(); pkt.streamId = streamId; } @@ -403,7 +421,7 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) } } - FNENetwork::PacketBufferEntry& pkt = network->m_peerLinkActPkt[peerId]; + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerReplicaActPkt[peerId]; if (pkt.locked) { while (pkt.locked) Thread::sleep(1U); @@ -415,38 +433,244 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) uint8_t* decompressed = nullptr; if (pkt.buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + diagNetwork->m_peerReplicaActPkt.lock(); std::string payload(decompressed + 8U, decompressed + decompressedLen); // parse JSON body json::value v; std::string err = json::parse(v, payload); if (!err.empty()) { - LogError(LOG_NET, "PEER %u error parsing active peer list, %s", peerId, err.c_str()); + LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); pkt.buffer->clear(); pkt.streamId = 0U; if (decompressed != nullptr) { delete[] decompressed; } - network->m_peerLinkActPkt.erase(peerId); + diagNetwork->m_peerReplicaActPkt.unlock(); + diagNetwork->m_peerReplicaActPkt.erase(peerId); break; } else { // ensure parsed JSON is an array if (!v.is()) { - LogError(LOG_NET, "PEER %u error parsing active peer list, data was not valid", peerId); + LogError(LOG_REPL, "PEER %u (%s) error parsing active peer list, data was not valid", peerId, connection->identWithQualifier().c_str()); pkt.buffer->clear(); delete pkt.buffer; pkt.streamId = 0U; if (decompressed != nullptr) { delete[] decompressed; } - network->m_peerLinkActPkt.erase(peerId); + diagNetwork->m_peerReplicaActPkt.unlock(); + diagNetwork->m_peerReplicaActPkt.erase(peerId); break; } else { json::array arr = v.get(); - LogInfoEx(LOG_NET, "PEER %u Peer-Link, Active Peer List, updating %u peer entries", peerId, arr.size()); - network->m_peerLinkPeers[peerId] = arr; + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, Active Peer List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); + network->m_peerReplicaPeers[peerId] = arr; + } + } + + pkt.buffer->clear(); + delete pkt.buffer; + pkt.streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + diagNetwork->m_peerReplicaActPkt.unlock(); + diagNetwork->m_peerReplicaActPkt.erase(peerId); + } else { + pkt.locked = false; + } + } + else { + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_HA_PARAMS) { // Peer Replication HA Parameters + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && connection->isNeighborFNEPeer() && + connection->isReplica()) { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + std::vector receivedParams; + + uint32_t len = GET_UINT32(rawPayload, 0U); + if (len > 0U) { + len /= HA_PARAMS_ENTRY_LEN; + } + + uint8_t offs = 4U; + for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { + uint32_t peerId = GET_UINT32(rawPayload, offs); + uint32_t ipAddr = GET_UINT32(rawPayload, offs + 4U); + uint16_t port = GET_UINT16(rawPayload, offs + 8U); + receivedParams.push_back(HAParameters(peerId, ipAddr, port)); + } + + if (receivedParams.size() > 0U) { + for (auto rxEntry : receivedParams) { + auto it = std::find_if(network->m_peerReplicaHAParams.begin(), network->m_peerReplicaHAParams.end(), + [&](HAParameters& x) + { + if (x.peerId == rxEntry.peerId) + return true; + return false; + }); + if (it != network->m_peerReplicaHAParams.end()) { + it->masterIP = rxEntry.masterIP; + it->masterPort = rxEntry.masterPort; + } else { + HAParameters param = rxEntry; + network->m_peerReplicaHAParams.push_back(param); + } + + if (network->m_debug) { + std::string address = __IP_FROM_UINT(rxEntry.masterIP); + LogDebugEx(LOG_REPL, "DiagNetwork::taskNetworkRx", "PEER %u (%s) Peer Replication, HA Parameters, %s:%u", peerId, connection->identWithQualifier().c_str(), + address.c_str(), rxEntry.masterPort); + } + } + + if (receivedParams.size() > 0) { + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, HA Parameters, updating %u entries, %u entries", peerId, connection->identWithQualifier().c_str(), receivedParams.size(), + network->m_peerReplicaHAParams.size()); + + // send peer updates to replica peers + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + std::vector haParams; + network->m_peerReplicaHAParams.lock(false); + for (auto entry : network->m_peerReplicaHAParams) { + haParams.push_back(entry); + } + network->m_peerReplicaHAParams.unlock(); + + peer.second->writeHAParams(haParams); + } + } + } + } + } + } + } else { + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_FUNC::NET_TREE: + if (!network->m_enableSpanningTree) + break; + if (req->fneHeader.getSubFunction() == NET_SUBFUNC::NET_TREE_LIST) { // FNE Network Tree List + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && connection->isNeighborFNEPeer()) { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "DiagNetwork::taskNetworkRx(), NET_TREE_LIST, Raw Payload", rawPayload, req->length); + + if (diagNetwork->m_peerTreeListPkt.find(peerId) == diagNetwork->m_peerTreeListPkt.end()) { + diagNetwork->m_peerTreeListPkt.insert(peerId, DiagNetwork::PacketBufferEntry()); + + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerTreeListPkt[peerId]; + pkt.buffer = new PacketBuffer(true, "Network Tree, Tree List"); + pkt.streamId = streamId; + + pkt.locked = false; + } else { + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerTreeListPkt[peerId]; + if (!pkt.locked && pkt.streamId != streamId) { + LogError(LOG_STP, "PEER %u (%s) Network Tree, Tree List, stream ID mismatch, expected %u, got %u", peerId, + connection->identWithQualifier().c_str(), pkt.streamId, streamId); + pkt.buffer->clear(); + pkt.streamId = streamId; + } + + if (pkt.streamId != streamId) { + // otherwise drop the packet + break; + } + } + + DiagNetwork::PacketBufferEntry& pkt = diagNetwork->m_peerTreeListPkt[peerId]; + if (pkt.locked) { + while (pkt.locked) + Thread::sleep(1U); + } + + pkt.locked = true; + + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (pkt.buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + diagNetwork->m_peerTreeListPkt.lock(); + std::string payload(decompressed + 8U, decompressed + decompressedLen); + + // parse JSON body + json::value v; + std::string err = json::parse(v, payload); + if (!err.empty()) { + LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, %s", peerId, connection->identWithQualifier().c_str(), err.c_str()); + pkt.buffer->clear(); + pkt.streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + diagNetwork->m_peerTreeListPkt.unlock(); + diagNetwork->m_peerTreeListPkt.erase(peerId); + break; + } + else { + // ensure parsed JSON is an array + if (!v.is()) { + LogError(LOG_STP, "PEER %u (%s) error parsing network tree list, data was not valid", peerId, connection->identWithQualifier().c_str()); + pkt.buffer->clear(); + delete pkt.buffer; + pkt.streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + diagNetwork->m_peerTreeListPkt.unlock(); + diagNetwork->m_peerTreeListPkt.erase(peerId); + break; + } + else { + json::array arr = v.get(); + LogInfoEx(LOG_STP, "PEER %u (%s) Network Tree, Tree List, updating %u peer entries", peerId, connection->identWithQualifier().c_str(), arr.size()); + + std::lock_guard guard(network->m_treeLock); + + std::vector duplicatePeers; + SpanningTree::deserializeTree(arr, network->m_treeRoot, &duplicatePeers); + + network->logSpanningTree(connection); + + if (duplicatePeers.size() > 0U) { + for (auto dupPeerId : duplicatePeers) { + LogWarning(LOG_STP, "PEER %u (%s) Network Tree, Tree Change, disconnecting duplicate peer connection for PEER %u to prevent network loop", + peerId, connection->identWithQualifier().c_str(), dupPeerId); + network->writeTreeDisconnect(peerId, dupPeerId); + } + } } } @@ -456,13 +680,14 @@ void DiagNetwork::taskNetworkRx(NetPacketRequest* req) if (decompressed != nullptr) { delete[] decompressed; } - network->m_peerLinkActPkt.erase(peerId); + diagNetwork->m_peerTreeListPkt.unlock(); + diagNetwork->m_peerTreeListPkt.erase(peerId); } else { pkt.locked = false; } } else { - network->writePeerNAK(peerId, 0U, TAG_PEER_LINK, NET_CONN_NAK_FNE_UNAUTHORIZED); + network->writePeerNAK(peerId, 0U, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); } } } diff --git a/src/fne/network/DiagNetwork.h b/src/fne/network/DiagNetwork.h index 2961ede8a..0faff1187 100644 --- a/src/fne/network/DiagNetwork.h +++ b/src/fne/network/DiagNetwork.h @@ -99,6 +99,26 @@ namespace network NET_CONN_STATUS m_status; + /** + * @brief Represents a packet buffer entry in a map. + */ + class PacketBufferEntry { + public: + /** + * @brief Stream ID of the packet. + */ + uint32_t streamId; + + /** + * @brief Packet fragment buffer. + */ + PacketBuffer* buffer; + + bool locked; + }; + concurrent::unordered_map m_peerReplicaActPkt; + concurrent::unordered_map m_peerTreeListPkt; + ThreadPool m_threadPool; /** diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 7f04225c2..d6bb9ed45 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -9,18 +9,21 @@ */ #include "fne/Defines.h" #include "common/edac/SHA256.h" -#include "common/network/json/json.h" #include "common/p25/kmm/KMMFactory.h" +#include "common/json/json.h" #include "common/zlib/Compression.h" #include "common/Log.h" +#include "common/StopWatch.h" #include "common/Utils.h" #include "network/FNENetwork.h" #include "network/callhandler/TagDMRData.h" #include "network/callhandler/TagP25Data.h" #include "network/callhandler/TagNXDNData.h" #include "network/callhandler/TagAnalogData.h" +#include "network/P25OTARService.h" #include "fne/ActivityLog.h" #include "HostFNE.h" +#include "FNEMain.h" using namespace network; using namespace network::callhandler; @@ -28,6 +31,7 @@ using namespace compress; #include #include +#include #include #include #include @@ -44,11 +48,13 @@ const uint32_t MAX_MISSED_ACL_UPDATES = 10U; const uint64_t PACKET_LATE_TIME = 200U; // 200ms +const uint32_t FIXED_HA_UPDATE_INTERVAL = 30U; // 30s + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- -std::timed_mutex FNENetwork::m_keyQueueMutex; +std::timed_mutex FNENetwork::s_keyQueueMutex; // --------------------------------------------------------------------------- // Public Class Members @@ -57,17 +63,21 @@ std::timed_mutex FNENetwork::m_keyQueueMutex; /* Initializes a new instance of the FNENetwork class. */ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, - bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt) : + std::string identity, bool debug, bool kmfDebug, bool verbose, bool reportPeerPing, + bool dmr, bool p25, bool nxdn, bool analog, + uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, + uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt) : BaseNetwork(peerId, true, debug, true, true, allowActivityTransfer, allowDiagnosticTransfer), m_tagDMR(nullptr), m_tagP25(nullptr), m_tagNXDN(nullptr), m_tagAnalog(nullptr), + m_p25OTARService(nullptr), m_host(host), m_address(address), m_port(port), m_password(password), + m_isReplica(false), m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), @@ -76,6 +86,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_parrotDelayTimer(1000U, 0U, parrotDelay), m_parrotGrantDemand(parrotGrantDemand), m_parrotOnlyOriginating(false), + m_kmfServicesEnabled(false), m_ridLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), @@ -83,22 +94,32 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_cryptoLookup(nullptr), m_status(NET_STAT_INVALID), m_peers(), - m_peerLinkPeers(), + m_peerReplicaPeers(), m_peerAffiliations(), m_ccPeerMap(), - m_peerLinkKeyQueue(), - m_peerLinkActPkt(), + m_peerReplicaKeyQueue(), + m_treeRoot(nullptr), + m_treeLock(), + m_peerReplicaHAParams(), + m_advertisedHAAddress(), + m_advertisedHAPort(TRAFFIC_DEFAULT_PORT), + m_haEnabled(false), m_maintainenceTimer(1000U, pingTime), m_updateLookupTimer(1000U, (updateLookupTime * 60U)), + m_haUpdateTimer(1000U, FIXED_HA_UPDATE_INTERVAL), m_softConnLimit(0U), - m_callInProgress(false), + m_enableSpanningTree(true), + m_logSpanningTreeChanges(false), + m_spanningTreeFastReconnect(true), + m_callCollisionTimeout(5U), m_disallowAdjStsBcast(false), m_disallowExtAdjStsBcast(true), m_allowConvSiteAffOverride(false), m_disallowCallTerm(false), m_restrictGrantToAffOnly(false), m_restrictPVCallToRegOnly(false), - m_enableInCallCtrl(true), + m_enableRIDInCallCtrl(true), + m_disallowInCallCtrl(false), m_rejectUnknownRID(false), m_maskOutboundPeerID(false), m_maskOutboundPeerIDForNonPL(false), @@ -118,6 +139,7 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_dumpPacketData(false), m_verbosePacketData(false), m_logDenials(false), + m_logUpstreamCallStartEnd(true), m_reportPeerPing(reportPeerPing), m_verbose(verbose) { @@ -126,16 +148,39 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, assert(port > 0U); assert(!password.empty()); + m_peers.reserve(MAX_HARD_CONN_CAP); + m_peerReplicaPeers.reserve(MAX_HARD_CONN_CAP); + m_peerAffiliations.reserve(MAX_HARD_CONN_CAP); + m_ccPeerMap.reserve(MAX_HARD_CONN_CAP); + m_tagDMR = new TagDMRData(this, debug); m_tagP25 = new TagP25Data(this, debug); m_tagNXDN = new TagNXDNData(this, debug); m_tagAnalog = new TagAnalogData(this, debug); + + m_p25OTARService = new P25OTARService(this, m_tagP25->packetData(), kmfDebug, verbose); + + SpanningTree::s_maxUpdatesBeforeReparent = (uint8_t)host->m_maxMissedPings; + m_treeRoot = new SpanningTree(peerId, peerId, nullptr); + m_treeRoot->identity(identity); + + /* + ** Initialize Threads + */ + + Thread::runAsThread(this, threadParrotHandler); } /* Finalizes a instance of the FNENetwork class. */ FNENetwork::~FNENetwork() { + if (m_kmfServicesEnabled) { + m_p25OTARService->close(); + } + + delete m_p25OTARService; + delete m_tagDMR; delete m_tagP25; delete m_tagNXDN; @@ -149,7 +194,8 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_disallowAdjStsBcast = conf["disallowAdjStsBcast"].as(false); m_disallowExtAdjStsBcast = conf["disallowExtAdjStsBcast"].as(true); m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as(true); - m_enableInCallCtrl = conf["enableInCallCtrl"].as(false); + m_enableRIDInCallCtrl = conf["enableRIDInCallCtrl"].as(false); + m_disallowInCallCtrl = conf["disallowInCallCtrl"].as(false); m_rejectUnknownRID = conf["rejectUnknownRID"].as(false); m_maskOutboundPeerID = conf["maskOutboundPeerID"].as(false); m_maskOutboundPeerIDForNonPL = conf["maskOutboundPeerIDForNonPeerLink"].as(false); @@ -160,7 +206,16 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_softConnLimit = MAX_HARD_CONN_CAP; } - // always force disable ADJ_STS_BCAST to external peers if the all option + m_enableSpanningTree = conf["enableSpanningTree"].as(true); + + if (!m_enableSpanningTree) { + LogWarning(LOG_MASTER, "WARNING: Disabling the peer spanning tree is not recommended! This can cause network loops and other issues in a multi-peer FNE network."); + } + + m_logSpanningTreeChanges = conf["logSpanningTreeChanges"].as(false); + m_spanningTreeFastReconnect = conf["spanningTreeFastReconnect"].as(true); + + // always force disable ADJ_STS_BCAST to neighbor FNE peers if the all option // is enabled if (m_disallowAdjStsBcast) { m_disallowExtAdjStsBcast = true; @@ -178,6 +233,24 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) } m_parrotOnlyOriginating = conf["parrotOnlyToOrginiatingPeer"].as(false); + +#if defined(ENABLE_SSL) + m_kmfServicesEnabled = conf["kmfServicesEnabled"].as(false); + uint16_t kmfOtarPort = conf["kmfOtarPort"].as(64414U); + if (m_kmfServicesEnabled) { + if (!m_p25OTARService->open(m_address, kmfOtarPort)) { + m_kmfServicesEnabled = false; + LogError(LOG_MASTER, "FNE OTAR KMF services failed to start, OTAR service disabled."); + } + } +#else + uint16_t kmfOtarPort = 64414U; // hardcoded + m_kmfServicesEnabled = false; + LogWarning(LOG_MASTER, "FNE is compiled without OpenSSL support, KMF services are unavailable."); +#endif // ENABLE_SSL + + m_callCollisionTimeout = conf["callCollisionTimeout"].as(5U); + m_restrictGrantToAffOnly = conf["restrictGrantToAffiliatedOnly"].as(false); m_restrictPVCallToRegOnly = conf["restrictPrivateCallToRegOnly"].as(false); m_filterTerminators = conf["filterTerminators"].as(true); @@ -187,6 +260,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_verbosePacketData = conf["verbosePacketData"].as(false); m_logDenials = conf["logDenials"].as(false); + m_logUpstreamCallStartEnd = conf["logUpstreamCallStartEnd"].as(true); /* ** Drop Unit to Unit Peers @@ -204,24 +278,44 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) } } + yaml::Node& haParams = conf["ha"]; + m_advertisedHAAddress = haParams["advertisedWANAddress"].as(); + m_advertisedHAPort = (uint16_t)haParams["advertisedWANPort"].as(TRAFFIC_DEFAULT_PORT); + m_haEnabled = haParams["enable"].as(false); + + if (m_haEnabled) { + uint32_t ipAddr = __IP_FROM_STR(m_advertisedHAAddress); + HAParameters params = HAParameters(m_peerId, ipAddr, m_advertisedHAPort); + m_peerReplicaHAParams.push_back(params); + } + if (printOptions) { LogInfo(" Maximum Permitted Connections: %u", m_softConnLimit); + LogInfo(" Enable Peer Spanning Tree: %s", m_enableSpanningTree ? "yes" : "no"); + LogInfo(" Log Spanning Tree Changes: %s", m_logSpanningTreeChanges ? "yes" : "no"); + LogInfo(" Spanning Tree Allow Fast Reconnect: %s", m_spanningTreeFastReconnect ? "yes" : "no"); LogInfo(" Disable adjacent site broadcasts to any peers: %s", m_disallowAdjStsBcast ? "yes" : "no"); if (m_disallowAdjStsBcast) { - LogWarning(LOG_NET, "NOTICE: All P25 ADJ_STS_BCAST messages will be blocked and dropped!"); + LogWarning(LOG_MASTER, "NOTICE: All P25 ADJ_STS_BCAST messages will be blocked and dropped!"); } LogInfo(" Disable Packet Data: %s", m_disablePacketData ? "yes" : "no"); LogInfo(" Dump Packet Data: %s", m_dumpPacketData ? "yes" : "no"); - LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no"); + LogInfo(" Disable P25 ADJ_STS_BCAST to neighbor peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no"); LogInfo(" Disable P25 TDULC call termination broadcasts to any peers: %s", m_disallowCallTerm ? "yes" : "no"); LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); - LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); + LogInfo(" Enable RID In-Call Control: %s", m_enableRIDInCallCtrl ? "yes" : "no"); + LogInfo(" Disallow In-Call Control Requests: %s", m_disallowInCallCtrl ? "yes" : "no"); LogInfo(" Reject Unknown RIDs: %s", m_rejectUnknownRID ? "yes" : "no"); LogInfo(" Log Traffic Denials: %s", m_logDenials ? "yes" : "no"); + LogInfo(" Log Upstream Call Start/End Events: %s", m_logUpstreamCallStartEnd ? "yes" : "no"); LogInfo(" Mask Outbound Traffic Peer ID: %s", m_maskOutboundPeerID ? "yes" : "no"); if (m_maskOutboundPeerIDForNonPL) { LogInfo(" Mask Outbound Traffic Peer ID for Non-Peer Link: yes"); } + LogInfo(" Call Collision Timeout: %us", m_callCollisionTimeout); + if (m_callCollisionTimeout == 0U) { + LogWarning(LOG_MASTER, "Call Collisions are disabled because the call collision timeout is set to 0 seconds. This is not recommended, and can cause undesired behavior."); + } LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Restrict private call to registered units: %s", m_restrictPVCallToRegOnly ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); @@ -235,6 +329,14 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" InfluxDB Log Raw TSBK/CSBK/RCCH: %s", m_influxLogRawData ? "yes" : "no"); } LogInfo(" Parrot Repeat to Only Originating Peer: %s", m_parrotOnlyOriginating ? "yes" : "no"); + LogInfo(" P25 OTAR KMF Services Enabled: %s", m_kmfServicesEnabled ? "yes" : "no"); + LogInfo(" P25 OTAR KMF Listening Address: %s", m_address.c_str()); + LogInfo(" P25 OTAR KMF Listening Port: %u", kmfOtarPort); + LogInfo(" High Availability Enabled: %s", m_haEnabled ? "yes" : "no"); + if (m_haEnabled) { + LogInfo(" Advertised HA WAN IP: %s", m_advertisedHAAddress.c_str()); + LogInfo(" Advertised HA WAN Port: %u", m_advertisedHAPort); + } } } @@ -281,6 +383,7 @@ void FNENetwork::processNetwork() NetPacketRequest* req = new NetPacketRequest(); req->obj = this; + req->diagObj = m_host->m_diagNetwork; req->peerId = peerId; req->address = address; @@ -307,6 +410,59 @@ void FNENetwork::processNetwork() } } +/* Process network tree disconnect notification. */ + +void FNENetwork::processNetworkTreeDisconnect(uint32_t peerId, uint32_t offendingPeerId) +{ + if (m_status != NET_STAT_MST_RUNNING) { + return; + } + + if (!m_enableSpanningTree) { + LogWarning(LOG_STP, "FNENetwork::processNetworkTreeDisconnect(), ignoring disconnect request for PEER %u, spanning tree is disabled", offendingPeerId); + return; + } + + if (offendingPeerId > 0 && (m_peers.find(offendingPeerId) != m_peers.end())) { + FNEPeerConnection* connection = m_peers[offendingPeerId]; + if (connection != nullptr) { + LogWarning(LOG_STP, "PEER %u (%s) NAK, server already connected via upstream master, duplicate connection dropped, connectionState = %u", offendingPeerId, connection->identWithQualifier().c_str(), + connection->connectionState()); + writePeerNAK(offendingPeerId, createStreamId(), TAG_REPEATER_CONFIG, NET_CONN_NAK_FNE_DUPLICATE_CONN); + disconnectPeer(offendingPeerId, connection); + logSpanningTree(); + } else { + LogError(LOG_STP, "Network Tree Disconnect, upstream master requested disconnect for PEER %u, but connection is null", offendingPeerId); + } + } else { + // is this perhaps a peer connection of ours? + if (m_host->m_peerNetworks.size() > 0) { + for (auto peer : m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->getPeerId() == peerId) { + LogWarning(LOG_STP, "PEER %u, upstream master requested disconnect for our peer connection, duplicate connection dropped", peerId); + peer.second->close(); + return; + } + } + } + } + + LogError(LOG_STP, "Network Tree Disconnect, upstream master requested disconnect for unknown PEER %u", offendingPeerId); + } +} + +/* Helper to process an downstream peer In-Call Control message. */ + +void FNENetwork::processDownstreamInCallCtrl(network::NET_ICC::ENUM command, network::NET_SUBFUNC::ENUM subFunc, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + if (m_disallowInCallCtrl) + return; + + processInCallCtrl(command, subFunc, dstId, slotNo, peerId, ssrc, streamId); +} + /* Updates the timer by the passed number of milliseconds. */ void FNENetwork::clock(uint32_t ms) @@ -319,7 +475,7 @@ void FNENetwork::clock(uint32_t ms) if (m_forceListUpdate) { for (auto peer : m_peers) { - peerACLUpdate(peer.first); + peerMetadataUpdate(peer.first); } m_forceListUpdate = false; } @@ -328,57 +484,51 @@ void FNENetwork::clock(uint32_t ms) if (m_maintainenceTimer.isRunning() && m_maintainenceTimer.hasExpired()) { // check to see if any peers have been quiet (no ping) longer than allowed std::vector peersToRemove = std::vector(); + m_peers.shared_lock(); for (auto peer : m_peers) { uint32_t id = peer.first; FNEPeerConnection* connection = peer.second; if (connection != nullptr) { uint64_t dt = 0U; - if (connection->isExternalPeer() || connection->isPeerLink()) + if (connection->isNeighborFNEPeer() || connection->isReplica()) dt = connection->lastPing() + ((m_host->m_pingTime * 1000) * (m_host->m_maxMissedPings * 2U)); else dt = connection->lastPing() + ((m_host->m_pingTime * 1000) * m_host->m_maxMissedPings); if (dt < now) { - LogInfoEx(LOG_NET, "PEER %u (%s) timed out, dt = %u, now = %u", id, connection->identity().c_str(), + LogInfoEx(LOG_MASTER, "PEER %u (%s) timed out, dt = %u, now = %u", id, connection->identWithQualifier().c_str(), dt, now); // set connection states for this stale connection connection->connected(false); connection->connectionState(NET_STAT_INVALID); - // if the connection was an external peer or a peer link -- be noisy about a possible - // netsplit - if (connection->isExternalPeer() || connection->isPeerLink()) { - for (uint8_t i = 0U; i < 3U; i++) - LogWarning(LOG_NET, "PEER %u (%s) downstream netsplit, dt = %u, now = %u", id, connection->identity().c_str(), - dt, now); - } - peersToRemove.push_back(id); } } } + m_peers.shared_unlock(); // remove any peers for (uint32_t peerId : peersToRemove) { FNEPeerConnection* connection = m_peers[peerId]; - erasePeer(peerId); - if (connection != nullptr) { - delete connection; - } - } - - // roll the RTP timestamp if no call is in progress - if (!m_callInProgress) { - frame::RTPHeader::resetStartTime(); - m_frameQueue->clearTimestamps(); + disconnectPeer(peerId, connection); } - // send active peer list to Peer-Link masters + // send peer updates to neighbor FNE peers if (m_host->m_peerNetworks.size() > 0) { for (auto peer : m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + // perform master tree maintainence tasks + if (peer.second->isEnabled() && peer.second->getRemotePeerId() > 0U && + m_enableSpanningTree) { + std::lock_guard guard(m_treeLock); + peer.second->writeSpanningTree(m_treeRoot); + } + + // perform peer replica maintainence tasks + if (peer.second->isEnabled() && peer.second->getRemotePeerId() > 0U && + peer.second->isReplica()) { if (!peer.second->getAttachedKeyRSPHandler()) { peer.second->setAttachedKeyRSPHandler(true); // this is the only place this should happen peer.second->setKeyResponseCallback([=](p25::kmm::KeyItem ki, uint8_t algId, uint8_t keyLength) { @@ -388,16 +538,18 @@ void FNENetwork::clock(uint32_t ms) if (m_peers.size() > 0) { json::array peers = json::array(); + m_peers.shared_lock(); for (auto entry : m_peers) { uint32_t peerId = entry.first; - network::FNEPeerConnection* peerConn = entry.second; - if (peerConn != nullptr) { - json::object peerObj = fneConnObject(peerId, peerConn); + network::FNEPeerConnection* connection = entry.second; + if (connection != nullptr) { + json::object peerObj = fneConnObject(peerId, connection); uint32_t peerNetPeerId = peer.second->getPeerId(); peerObj["parentPeerId"].set(peerNetPeerId); peers.push_back(json::value(peerObj)); } } + m_peers.shared_unlock(); peer.second->writePeerLinkPeers(&peers); } @@ -406,73 +558,78 @@ void FNENetwork::clock(uint32_t ms) } } + // cleanup possibly stale data calls + m_tagDMR->packetData()->cleanupStale(); + m_tagP25->packetData()->cleanupStale(); + m_maintainenceTimer.start(); } m_updateLookupTimer.clock(ms); if (m_updateLookupTimer.isRunning() && m_updateLookupTimer.hasExpired()) { - // send ACL updates to peers - m_peers.lock(false); + // send network metadata updates to peers + m_peers.shared_lock(); for (auto peer : m_peers) { uint32_t id = peer.first; FNEPeerConnection* connection = peer.second; if (connection != nullptr) { - // if this connection is a peer link *always* send the update -- no stream checking - if (connection->connected() && connection->isPeerLink()) { - LogInfoEx(LOG_NET, "PEER %u (%s), Peer-Link, updating ACL list", id, connection->identity().c_str()); + // if this connection is a peer replica *always* send the update -- no stream checking + if (connection->connected() && connection->isReplica()) { + LogInfoEx(LOG_MASTER, "PEER %u (%s), Peer Replication, updating network metadata", id, connection->identWithQualifier().c_str()); - peerACLUpdate(id); - connection->missedACLUpdates(0U); + peerMetadataUpdate(id); + connection->missedMetadataUpdates(0U); continue; } if (connection->connected()) { - if ((connection->streamCount() <= 1) || (connection->missedACLUpdates() > MAX_MISSED_ACL_UPDATES)) { - LogInfoEx(LOG_NET, "PEER %u (%s) updating ACL list", id, connection->identity().c_str()); - peerACLUpdate(id); - connection->missedACLUpdates(0U); + if ((connection->streamCount() <= 1) || (connection->missedMetadataUpdates() > MAX_MISSED_ACL_UPDATES)) { + LogInfoEx(LOG_MASTER, "PEER %u (%s) updating ACL list", id, connection->identWithQualifier().c_str()); + peerMetadataUpdate(id); + connection->missedMetadataUpdates(0U); } else { - uint32_t missed = connection->missedACLUpdates(); + uint32_t missed = connection->missedMetadataUpdates(); missed++; - LogInfoEx(LOG_NET, "PEER %u (%s) skipped for ACL update, traffic in progress", id, connection->identity().c_str()); - connection->missedACLUpdates(missed); + LogInfoEx(LOG_MASTER, "PEER %u (%s) skipped for metadata update, traffic in progress", id, connection->identWithQualifier().c_str()); + connection->missedMetadataUpdates(missed); } } } } - m_peers.unlock(); + m_peers.shared_unlock(); m_updateLookupTimer.start(); } - m_parrotDelayTimer.clock(ms); - if (m_parrotDelayTimer.isRunning() && m_parrotDelayTimer.hasExpired()) { - // if the DMR handler has parrot frames to playback, playback a frame - if (m_tagDMR->hasParrotFrames()) { - m_tagDMR->playbackParrot(); - } - - // if the P25 handler has parrot frames to playback, playback a frame - if (m_tagP25->hasParrotFrames()) { - m_tagP25->playbackParrot(); - } + // if HA is enabled perform HA parameter updates + if (m_haEnabled) { + m_haUpdateTimer.clock(ms); + if (m_haUpdateTimer.isRunning() && m_haUpdateTimer.hasExpired()) { + // send peer updates to replica peers + if (m_host->m_peerNetworks.size() > 0) { + for (auto peer : m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isReplica()) { + std::vector haParams; + m_peerReplicaHAParams.lock(false); + for (auto entry : m_peerReplicaHAParams) { + haParams.push_back(entry); + } + m_peerReplicaHAParams.unlock(); - // if the NXDN handler has parrot frames to playback, playback a frame - if (m_tagNXDN->hasParrotFrames()) { - m_tagNXDN->playbackParrot(); - } + peer.second->writeHAParams(haParams); + } + } + } + } - // if the analog handler has parrot frames to playback, playback a frame - if (m_tagAnalog->hasParrotFrames()) { - m_tagAnalog->playbackParrot(); + m_haUpdateTimer.start(); } } - if (!m_tagDMR->hasParrotFrames() && !m_tagP25->hasParrotFrames() && !m_tagNXDN->hasParrotFrames() && !m_tagAnalog->hasParrotFrames() && - m_parrotDelayTimer.isRunning() && m_parrotDelayTimer.hasExpired()) { - m_parrotDelayTimer.stop(); - } + if (m_kmfServicesEnabled) + m_p25OTARService->clock(ms); } /* Opens connection to the network. */ @@ -480,7 +637,7 @@ void FNENetwork::clock(uint32_t ms) bool FNENetwork::open() { if (m_debug) - LogMessage(LOG_NET, "Opening Network"); + LogInfoEx(LOG_MASTER, "Opening Network"); // start thread pool m_threadPool.start(); @@ -493,6 +650,10 @@ bool FNENetwork::open() m_status = NET_STAT_MST_RUNNING; m_maintainenceTimer.start(); m_updateLookupTimer.start(); + + if (m_haEnabled) { + m_haUpdateTimer.start(); + } m_socket = new udp::Socket(m_address, m_port); @@ -504,6 +665,8 @@ bool FNENetwork::open() bool ret = m_socket->open(); if (!ret) { + m_socket->recvBufSize(524288U); // 512K recv buffer + m_socket->sendBufSize(524288U); // 512K send buffer m_status = NET_STAT_INVALID; } @@ -515,7 +678,7 @@ bool FNENetwork::open() void FNENetwork::close() { if (m_debug) - LogMessage(LOG_NET, "Closing Network"); + LogInfoEx(LOG_MASTER, "Closing Network"); if (m_status == NET_STAT_MST_RUNNING) { uint8_t buffer[1U]; @@ -524,7 +687,7 @@ void FNENetwork::close() uint32_t streamId = createStreamId(); for (auto peer : m_peers) { writePeer(peer.first, m_peerId, { NET_FUNC::MST_DISC, NET_SUBFUNC::NOP }, buffer, 1U, RTP_END_OF_CALL_SEQ, - streamId, false); + streamId); } } @@ -550,6 +713,116 @@ void FNENetwork::close() // Private Class Members // --------------------------------------------------------------------------- +/* Entry point to parrot handler thread. */ + +void* FNENetwork::threadParrotHandler(void* arg) +{ + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("fne:parrot"); + FNENetwork* fne = static_cast(th->obj); + if (fne == nullptr) { + g_killed = true; + LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); + } + + if (g_killed) { + delete th; + return nullptr; + } + + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + StopWatch stopWatch; + stopWatch.start(); + + if (fne != nullptr) { + while (!g_killed) { + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + fne->m_parrotDelayTimer.clock(ms); + if (fne->m_parrotDelayTimer.isRunning() && fne->m_parrotDelayTimer.hasExpired()) { + // if the DMR handler has parrot frames to playback, playback a frame + if (fne->m_tagDMR->hasParrotFrames()) { + fne->m_tagDMR->playbackParrot(); + } + + // if the P25 handler has parrot frames to playback, playback a frame + if (fne->m_tagP25->hasParrotFrames()) { + fne->m_tagP25->playbackParrot(); + } + + // if the NXDN handler has parrot frames to playback, playback a frame + if (fne->m_tagNXDN->hasParrotFrames()) { + fne->m_tagNXDN->playbackParrot(); + } + + // if the analog handler has parrot frames to playback, playback a frame + if (fne->m_tagAnalog->hasParrotFrames()) { + fne->m_tagAnalog->playbackParrot(); + } + } + + if (!fne->m_tagDMR->hasParrotFrames() && !fne->m_tagP25->hasParrotFrames() && !fne->m_tagNXDN->hasParrotFrames() && !fne->m_tagAnalog->hasParrotFrames() && + fne->m_parrotDelayTimer.isRunning() && fne->m_parrotDelayTimer.hasExpired()) { + fne->m_parrotDelayTimer.stop(); + } + + if (!fne->m_parrotDelayTimer.isRunning()) { + // if the DMR handle is marked as playing back parrot frames, but has no more frames in the queue + // clear the playback flag + if (fne->m_tagDMR->isParrotPlayback() && !fne->m_tagDMR->hasParrotFrames()) { + LogInfoEx(LOG_MASTER, "DMR, Parrot Call End, peer = %u, srcId = %u, dstId = %u", + fne->m_tagDMR->lastParrotPeerId(), fne->m_tagDMR->lastParrotSrcId(), fne->m_tagDMR->lastParrotDstId()); + fne->m_tagDMR->clearParrotPlayback(); + } + + // if the P25 handle is marked as playing back parrot frames, but has no more frames in the queue + // clear the playback flag + if (fne->m_tagP25->isParrotPlayback() && !fne->m_tagP25->hasParrotFrames()) { + LogInfoEx(LOG_MASTER, "P25, Parrot Call End, peer = %u, srcId = %u, dstId = %u", + fne->m_tagP25->lastParrotPeerId(), fne->m_tagP25->lastParrotSrcId(), fne->m_tagP25->lastParrotDstId()); + fne->m_tagP25->clearParrotPlayback(); + } + + // if the NXDN handle is marked as playing back parrot frames, but has no more frames in the queue + // clear the playback flag + if (fne->m_tagNXDN->isParrotPlayback() && !fne->m_tagNXDN->hasParrotFrames()) { + LogInfoEx(LOG_MASTER, "NXDN, Parrot Call End, peer = %u, srcId = %u, dstId = %u", + fne->m_tagNXDN->lastParrotPeerId(), fne->m_tagNXDN->lastParrotSrcId(), fne->m_tagNXDN->lastParrotDstId()); + fne->m_tagNXDN->clearParrotPlayback(); + } + + // if the analog handle is marked as playing back parrot frames, but has no more frames in the queue + // clear the playback flag + if (fne->m_tagAnalog->isParrotPlayback() && !fne->m_tagAnalog->hasParrotFrames()) { + LogInfoEx(LOG_MASTER, "Analog, Parrot Call End, peer = %u, srcId = %u, dstId = %u", + fne->m_tagAnalog->lastParrotPeerId(), fne->m_tagAnalog->lastParrotSrcId(), fne->m_tagAnalog->lastParrotDstId()); + fne->m_tagAnalog->clearParrotPlayback(); + } + } + + Thread::sleep(1U); + } + } + + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + /* Process a data frames from the network. */ void FNENetwork::taskNetworkRx(NetPacketRequest* req) @@ -580,32 +853,26 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) uint64_t dt = req->pktRxTime + PACKET_LATE_TIME; if (dt < now) { std::string peerIdentity = network->resolvePeerIdentity(peerId); - LogWarning(LOG_NET, "PEER %u (%s) packet processing latency >200ms, dt = %u, now = %u", peerId, peerIdentity.c_str(), + LogWarning(LOG_MASTER, "PEER %u (%s) packet processing latency >200ms, dt = %u, now = %u", peerId, peerIdentity.c_str(), dt, now); } // update current peer packet sequence and stream ID - if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end()) && streamId != 0U) { + if (peerId > 0U && (network->m_peers.find(peerId) != network->m_peers.end()) && streamId != 0U) { FNEPeerConnection* connection = network->m_peers[peerId]; uint16_t pktSeq = req->rtpHeader.getSequence(); if (connection != nullptr) { - if (pktSeq == RTP_END_OF_CALL_SEQ) { - // only reset packet sequences if we're a PROTOCOL or RPTC function - if ((req->fneHeader.getFunction() == NET_FUNC::PROTOCOL) || - (req->fneHeader.getFunction() == NET_FUNC::RPTC)) { - connection->eraseStreamPktSeq(streamId); // attempt to erase packet sequence for the stream - } - } else { - if (connection->hasStreamPktSeq(streamId)) { - uint16_t currPkt = connection->getStreamPktSeq(streamId); - if ((pktSeq != currPkt) && (pktSeq != (RTP_END_OF_CALL_SEQ - 1U)) && pktSeq != 0U) { - LogWarning(LOG_NET, "PEER %u (%s) stream %u out-of-sequence; %u != %u", peerId, connection->identity().c_str(), - streamId, pktSeq, currPkt); - } - } + uint16_t lastRxSeq = 0U; - connection->incStreamPktSeq(streamId, pktSeq + 1U); + MULTIPLEX_RET_CODE ret = connection->verifyStream(streamId, pktSeq, req->fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_MASTER, "PEER %u (%s) stream %u possible lost frames; got %u, expected %u", peerId, connection->identWithQualifier().c_str(), + streamId, pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_MASTER, "PEER %u (%s) stream %u out-of-order; got %u, expected >%u", peerId, connection->identWithQualifier().c_str(), + streamId, pktSeq, lastRxSeq); } } @@ -615,7 +882,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // if we don't have a stream ID and are receiving call data -- throw an error and discard if (streamId == 0U && req->fneHeader.getFunction() == NET_FUNC::PROTOCOL) { std::string peerIdentity = network->resolvePeerIdentity(peerId); - LogError(LOG_NET, "PEER %u (%s) malformed packet (no stream ID for a call?)", peerId, peerIdentity.c_str()); + LogError(LOG_MASTER, "PEER %u (%s) malformed packet (no stream ID for a call?)", peerId, peerIdentity.c_str()); if (req->buffer != nullptr) delete[] req->buffer; @@ -741,17 +1008,17 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } break; - case NET_FUNC::RPTL: // Repeater Login + case NET_FUNC::RPTL: // Repeater/Peer Login { if (peerId > 0 && (network->m_peers.find(peerId) == network->m_peers.end())) { if (network->m_peers.size() >= MAX_HARD_CONN_CAP) { - LogError(LOG_NET, "PEER %u attempted to connect with no more connections available, currConnections = %u", peerId, network->m_peers.size()); + LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, currConnections = %u", peerId, network->m_peers.size()); network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); break; } if (network->m_softConnLimit > 0U && network->m_peers.size() >= network->m_softConnLimit) { - LogError(LOG_NET, "PEER %u attempted to connect with no more connections available, maxConnections = %u, currConnections = %u", peerId, network->m_softConnLimit, network->m_peers.size()); + LogError(LOG_MASTER, "PEER %u attempted to connect with no more connections available, maxConnections = %u, currConnections = %u", peerId, network->m_softConnLimit, network->m_peers.size()); network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_FNE_MAX_CONN, req->address, req->addrLen); break; } @@ -764,16 +1031,14 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // check if the peer is in the peer ACL list if (network->m_peerListLookup->getACL()) { if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); } if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "PEER %u RPTL, failed peer ACL check", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } } } @@ -784,7 +1049,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) FNEPeerConnection* connection = network->m_peers[peerId]; if (connection != nullptr) { if (connection->connectionState() == NET_STAT_RUNNING) { - LogMessage(LOG_NET, "PEER %u (%s) resetting peer connection, connectionState = %u", peerId, connection->identity().c_str(), + LogInfoEx(LOG_MASTER, "PEER %u (%s) resetting peer connection, connectionState = %u", peerId, connection->identWithQualifier().c_str(), connection->connectionState()); delete connection; @@ -797,38 +1062,33 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // check if the peer is in the peer ACL list if (network->m_peerListLookup->getACL()) { if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); } if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "PEER %u RPTL, failed peer ACL check", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTL, failed peer ACL check", peerId); network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } } } else { network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - LogWarning(LOG_NET, "PEER %u (%s) RPTL NAK, bad connection state, connectionState = %u", peerId, connection->identity().c_str(), + LogWarning(LOG_MASTER, "PEER %u (%s) RPTL NAK, bad connection state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), connection->connectionState()); - - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } } else { network->writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->erasePeer(peerId); - LogWarning(LOG_NET, "PEER %u RPTL NAK, having no connection", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTL NAK, having no connection", peerId); } } } } break; - case NET_FUNC::RPTK: // Repeater Authentication + case NET_FUNC::RPTK: // Repeater/Peer Authentication { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -851,7 +1111,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) bool validAcl = true; if (network->m_peerListLookup->getACL()) { if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "PEER %u RPTK, failed peer ACL check", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); validAcl = false; } else { lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); @@ -866,7 +1126,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } if (network->m_peerListLookup->isPeerListEmpty()) { - LogWarning(LOG_NET, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); validAcl = true; } } @@ -899,37 +1159,34 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (validHash) { connection->connectionState(NET_STAT_WAITING_CONFIG); network->writePeerACK(peerId, streamId); - LogInfoEx(LOG_NET, "PEER %u RPTK ACK, completed the login exchange", peerId); + LogInfoEx(LOG_MASTER, "PEER %u RPTK ACK, completed the login exchange", peerId); network->m_peers[peerId] = connection; } else { - LogWarning(LOG_NET, "PEER %u RPTK NAK, failed the login exchange", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, failed the login exchange", peerId); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - network->erasePeer(peerId); + network->disconnectPeer(peerId, connection); } } else { network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_PEER_ACL, req->address, req->addrLen); - network->erasePeer(peerId); + network->disconnectPeer(peerId, connection); } } else { - LogWarning(LOG_NET, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->connectionState()); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } } } else { network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->erasePeer(peerId); - LogWarning(LOG_NET, "PEER %u RPTK NAK, having no connection", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTK NAK, having no connection", peerId); } } break; - case NET_FUNC::RPTC: // Repeater Configuration + case NET_FUNC::RPTC: // Repeater/Peer Configuration { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -945,18 +1202,16 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) json::value v; std::string err = json::parse(v, payload); if (!err.empty()) { - LogWarning(LOG_NET, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } else { // ensure parsed JSON is an object if (!v.is()) { - LogWarning(LOG_NET, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, supplied invalid configuration data", peerId); network->writePeerNAK(peerId, TAG_REPEATER_AUTH, NET_CONN_NAK_INVALID_CONFIG_DATA, req->address, req->addrLen); - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } else { connection->config(v.get()); @@ -964,7 +1219,16 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) connection->connected(true); connection->pingsReceived(0U); connection->lastPing(now); - connection->missedACLUpdates(0U); + connection->missedMetadataUpdates(0U); + + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (!peerEntry.peerDefault()) { + if (peerEntry.hasCallPriority()) { + connection->hasCallPriority(peerEntry.hasCallPriority()); + LogInfoEx(LOG_MASTER, "PEER %u >> Has Call Priority", peerId); + } + } + network->m_peers[peerId] = connection; // attach extra notification data to the RPTC ACK to notify the peer of @@ -975,89 +1239,142 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) buffer[0U] = 0x80U; } - network->writePeerACK(peerId, streamId, buffer, 1U); - LogInfoEx(LOG_NET, "PEER %u RPTC ACK, completed the configuration exchange", peerId); - json::object peerConfig = connection->config(); + + std::string identity = "* UNK *"; if (peerConfig["identity"].is()) { - std::string identity = peerConfig["identity"].get(); + identity = peerConfig["identity"].getDefault("* UNK *"); connection->identity(identity); - LogInfoEx(LOG_NET, "PEER %u reports identity [%8s]", peerId, identity.c_str()); + LogInfoEx(LOG_MASTER, "PEER %u >> Identity [%8s]", peerId, identity.c_str()); } - // is the peer reporting it is an external peer? + if (peerConfig["software"].is()) { + std::string software = peerConfig["software"].get(); + LogInfoEx(LOG_MASTER, "PEER %u >> Software Version [%s]", peerId, software.c_str()); + } + + // is the peer reporting it is a SysView peer? + if (peerConfig["sysView"].is()) { + bool sysView = peerConfig["sysView"].get(); + connection->isSysView(sysView); + if (sysView) + LogInfoEx(LOG_MASTER, "PEER %u >> SysView Peer", peerId); + } + + // is the peer reporting it is an downstream FNE neighbor peer? + /* + ** bryanb: don't change externalPeer to neighborPeer -- this will break backward + ** compat with older FNE versions (we're stuck with this naming :() + */ if (peerConfig["externalPeer"].is()) { - bool external = peerConfig["externalPeer"].get(); - connection->isExternalPeer(external); - if (external) - LogInfoEx(LOG_NET, "PEER %u reports external peer", peerId); + bool neighbor = peerConfig["externalPeer"].get(); + connection->isNeighborFNEPeer(neighbor); + if (neighbor) + LogInfoEx(LOG_MASTER, "PEER %u >> Downstream Neighbor FNE Peer", peerId); + + uint32_t masterPeerId = 0U; + if (peerConfig["masterPeerId"].is()) { + masterPeerId = peerConfig["masterPeerId"].get(); + connection->masterId(masterPeerId); + LogInfoEx(LOG_MASTER, "PEER %u >> Master Peer ID [%u]", peerId, masterPeerId); + } - // check if the peer is participating in peer link + // master peer ID should never be zero for an neighbor peer -- use the peer ID instead + if (masterPeerId == 0U) { + LogWarning(LOG_MASTER, "PEER %u reports to be a downstream FNE neighbor peer but has not supplied a valid masterPeerId, using own peerId as masterPeerId (old FNE perhaps?)", peerId); + masterPeerId = peerId; + } + + // check if the peer a peer replication participant lookups::PeerId peerEntry = network->m_peerListLookup->find(req->peerId); if (!peerEntry.peerDefault()) { - if (peerEntry.peerLink()) { + if (peerEntry.peerReplica()) { if (network->m_host->m_useAlternatePortForDiagnostics) { - connection->isPeerLink(true); - if (external) - LogInfoEx(LOG_NET, "PEER %u configured for Peer-Link", peerId); + connection->isReplica(true); + if (neighbor) + LogInfoEx(LOG_MASTER, "PEER %u >> Participates in Peer Replication", peerId); } else { - LogError(LOG_NET, "PEER %u, Peer-Link operations *require* the alternate diagnostics port option to be enabled.", peerId); - LogError(LOG_NET, "PEER %u, will not receive Peer-Link ACL updates.", peerId); + LogError(LOG_MASTER, "PEER %u, Peer replication operations *require* the alternate diagnostics port option to be enabled.", peerId); + LogError(LOG_MASTER, "PEER %u, will not receive peer replication ACL updates.", peerId); } } } + + if (network->m_enableSpanningTree && !connection->isSysView()) { + network->m_treeLock.lock(); + + // check if this peer is already connected via another peer + SpanningTree* tree = SpanningTree::findByMasterID(masterPeerId); + if (tree != nullptr) { + // are we allowing a fast reconnect? (this happens when a connecting peer + // uses the same peer ID and master ID already announced in the tree, but + // the tree entry wasn't yet erased) + if ((tree->id() == peerId && tree->masterId() == masterPeerId) && + network->m_spanningTreeFastReconnect) { + LogWarning(LOG_STP, "PEER %u (%s) server already announced in server tree, fast peer reconnect, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, masterPeerId, tree->id(), tree->masterId(), connection->connectionState()); + if (identity != tree->identity()) { + LogWarning(LOG_STP, "PEER %u (%s) why has this server's announced identity changed? *big hmmmm*", peerId, connection->identWithQualifier().c_str()); + } + SpanningTree::moveParent(tree, network->m_treeRoot); + network->logSpanningTree(connection); + } else { + LogWarning(LOG_STP, "PEER %u (%s) RPTC NAK, server already connected via PEER %u, duplicate connection denied, peerId = %u, masterId = %u, treePeerId = %u, treeMasterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, masterPeerId, tree->id(), tree->masterId(), tree->id(), connection->connectionState()); + network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_FNE_DUPLICATE_CONN, req->address, req->addrLen); + network->m_treeLock.unlock(); + network->disconnectPeer(peerId, connection); + break; + } + } else { + SpanningTree* node = new SpanningTree(peerId, masterPeerId, network->m_treeRoot); + node->identity(identity); + network->logSpanningTree(connection); + } + + network->m_treeLock.unlock(); + } } + network->writePeerACK(peerId, streamId, buffer, 1U); + LogInfoEx(LOG_MASTER, "PEER %u RPTC ACK, completed the configuration exchange", peerId); + // is the peer reporting it is a conventional peer? if (peerConfig["conventionalPeer"].is()) { if (network->m_allowConvSiteAffOverride) { bool convPeer = peerConfig["conventionalPeer"].get(); connection->isConventionalPeer(convPeer); if (convPeer) - LogInfoEx(LOG_NET, "PEER %u reports conventional peer", peerId); + LogInfoEx(LOG_MASTER, "PEER %u >> Conventional Peer", peerId); } } - // is the peer reporting it is a SysView peer? - if (peerConfig["sysView"].is()) { - bool sysView = peerConfig["sysView"].get(); - connection->isSysView(sysView); - if (sysView) - LogInfoEx(LOG_NET, "PEER %u reports SysView peer", peerId); - } - - if (peerConfig["software"].is()) { - std::string software = peerConfig["software"].get(); - LogInfoEx(LOG_NET, "PEER %u reports software %s", peerId, software.c_str()); - } - // setup the affiliations list for this peer std::stringstream peerName; peerName << "PEER " << peerId; network->createPeerAffiliations(peerId, peerName.str()); - // spin up a thread and send ACL list over to peer - network->peerACLUpdate(peerId); + // spin up a thread and send metadata over to peer + network->peerMetadataUpdate(peerId); } } } else { - LogWarning(LOG_NET, "PEER %u (%s) RPTC NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->identity().c_str(), + LogWarning(LOG_MASTER, "PEER %u (%s) RPTC NAK, login exchange while in an incorrect state, connectionState = %u", peerId, connection->identWithQualifier().c_str(), connection->connectionState()); network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - network->erasePeer(peerId); - delete connection; + network->disconnectPeer(peerId, connection); } } } else { network->writePeerNAK(peerId, TAG_REPEATER_CONFIG, NET_CONN_NAK_BAD_CONN_STATE, req->address, req->addrLen); - LogWarning(LOG_NET, "PEER %u RPTC NAK, having no connection", peerId); + LogWarning(LOG_MASTER, "PEER %u RPTC NAK, having no connection", peerId); } } break; - case NET_FUNC::RPT_DISC: // Repeater Disconnect + case NET_FUNC::RPT_DISC: // Repeater/Peer Disconnect { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -1066,15 +1383,14 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip) { - LogInfoEx(LOG_NET, "PEER %u (%s) disconnected", peerId, connection->identity().c_str()); - network->erasePeer(peerId); - delete connection; + LogInfoEx(LOG_MASTER, "PEER %u (%s) disconnected", peerId, connection->identWithQualifier().c_str()); + network->disconnectPeer(peerId, connection); } } } } break; - case NET_FUNC::PING: // Repeater Ping + case NET_FUNC::PING: // Ping { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -1107,9 +1423,28 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) network->writePeerCommand(peerId, { NET_FUNC::PONG, NET_SUBFUNC::NOP }, payload, 8U, streamId, false); if (network->m_reportPeerPing) { - LogInfoEx(LOG_NET, "PEER %u (%s) ping, pingsReceived = %u, lastPing = %u, now = %u", peerId, connection->identity().c_str(), + LogInfoEx(LOG_MASTER, "PEER %u (%s) ping, pingsReceived = %u, lastPing = %u, now = %u", peerId, connection->identWithQualifier().c_str(), connection->pingsReceived(), lastPing, now); } + + // ensure STP sanity, when we receive a ping from a downstream leaf + // this check ensures a STP entry for a downstream leaf isn't accidentally blown off + // the tree during a fast reconnect + if (network->m_enableSpanningTree && connection->isNeighborFNEPeer() && !connection->isSysView()) { + std::lock_guard guard(network->m_treeLock); + + if ((connection->masterId() != peerId) && (connection->masterId() != 0U)) { + // check if this peer is already connected via another peer + SpanningTree* tree = SpanningTree::findByMasterID(connection->masterId()); + if (tree == nullptr) { + LogWarning(LOG_STP, "PEER %u (%s) downstream server not announced in server tree, reinitializing STP entry, this is abnormal, peerId = %u, masterId = %u, connectionState = %u", peerId, connection->identWithQualifier().c_str(), + peerId, connection->masterId(), connection->connectionState()); + SpanningTree* node = new SpanningTree(peerId, connection->masterId(), network->m_treeRoot); + node->identity(connection->identity()); + network->logSpanningTree(connection); + } + } + } } else { network->writePeerNAK(peerId, streamId, TAG_REPEATER_PING); @@ -1119,7 +1454,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } break; - case NET_FUNC::GRANT_REQ: // Repeater Grant Request + case NET_FUNC::GRANT_REQ: // Grant Request { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -1180,8 +1515,27 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) case NET_FUNC::INCALL_CTRL: // In-Call Control { - // FNEs are god-like entities, and we don't recognize the authority of foreign FNEs telling us what - // to do... + if (network->m_disallowInCallCtrl) + break; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + NET_ICC::ENUM command = (NET_ICC::ENUM)req->buffer[10U]; + uint32_t dstId = GET_UINT24(req->buffer, 11U); + uint8_t slot = req->buffer[14U]; + + network->processInCallCtrl(command, req->fneHeader.getSubFunction(), dstId, slot, peerId, ssrc, streamId); + } + else { + network->writePeerNAK(peerId, streamId, TAG_INCALL_CTRL, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } } break; @@ -1204,7 +1558,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) break; } else { if (!peerEntry.canRequestKeys()) { - LogError(LOG_NET, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) requested enc. key but is not allowed, no response", peerId, connection->identWithQualifier().c_str()); break; } } @@ -1212,7 +1566,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) std::unique_ptr frame = KMMFactory::create(req->buffer + 11U); if (frame == nullptr) { - LogWarning(LOG_NET, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identity().c_str()); + LogWarning(LOG_MASTER, "PEER %u (%s), undecodable KMM frame from peer", peerId, connection->identWithQualifier().c_str()); break; } @@ -1221,9 +1575,9 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) { KMMModifyKey* modifyKey = static_cast(frame.get()); if (modifyKey->getAlgId() > 0U && modifyKey->getKId() > 0U) { - LogMessage(LOG_NET, "PEER %u (%s) requested enc. key, algId = $%02X, kID = $%04X", peerId, connection->identity().c_str(), + LogInfoEx(LOG_MASTER, "PEER %u (%s) requested enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), modifyKey->getAlgId(), modifyKey->getKId()); - ::KeyItem keyItem = network->m_cryptoLookup->find(modifyKey->getKId()); + ::EKCKeyItem keyItem = network->m_cryptoLookup->find(modifyKey->getKId()); if (!keyItem.isInvalid()) { uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); @@ -1234,7 +1588,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) Utils::dump(1U, "FNENetwork::taskNetworkRx(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); } - LogMessage(LOG_NET, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identity().c_str(), + LogInfoEx(LOG_MASTER, "PEER %u (%s) local enc. key, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), modifyKey->getAlgId(), modifyKey->getKId()); // build response buffer @@ -1263,24 +1617,24 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) modifyKeyRsp.encode(buffer + 11U); network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, - RTP_END_OF_CALL_SEQ, network->createStreamId(), false, false, true); + RTP_END_OF_CALL_SEQ, network->createStreamId()); } else { - // attempt to forward KMM key request to Peer-Link masters + // attempt to forward KMM key request to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - LogMessage(LOG_NET, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, kID = $%04X", peerId, connection->identity().c_str(), + if (peer.second->isEnabled() && peer.second->isReplica()) { + LogInfoEx(LOG_PEER, "PEER %u (%s) no local key or container, requesting key from upstream master, algId = $%02X, kID = $%04X", peerId, connection->identWithQualifier().c_str(), modifyKey->getAlgId(), modifyKey->getKId()); - bool locked = network->m_keyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); - network->m_peerLinkKeyQueue[peerId] = modifyKey->getKId(); + bool locked = network->s_keyQueueMutex.try_lock_for(std::chrono::milliseconds(60)); + network->m_peerReplicaKeyQueue[peerId] = modifyKey->getKId(); if (locked) - network->m_keyQueueMutex.unlock(); + network->s_keyQueueMutex.unlock(); peer.second->writeMaster({ NET_FUNC::KEY_REQ, NET_SUBFUNC::NOP }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, false); + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false); } } } @@ -1318,7 +1672,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) std::string ip = udp::Socket::address(req->address); lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } @@ -1329,13 +1683,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) aff->groupUnaff(srcId); aff->groupAff(srcId, dstId); - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); } } } @@ -1357,7 +1711,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) std::string ip = udp::Socket::address(req->address); fne_lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } @@ -1366,13 +1720,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->unitReg(srcId, ssrc); - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false, 0U, ssrc); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, 0U, ssrc); } } } @@ -1393,7 +1747,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) std::string ip = udp::Socket::address(req->address); lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } @@ -1402,13 +1756,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->unitDereg(srcId); - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); } } } @@ -1430,7 +1784,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) std::string ip = udp::Socket::address(req->address); lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } @@ -1439,13 +1793,13 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) uint32_t srcId = GET_UINT24(req->buffer, 0U); // Source Address aff->groupUnaff(srcId); - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); } } } @@ -1470,7 +1824,7 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) if (connection->connected() && connection->address() == ip) { lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identWithQualifier().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } @@ -1487,15 +1841,15 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) aff->groupAff(srcId, dstId); offs += 8U; } - LogMessage(LOG_NET, "PEER %u (%s) announced %u affiliations", peerId, connection->identity().c_str(), len); + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u affiliations", peerId, connection->identWithQualifier().c_str(), len); - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); } } } @@ -1535,16 +1889,16 @@ void FNENetwork::taskNetworkRx(NetPacketRequest* req) } offs += 4U; } - LogMessage(LOG_NET, "PEER %u (%s) announced %u VCs", peerId, connection->identity().c_str(), len); + LogInfoEx(LOG_MASTER, "PEER %u (%s) announced %u VCs", peerId, connection->identWithQualifier().c_str(), len); network->m_ccPeerMap[peerId] = vcPeers; - // attempt to repeat traffic to Peer-Link masters + // attempt to repeat traffic to replica masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { + if (peer.second->isEnabled() && peer.second->isReplica()) { peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false); } } } @@ -1590,6 +1944,22 @@ bool FNENetwork::checkU2UDroppedPeer(uint32_t peerId) return false; } +/* Helper to dump the current spanning tree configuration to the log. */ + +void FNENetwork::logSpanningTree(FNEPeerConnection* connection) +{ + if (!m_enableSpanningTree) + return; + + if (m_logSpanningTreeChanges && m_treeRoot->hasChildren()) { + if (connection != nullptr) + LogInfoEx(LOG_STP, "PEER %u (%s) Network Tree, Tree Change, Current Tree", connection->id(), connection->identWithQualifier().c_str()); + else + LogInfoEx(LOG_STP, "PEER %u Network Tree, Tree Display, Current Tree", m_peerId); + SpanningTree::visualizeTreeToLog(m_treeRoot); + } +} + /* Erases a stream ID from the given peer ID connection. */ void FNENetwork::eraseStreamPktSeq(uint32_t peerId, uint32_t streamId) @@ -1597,7 +1967,7 @@ void FNENetwork::eraseStreamPktSeq(uint32_t peerId, uint32_t streamId) if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { - connection->eraseStreamPktSeq(streamId); + connection->erasePktSeq(streamId); } } } @@ -1634,37 +2004,103 @@ bool FNENetwork::erasePeerAffiliations(uint32_t peerId) return false; } +/* Helper to disconnect a downstream peer. */ + +void FNENetwork::disconnectPeer(uint32_t peerId, FNEPeerConnection* connection) +{ + if (peerId == 0U) + return; + if (connection == nullptr) + return; + + connection->connected(false); + connection->connectionState(NET_STAT_INVALID); + + connection->lock(); + erasePeer(peerId); + connection->unlock(); + if (connection != nullptr) { + delete connection; + } +} + /* Helper to erase the peer from the peers list. */ void FNENetwork::erasePeer(uint32_t peerId) { + bool neighborFNE = false; { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { + neighborFNE = it->second->isNeighborFNEPeer(); m_peers.erase(peerId); } } // erase any CC maps for this peer { - auto it = std::find_if(m_ccPeerMap.begin(), m_ccPeerMap.end(), [&](auto x) { return x.first == peerId; }); + auto it = std::find_if(m_ccPeerMap.begin(), m_ccPeerMap.end(), [&](auto& x) { return x.first == peerId; }); if (it != m_ccPeerMap.end()) { m_ccPeerMap.erase(peerId); } } - // erase any Peer-Link entries for this peer + // erase any peer replication entries for this peer { - auto it = std::find_if(m_peerLinkPeers.begin(), m_peerLinkPeers.end(), [&](auto x) { return x.first == peerId; }); - if (it != m_peerLinkPeers.end()) { - m_peerLinkPeers.erase(peerId); + auto it = std::find_if(m_peerReplicaPeers.begin(), m_peerReplicaPeers.end(), [&](auto& x) { return x.first == peerId; }); + if (it != m_peerReplicaPeers.end()) { + m_peerReplicaPeers.erase(peerId); + } + } + + // erase any HA parameters for this peer + { + auto it = std::find_if(m_peerReplicaHAParams.begin(), m_peerReplicaHAParams.end(), [&](auto& x) { return x.peerId == peerId; }); + if (it != m_peerReplicaHAParams.end()) { + m_peerReplicaHAParams.erase(it); + } + } + + if (neighborFNE && m_enableSpanningTree) { + std::lock_guard guard(m_treeLock); + + // erase this peer from the master tree + SpanningTree* tree = SpanningTree::findByPeerID(peerId); + if (tree != nullptr) { + if (tree->hasChildren()) { + uint32_t totalChildren = tree->countChildren(tree); + + // netsplit be as noisy as possible about it... + for (uint8_t i = 0U; i < 3U; i++) + LogWarning(LOG_MASTER, "PEER %u downstream netsplit, lost %u downstream connections", peerId, totalChildren); + } + + LogWarning(LOG_MASTER, "PEER %u downstream netsplit, disconnected", peerId); + SpanningTree::erasePeer(peerId); } + + logSpanningTree(); } // cleanup peer affiliations erasePeerAffiliations(peerId); } +/* Helper to determine if the peer is local to this master. */ + +bool FNENetwork::isPeerLocal(uint32_t peerId) +{ + m_peers.shared_lock(); + auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); + if (it != m_peers.end()) { + m_peers.shared_unlock(); + return true; + } + m_peers.shared_unlock(); + + return false; +} + /* Helper to find the unit registration for the given source ID. */ uint32_t FNENetwork::findPeerUnitReg(uint32_t srcId) @@ -1709,7 +2145,7 @@ json::object FNENetwork::fneConnObject(uint32_t peerId, FNEPeerConnection *conn) peerObj["config"].set(peerConfig); json::array voiceChannels = json::array(); - auto it = std::find_if(m_ccPeerMap.begin(), m_ccPeerMap.end(), [&](auto x) { return x.first == peerId; }); + auto it = std::find_if(m_ccPeerMap.begin(), m_ccPeerMap.end(), [&](auto& x) { return x.first == peerId; }); if (it != m_ccPeerMap.end()) { std::vector vcPeers = m_ccPeerMap[peerId]; for (uint32_t vcEntry : vcPeers) { @@ -1731,21 +2167,40 @@ bool FNENetwork::resetPeer(uint32_t peerId) sockaddr_storage addr = connection->socketStorage(); uint32_t addrLen = connection->sockStorageLen(); - LogInfoEx(LOG_NET, "PEER %u (%s) resetting peer connection", peerId, connection->identity().c_str()); + LogInfoEx(LOG_MASTER, "PEER %u (%s) resetting peer connection", peerId, connection->identWithQualifier().c_str()); writePeerNAK(peerId, TAG_REPEATER_LOGIN, NET_CONN_NAK_PEER_RESET, addr, addrLen); - + connection->lock(); erasePeer(peerId); + connection->unlock(); delete connection; return true; } } - LogWarning(LOG_NET, "PEER %u reset failed; peer not found.", peerId); + LogWarning(LOG_MASTER, "PEER %u reset failed; peer not found", peerId); return false; } +/* Helper to set the master is upstream peer replica flag. */ + +void FNENetwork::setPeerReplica(bool replica) +{ + if (!m_isReplica && replica) { + LogInfoEx(LOG_MASTER, "Set as upstream peer replica, receiving ACL updates from upstream master"); + } + + m_isReplica = replica; + + // be very noisy about being a peer replica and having multiple upstream peers + if (m_isReplica) { + if (m_host->m_peerNetworks.size() > 1) { + LogWarning(LOG_MASTER, "We are a upstream peer replica, and have multiple upstream peers? This is a bad idea. Peer Replica FNEs should have a single upstream peer connection."); + } + } +} + /* Helper to resolve the peer ID to its identity string. */ std::string FNENetwork::resolvePeerIdentity(uint32_t peerId) @@ -1754,7 +2209,7 @@ std::string FNENetwork::resolvePeerIdentity(uint32_t peerId) if (it != m_peers.end()) { if (it->second != nullptr) { FNEPeerConnection* peer = it->second; - return peer->identity(); + return peer->identWithQualifier(); } } @@ -1768,7 +2223,7 @@ void FNENetwork::setupRepeaterLogin(uint32_t peerId, uint32_t streamId, FNEPeerC std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); connection->salt(dist(m_random)); - LogInfoEx(LOG_NET, "PEER %u started login from, %s:%u", peerId, connection->address().c_str(), connection->port()); + LogInfoEx(LOG_MASTER, "PEER %u started login from, %s:%u", peerId, connection->address().c_str(), connection->port()); connection->connectionState(NET_STAT_WAITING_AUTHORISATION); m_peers[peerId] = connection; @@ -1779,28 +2234,138 @@ void FNENetwork::setupRepeaterLogin(uint32_t peerId, uint32_t streamId, FNEPeerC SET_UINT32(connection->salt(), salt, 0U); writePeerACK(peerId, streamId, salt, 4U); - LogInfoEx(LOG_NET, "PEER %u RPTL ACK, challenge response sent for login", peerId); + LogInfoEx(LOG_MASTER, "PEER %u RPTL ACK, challenge response sent for login", peerId); +} + +/* Helper to process an In-Call Control message. */ + +void FNENetwork::processInCallCtrl(network::NET_ICC::ENUM command, network::NET_SUBFUNC::ENUM subFunc, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + if (m_debug) + LogDebugEx(LOG_HOST, "FNENetwork::processInCallCtrl()", "peerId = %u, command = $%02X, subFunc = $%02X, dstId = %u, slot = %u, ssrc = %u, streamId = %u", + peerId, command, subFunc, dstId, slotNo, ssrc, streamId); + + if (m_disallowInCallCtrl) { + LogWarning(LOG_MASTER, "PEER %u In-Call Control disabled, ignoring ICC request, dstId = %u, slot = %u, ssrc = %u, streamId = %u", + peerId, dstId, slotNo, ssrc, streamId); + return; + } + + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + { + // is this a local peer? + if (ssrc > 0 && (m_peers.find(ssrc) != m_peers.end())) { + FNEPeerConnection* connection = m_peers[ssrc]; + if (connection != nullptr) { + // validate peer (simple validation really) + if (connection->connected()) { + LogInfoEx(LOG_MASTER, "PEER %u In-Call Control Request to Local Peer, dstId = %u, slot = %u, ssrc = %u, streamId = %u", peerId, dstId, slotNo, ssrc, streamId); + + // send ICC request to local peer + writePeerICC(ssrc, streamId, subFunc, command, dstId, slotNo, true); + + // flag the protocol call handler to allow call takeover on the next audio frame + switch (subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + m_tagDMR->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + m_tagP25->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + m_tagNXDN->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + m_tagAnalog->triggerCallTakeover(dstId); + break; + + default: + break; + } + } + } + } else { + LogInfoEx(LOG_MASTER, "PEER %u In-Call Control Request to Neighbors, dstId = %u, slot = %u, ssrc = %u, streamId = %u", peerId, dstId, slotNo, ssrc, streamId); + + // send ICC request to any peers connected to us that are neighbor FNEs + m_peers.shared_lock(); + for (auto peer : m_peers) { + if (peer.second == nullptr) + continue; + if (peerId != peer.first) { + FNEPeerConnection* conn = peer.second; + if (peerId == peer.first) { + // skip the peer if it is the source peer + continue; + } + + if (conn->isNeighborFNEPeer()) { + // send ICC request to local peer + writePeerICC(peer.first, streamId, subFunc, command, dstId, slotNo, true, false, ssrc); + } + } + } + m_peers.shared_unlock(); + + // flag the protocol call handler to allow call takeover on the next audio frame + switch (subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + m_tagDMR->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + m_tagP25->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + m_tagNXDN->triggerCallTakeover(dstId); + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + m_tagAnalog->triggerCallTakeover(dstId); + break; + + default: + break; + } + + // send further up the network tree + if (m_host->m_peerNetworks.size() > 0) { + writePeerICC(peerId, streamId, subFunc, command, dstId, slotNo, true, true, ssrc); + } + } + } + break; + + default: + break; + } } -/* Helper to send the ACL lists to the specified peer in a separate thread. */ +/* Helper to send the network metadata to the specified peer in a separate thread. */ -void FNENetwork::peerACLUpdate(uint32_t peerId) +void FNENetwork::peerMetadataUpdate(uint32_t peerId) { - ACLUpdateRequest* req = new ACLUpdateRequest(); + MetadataUpdateRequest* req = new MetadataUpdateRequest(); req->obj = this; req->peerId = peerId; // enqueue the task - if (!m_threadPool.enqueue(new_pooltask(taskACLUpdate, req))) { - LogError(LOG_NET, "Failed to task enqueue ACL update, peerId = %u", peerId); + if (!m_threadPool.enqueue(new_pooltask(taskMetadataUpdate, req))) { + LogError(LOG_NET, "Failed to task enqueue metadata update, peerId = %u", peerId); if (req != nullptr) delete req; } } -/* Helper to send the ACL lists to the specified peer in a separate thread. */ +/* Helper to send the network metadata to the specified peer in a separate thread. */ -void FNENetwork::taskACLUpdate(ACLUpdateRequest* req) +void FNENetwork::taskMetadataUpdate(MetadataUpdateRequest* req) { if (req != nullptr) { FNENetwork* network = static_cast(req->obj); @@ -1817,24 +2382,33 @@ void FNENetwork::taskACLUpdate(ACLUpdateRequest* req) FNEPeerConnection* connection = network->m_peers[req->peerId]; if (connection != nullptr) { - uint32_t aclStreamId = network->createStreamId(); + if (connection->connected()) { + connection->lock(); + uint32_t streamId = network->createStreamId(); - // if the connection is an external peer, and peer is participating in peer link, - // send the peer proper configuration data - if (connection->isExternalPeer() && connection->isPeerLink()) { - LogInfoEx(LOG_NET, "PEER %u (%s) sending Peer-Link ACL list updates", req->peerId, peerIdentity.c_str()); + // if the connection is a downstream neighbor FNE peer, and peer is participating in peer link, + // send the peer proper configuration data + if (connection->isNeighborFNEPeer() && connection->isReplica()) { + LogInfoEx(LOG_MASTER, "PEER %u (%s) sending replica network metadata updates", req->peerId, peerIdentity.c_str()); - network->writeWhitelistRIDs(req->peerId, aclStreamId, true); - network->writeTGIDs(req->peerId, aclStreamId, true); - network->writePeerList(req->peerId, aclStreamId); - } - else { - LogInfoEx(LOG_NET, "PEER %u (%s) sending ACL list updates", req->peerId, peerIdentity.c_str()); + network->writeWhitelistRIDs(req->peerId, streamId, true); + network->writeTGIDs(req->peerId, streamId, true); + network->writePeerList(req->peerId, streamId); + + network->writeHAParameters(req->peerId, streamId, true); + } + else { + LogInfoEx(LOG_MASTER, "PEER %u (%s) sending network metadata updates", req->peerId, peerIdentity.c_str()); - network->writeWhitelistRIDs(req->peerId, aclStreamId, false); - network->writeBlacklistRIDs(req->peerId, aclStreamId); - network->writeTGIDs(req->peerId, aclStreamId, false); - network->writeDeactiveTGIDs(req->peerId, aclStreamId); + network->writeWhitelistRIDs(req->peerId, streamId, false); + network->writeBlacklistRIDs(req->peerId, streamId); + network->writeTGIDs(req->peerId, streamId, false); + network->writeDeactiveTGIDs(req->peerId, streamId); + + network->writeHAParameters(req->peerId, streamId, false); + } + + connection->unlock(); } } @@ -1842,24 +2416,41 @@ void FNENetwork::taskACLUpdate(ACLUpdateRequest* req) } } +/* +** ACL Message Writing +*/ + /* Helper to send the list of whitelisted RIDs to the specified peer. */ -void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isExternalPeer) +void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool sendReplica) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - // sending PEER_LINK style RID list to external peers - if (isExternalPeer) { + // sending REPL style RID list to replica neighbor FNE peers + if (sendReplica) { FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { - std::string filename = m_ridLookup->filename(); - if (filename.empty()) { - return; + // save out radio ID table to disk + std::string tempFile; + if (m_isReplica) { + std::ostringstream s; + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); + s << "/tmp/rid_acl.dat." << dist(mt); + + tempFile = s.str(); + std::string origFile = m_ridLookup->filename(); + m_ridLookup->filename(tempFile); + m_ridLookup->commit(true); + m_ridLookup->filename(origFile); + } else { + tempFile = m_ridLookup->filename(); } // read entire file into string buffer std::stringstream b; - std::ifstream stream(filename); + std::ifstream stream(tempFile); if (stream.is_open()) { while (stream.peek() != EOF) { b << (char)stream.get(); @@ -1868,19 +2459,23 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE stream.close(); } + if (m_isReplica) + ::remove(tempFile.c_str()); + // convert to a byte array uint32_t len = b.str().size(); DECLARE_UINT8_ARRAY(buffer, len); ::memcpy(buffer, b.str().data(), len); - PacketBuffer pkt(true, "Peer-Link, RID List"); + PacketBuffer pkt(true, "Peer Replication, RID List"); pkt.encode((uint8_t*)buffer, len); - LogInfoEx(LOG_NET, "PEER %u Peer-Link, RID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, RID List, blocks %u, streamId = %u", peerId, connection->identWithQualifier().c_str(), + pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_RID_LIST }, - frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); + writePeer(peerId, m_peerId, { NET_FUNC::REPL, NET_SUBFUNC::REPL_RID_LIST }, + frag.second->data, FRAG_SIZE, 0U, streamId); Thread::sleep(60U); // pace block transmission } } @@ -1893,9 +2488,7 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE // send radio ID white/black lists std::vector ridWhitelist; - - auto ridLookups = m_ridLookup->table(); - for (auto entry : ridLookups) { + for (auto entry : m_ridLookup->table()) { uint32_t id = entry.first; if (entry.second.radioEnabled()) { ridWhitelist.push_back(id); @@ -1942,7 +2535,7 @@ void FNENetwork::writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool isE uint32_t id = ridWhitelist.at(j + (i * MAX_RID_LIST_CHUNK)); if (m_debug) - LogDebug(LOG_NET, "PEER %u (%s) whitelisting RID %u (%d / %d)", peerId, connection->identity().c_str(), + LogDebug(LOG_MASTER, "PEER %u (%s) whitelisting RID %u (%d / %d)", peerId, connection->identWithQualifier().c_str(), id, i, j); SET_UINT32(id, payload, offs); @@ -1965,9 +2558,7 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId, uint32_t streamId) // send radio ID blacklist std::vector ridBlacklist; - - auto ridLookups = m_ridLookup->table(); - for (auto entry : ridLookups) { + for (auto entry : m_ridLookup->table()) { uint32_t id = entry.first; if (!entry.second.radioEnabled()) { ridBlacklist.push_back(id); @@ -2014,7 +2605,7 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId, uint32_t streamId) uint32_t id = ridBlacklist.at(j + (i * MAX_RID_LIST_CHUNK)); if (m_debug) - LogDebug(LOG_NET, "PEER %u (%s) blacklisting RID %u (%d / %d)", peerId, connection->identity().c_str(), + LogDebug(LOG_MASTER, "PEER %u (%s) blacklisting RID %u (%d / %d)", peerId, connection->identWithQualifier().c_str(), id, i, j); SET_UINT32(id, payload, offs); @@ -2031,24 +2622,36 @@ void FNENetwork::writeBlacklistRIDs(uint32_t peerId, uint32_t streamId) /* Helper to send the list of active TGIDs to the specified peer. */ -void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalPeer) +void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool sendReplica) { if (!m_tidLookup->sendTalkgroups()) { return; } - // sending PEER_LINK style TGID list to external peers - if (isExternalPeer) { + // sending REPL style TGID list to replica neighbor FNE peers + if (sendReplica) { FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { - std::string filename = m_tidLookup->filename(); - if (filename.empty()) { - return; + std::string tempFile; + if (m_isReplica) { + std::ostringstream s; + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); + s << "/tmp/talkgroup_rules.yml." << dist(mt); + + tempFile = s.str(); + std::string origFile = m_tidLookup->filename(); + m_tidLookup->filename(tempFile); + m_tidLookup->commit(true); + m_tidLookup->filename(origFile); + } else { + tempFile = m_tidLookup->filename(); } // read entire file into string buffer std::stringstream b; - std::ifstream stream(filename); + std::ifstream stream(tempFile); if (stream.is_open()) { while (stream.peek() != EOF) { b << (char)stream.get(); @@ -2057,19 +2660,23 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP stream.close(); } + if (m_isReplica) + ::remove(tempFile.c_str()); + // convert to a byte array uint32_t len = b.str().size(); DECLARE_UINT8_ARRAY(buffer, len); ::memcpy(buffer, b.str().data(), len); - PacketBuffer pkt(true, "Peer-Link, TGID List"); + PacketBuffer pkt(true, "Peer Replication, TGID List"); pkt.encode((uint8_t*)buffer, len); - LogInfoEx(LOG_NET, "PEER %u Peer-Link, TGID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, TGID List, blocks %u, streamId = %u", peerId, connection->identWithQualifier().c_str(), + pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_TALKGROUP_LIST }, - frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); + writePeer(peerId, m_peerId, { NET_FUNC::REPL, NET_SUBFUNC::REPL_TALKGROUP_LIST }, + frag.second->data, FRAG_SIZE, 0U, streamId); Thread::sleep(60U); // pace block transmission } } @@ -2081,8 +2688,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP } std::vector> tgidList; - auto groupVoice = m_tidLookup->groupVoice(); - for (auto entry : groupVoice) { + for (auto entry : m_tidLookup->groupVoice()) { std::vector inclusion = entry.config().inclusion(); std::vector exclusion = entry.config().exclusion(); std::vector preferred = entry.config().preferred(); @@ -2091,7 +2697,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP if (inclusion.size() > 0) { auto it = std::find(inclusion.begin(), inclusion.end(), peerId); if (it == inclusion.end()) { - // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); + // LogDebug(LOG_MASTER, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } @@ -2099,7 +2705,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP if (exclusion.size() > 0) { auto it = std::find(exclusion.begin(), exclusion.end(), peerId); if (it != exclusion.end()) { - // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); + // LogDebug(LOG_MASTER, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } @@ -2141,7 +2747,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId, uint32_t streamId, bool isExternalP for (std::pair tg : tgidList) { if (m_debug) { std::string peerIdentity = resolvePeerIdentity(peerId); - LogDebug(LOG_NET, "PEER %u (%s) activating TGID %u TS %u", peerId, peerIdentity.c_str(), + LogDebug(LOG_MASTER, "PEER %u (%s) activating TGID %u TS %u", peerId, peerIdentity.c_str(), tg.first, tg.second); } SET_UINT32(tg.first, payload, offs); @@ -2162,8 +2768,7 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) } std::vector> tgidList; - auto groupVoice = m_tidLookup->groupVoice(); - for (auto entry : groupVoice) { + for (auto entry : m_tidLookup->groupVoice()) { std::vector inclusion = entry.config().inclusion(); std::vector exclusion = entry.config().exclusion(); @@ -2171,7 +2776,7 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) if (inclusion.size() > 0) { auto it = std::find(inclusion.begin(), inclusion.end(), peerId); if (it == inclusion.end()) { - // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); + // LogDebug(LOG_MASTER, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } @@ -2179,7 +2784,7 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) if (exclusion.size() > 0) { auto it = std::find(exclusion.begin(), exclusion.end(), peerId); if (it != exclusion.end()) { - // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); + // LogDebug(LOG_MASTER, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } @@ -2200,7 +2805,7 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) for (std::pair tg : tgidList) { if (m_debug) { std::string peerIdentity = resolvePeerIdentity(peerId); - LogDebug(LOG_NET, "PEER %u (%s) deactivating TGID %u TS %u", peerId, peerIdentity.c_str(), + LogDebug(LOG_MASTER, "PEER %u (%s) deactivating TGID %u TS %u", peerId, peerIdentity.c_str(), tg.first, tg.second); } SET_UINT32(tg.first, payload, offs); @@ -2216,17 +2821,29 @@ void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) { - // sending PEER_LINK style RID list to external peers + // sending REPL style PID list to replica neighbor FNE peers FNEPeerConnection* connection = m_peers[peerId]; if (connection != nullptr) { - std::string filename = m_peerListLookup->filename(); - if (filename.empty()) { - return; + std::string tempFile; + if (m_isReplica) { + std::ostringstream s; + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); + s << "/tmp/peer_list.dat." << dist(mt); + + tempFile = s.str(); + std::string origFile = m_peerListLookup->filename(); + m_peerListLookup->filename(tempFile); + m_peerListLookup->commit(true); + m_peerListLookup->filename(origFile); + } else { + tempFile = m_peerListLookup->filename(); } // read entire file into string buffer std::stringstream b; - std::ifstream stream(filename); + std::ifstream stream(tempFile); if (stream.is_open()) { while (stream.peek() != EOF) { b << (char)stream.get(); @@ -2235,19 +2852,23 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) stream.close(); } + if (m_isReplica) + ::remove(tempFile.c_str()); + // convert to a byte array uint32_t len = b.str().size(); DECLARE_UINT8_ARRAY(buffer, len); ::memcpy(buffer, b.str().data(), len); - PacketBuffer pkt(true, "Peer-Link, PID List"); + PacketBuffer pkt(true, "Peer Replication, PID List"); pkt.encode((uint8_t*)buffer, len); - LogInfoEx(LOG_NET, "PEER %u Peer-Link, PID List, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, PID List, blocks %u, streamId = %u", peerId, connection->identWithQualifier().c_str(), + pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writePeer(peerId, m_peerId, { NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_PEER_LIST }, - frag.second->data, FRAG_SIZE, 0U, streamId, false, true, true); + writePeer(peerId, m_peerId, { NET_FUNC::REPL, NET_SUBFUNC::REPL_PEER_LIST }, + frag.second->data, FRAG_SIZE, 0U, streamId); Thread::sleep(60U); // pace block transmission } } @@ -2258,32 +2879,133 @@ void FNENetwork::writePeerList(uint32_t peerId, uint32_t streamId) return; } +/* Helper to send the HA parameters to the specified peer. */ + +void FNENetwork::writeHAParameters(uint32_t peerId, uint32_t streamId, bool sendReplica) +{ + if (!m_haEnabled) { + return; + } + + uint32_t len = 4U + (m_peerReplicaHAParams.size() * HA_PARAMS_ENTRY_LEN); + DECLARE_UINT8_ARRAY(buffer, len); + + SET_UINT32((len - 4U), buffer, 0U); + + uint32_t offs = 4U; + m_peerReplicaHAParams.lock(false); + for (uint8_t i = 0U; i < m_peerReplicaHAParams.size(); i++) { + uint32_t peerId = m_peerReplicaHAParams[i].peerId; + uint32_t ipAddr = m_peerReplicaHAParams[i].masterIP; + uint16_t port = m_peerReplicaHAParams[i].masterPort; + + SET_UINT32(peerId, buffer, offs); + SET_UINT32(ipAddr, buffer, offs + 4U); + SET_UINT16(port, buffer, offs + 8U); + + offs += HA_PARAMS_ENTRY_LEN; + } + m_peerReplicaHAParams.unlock(); + + // sending REPL style HA parameters list to replica neighbor FNE peers + if (sendReplica) { + FNEPeerConnection* connection = m_peers[peerId]; + if (connection != nullptr) { + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, HA parameters, streamId = %u", peerId, connection->identWithQualifier().c_str(), streamId); + writePeer(peerId, m_peerId, { NET_FUNC::REPL, NET_SUBFUNC::REPL_HA_PARAMS}, + buffer, len, 0U, streamId); + } + } + + writePeerCommand(peerId, { NET_FUNC::MASTER, NET_SUBFUNC::MASTER_HA_PARAMS }, + buffer, len, streamId, true); +} + +/* Helper to send a network tree disconnect to the specified peer. */ + +void FNENetwork::writeTreeDisconnect(uint32_t peerId, uint32_t offendingPeerId) +{ + if (!m_enableSpanningTree) + return; + + if (peerId == 0) + return; + if (offendingPeerId == 0U) + return; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + SET_UINT32(offendingPeerId, buffer, 0U); // Offending Peer ID + + writePeerCommand(peerId, { NET_FUNC::NET_TREE, NET_SUBFUNC::NET_TREE_DISC }, buffer, 4U, RTP_END_OF_CALL_SEQ, createStreamId()); +} + /* Helper to send a In-Call Control command to the specified peer. */ -bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::ENUM subFunc, NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) +bool FNENetwork::writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::ENUM subFunc, NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo, + bool systemReq, bool toUpstream, uint32_t ssrc) { if (peerId == 0) return false; - if (!m_enableInCallCtrl) + if (!m_enableRIDInCallCtrl && !systemReq) return false; if (dstId == 0U) return false; + if (systemReq && ssrc == 0U) + ssrc = peerId; + + if (m_debug) + LogDebugEx(LOG_HOST, "FNENetwork::writePeerICC()", "peerId = %u, command = $%02X, subFunc = $%02X, dstId = %u, slot = %u, ssrc = %u, streamId = %u", + peerId, command, subFunc, dstId, slotNo, ssrc, streamId); + uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); - SET_UINT32(peerId, buffer, 6U); // Peer ID + if (systemReq) { + SET_UINT32(ssrc, buffer, 6U); // Peer ID + } else { + SET_UINT32(peerId, buffer, 6U); // Peer ID + } buffer[10U] = (uint8_t)command; // In-Call Control Command SET_UINT24(dstId, buffer, 11U); // Destination ID buffer[14U] = slotNo; // DMR Slot No - return writePeer(peerId, m_peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false); + // are we sending this ICC request upstream? + if (toUpstream && systemReq) { + if (m_host->m_peerNetworks.size() > 0U) { + for (auto peer : m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled()) { + peer.second->writeMaster({ NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId, false, 0U, ssrc); + } + } + } + } + + return true; + } + else + return writePeer(peerId, ssrc, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 15U, RTP_END_OF_CALL_SEQ, streamId); } +/* +** Generic Message Writing +*/ + /* Helper to send a data message to the specified peer with a explicit packet sequence. */ bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, - uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq, bool directWrite) const + uint32_t length, uint16_t pktSeq, uint32_t streamId, bool incPktSeq) const +{ + return writePeerQueue(nullptr, peerId, ssrc, opcode, data, length, pktSeq, streamId, incPktSeq); +} + +/* Helper to queue a data message to the specified peer with a explicit packet sequence. */ + +bool FNENetwork::writePeerQueue(udp::BufferQueue* buffers, uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, + const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool incPktSeq) const { if (streamId == 0U) { LogError(LOG_NET, "BUGBUG: PEER %u, trying to send data with a streamId of 0?", peerId); @@ -2296,16 +3018,19 @@ bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePai sockaddr_storage addr = connection->socketStorage(); uint32_t addrLen = connection->sockStorageLen(); - if (incPktSeq) { - pktSeq = connection->incStreamPktSeq(streamId, pktSeq); + if (incPktSeq && pktSeq != RTP_END_OF_CALL_SEQ) { + pktSeq = connection->incPktSeq(streamId); } - +#if DEBUG_RTP_MUX + if (m_debug) + LogDebugEx(LOG_NET, "FNENetwork::writePeerQueue()", "PEER %u, streamId = %u, pktSeq = %u", peerId, streamId, pktSeq); +#endif if (m_maskOutboundPeerID) ssrc = m_peerId; // mask the source SSRC to our own peer ID else { - if ((connection->isExternalPeer() && !connection->isPeerLink()) && m_maskOutboundPeerIDForNonPL) { - // if the peer is an external peer, and not a Peer-Link peer, we need to send the packet - // to the external peer with our peer ID as the source instead of the originating peer + if ((connection->isNeighborFNEPeer() && !connection->isReplica()) && m_maskOutboundPeerIDForNonPL) { + // if the peer is a downstream FNE neighbor peer, and not a replica peer, we need to send the packet + // to the neighbor FNE peer with our peer ID as the source instead of the originating peer // because we have routed it ssrc = m_peerId; } @@ -2316,13 +3041,11 @@ bool FNENetwork::writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePai } } - if (directWrite) + if (buffers == nullptr) return m_frameQueue->write(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); else { - m_frameQueue->enqueueMessage(data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); - if (queueOnly) - return true; - return m_frameQueue->flushQueue(); + m_frameQueue->enqueueMessage(buffers, data, length, streamId, peerId, ssrc, opcode, pktSeq, addr, addrLen); + return true; } } } @@ -2346,7 +3069,7 @@ bool FNENetwork::writePeerCommand(uint32_t peerId, FrameQueue::OpcodePair opcode } uint32_t len = length + 6U; - return writePeer(peerId, m_peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, false, incPktSeq, true); + return writePeer(peerId, m_peerId, opcode, buffer, len, RTP_END_OF_CALL_SEQ, streamId, incPktSeq); } /* Helper to send a ACK response to the specified peer. */ @@ -2363,7 +3086,7 @@ bool FNENetwork::writePeerACK(uint32_t peerId, uint32_t streamId, const uint8_t* } return writePeer(peerId, m_peerId, { NET_FUNC::ACK, NET_SUBFUNC::NOP }, buffer, length + 10U, RTP_END_OF_CALL_SEQ, - streamId, false, false, true); + streamId); } /* Helper to log a warning specifying which NAK reason is being sent a peer. */ @@ -2372,33 +3095,37 @@ void FNENetwork::logPeerNAKReason(uint32_t peerId, const char* tag, NET_CONN_NAK { switch (reason) { case NET_CONN_NAK_MODE_NOT_ENABLED: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; digital mode not enabled on FNE", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; digital mode not enabled on FNE", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_ILLEGAL_PACKET: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; illegal/unknown packet", peerId ,tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; illegal/unknown packet", peerId ,tag, (uint16_t)reason); break; case NET_CONN_NAK_FNE_UNAUTHORIZED: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; unauthorized", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; unauthorized", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_BAD_CONN_STATE: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; bad connection state", peerId ,tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; bad connection state", peerId ,tag, (uint16_t)reason); break; case NET_CONN_NAK_INVALID_CONFIG_DATA: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; invalid configuration data", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; invalid configuration data", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_FNE_MAX_CONN: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; FNE has reached maximum permitted connections", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; FNE has reached maximum permitted connections", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_PEER_RESET: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; FNE demanded connection reset", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; FNE demanded connection reset", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_PEER_ACL: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; ACL rejection", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; ACL rejection", peerId, tag, (uint16_t)reason); + break; + + case NET_CONN_NAK_FNE_DUPLICATE_CONN: + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; duplicate connection drop", peerId, tag, (uint16_t)reason); break; case NET_CONN_NAK_GENERAL_FAILURE: default: - LogWarning(LOG_NET, "PEER %u NAK %s, reason = %u; general failure", peerId, tag, (uint16_t)reason); + LogWarning(LOG_MASTER, "PEER %u NAK %s, reason = %u; general failure", peerId, tag, (uint16_t)reason); break; } } @@ -2419,7 +3146,7 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, uint32_t streamId, const char* ta SET_UINT16((uint16_t)reason, buffer, 10U); // Reason logPeerNAKReason(peerId, tag, reason); - return writePeer(peerId, m_peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, streamId, false); + return writePeer(peerId, m_peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 12U, RTP_END_OF_CALL_SEQ, streamId); } /* Helper to send a NAK response to the specified peer. */ @@ -2438,11 +3165,15 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag, NET_CONN_NAK_REA SET_UINT16((uint16_t)reason, buffer, 10U); // Reason logPeerNAKReason(peerId, tag, reason); - LogWarning(LOG_NET, "PEER %u NAK %s -> %s:%u", peerId, tag, udp::Socket::address(addr).c_str(), udp::Socket::port(addr)); + LogWarning(LOG_MASTER, "PEER %u NAK %s -> %s:%u", peerId, tag, udp::Socket::address(addr).c_str(), udp::Socket::port(addr)); return m_frameQueue->write(buffer, 12U, createStreamId(), peerId, m_peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, 0U, addr, addrLen); } +/* +** Internal KMM Callback. +*/ + /* Helper to process a FNE KMM TEK response. */ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uint8_t keyLength) @@ -2453,12 +3184,12 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin if (rspKi == nullptr) return; - LogMessage(LOG_NET, "upstream master enc. key, algId = $%02X, kID = $%04X", algId, rspKi->kId()); + LogInfoEx(LOG_PEER, "upstream master enc. key, algId = $%02X, kID = $%04X", algId, rspKi->kId()); - m_keyQueueMutex.lock(); + s_keyQueueMutex.lock(); std::vector peersToRemove; - for (auto entry : m_peerLinkKeyQueue) { + for (auto entry : m_peerReplicaKeyQueue) { uint16_t keyId = entry.second; if (keyId == rspKi->kId() && algId > 0U) { uint32_t peerId = entry.first; @@ -2498,7 +3229,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin modifyKeyRsp.encode(buffer + 11U); writePeer(peerId, m_peerId, { NET_FUNC::KEY_RSP, NET_SUBFUNC::NOP }, buffer, modifyKeyRsp.length() + 11U, - RTP_END_OF_CALL_SEQ, createStreamId(), false, false, true); + RTP_END_OF_CALL_SEQ, createStreamId()); peersToRemove.push_back(peerId); } @@ -2506,7 +3237,7 @@ void FNENetwork::processTEKResponse(p25::kmm::KeyItem* rspKi, uint8_t algId, uin // remove peers who were sent keys for (auto peerId : peersToRemove) - m_peerLinkKeyQueue.erase(peerId); + m_peerReplicaKeyQueue.erase(peerId); - m_keyQueueMutex.unlock(); + s_keyQueueMutex.unlock(); } diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index a0a230b2a..04783234c 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -26,17 +26,21 @@ #include "fne/Defines.h" #include "common/concurrent/unordered_map.h" -#include "common/network/BaseNetwork.h" -#include "common/network/json/json.h" +#include "common/concurrent/shared_unordered_map.h" +#include "common/json/json.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" #include "common/lookups/AdjSiteMapLookup.h" +#include "common/network/BaseNetwork.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" #include "common/ThreadPool.h" #include "fne/lookups/AffiliationLookup.h" #include "fne/network/influxdb/InfluxDB.h" +#include "fne/network/FNEPeerConnection.h" +#include "fne/network/SpanningTree.h" +#include "fne/network/HAParameters.h" #include "fne/CryptoContainer.h" #include @@ -54,6 +58,7 @@ namespace network { namespace callhandler { class HOST_SW_API TagDMRData; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API DMRPacketData; } } } namespace network { namespace callhandler { class HOST_SW_API TagP25Data; } } namespace network { namespace callhandler { namespace packetdata { class HOST_SW_API P25PacketData; } } } +namespace network { class HOST_SW_API P25OTARService; } namespace network { namespace callhandler { class HOST_SW_API TagNXDNData; } } namespace network { namespace callhandler { class HOST_SW_API TagAnalogData; } } @@ -63,17 +68,19 @@ namespace network // Constants // --------------------------------------------------------------------------- + #define MAX_QUEUED_PEER_MSGS 5U + /** * @brief DVM states. */ enum DVM_STATE { - STATE_IDLE = 0U, //! Idle + STATE_IDLE = 0U, //!< Idle // DMR - STATE_DMR = 1U, //! Digital Mobile Radio + STATE_DMR = 1U, //!< Digital Mobile Radio // Project 25 - STATE_P25 = 2U, //! Project 25 + STATE_P25 = 2U, //!< Project 25 // NXDN - STATE_NXDN = 3U, //! NXDN + STATE_NXDN = 3U, //!< NXDN }; #define INFLUXDB_ERRSTR_DISABLED_SRC_RID "disabled source RID" @@ -91,293 +98,16 @@ namespace network class HOST_SW_API DiagNetwork; class HOST_SW_API FNENetwork; - // --------------------------------------------------------------------------- - // Class Declaration - // --------------------------------------------------------------------------- - - /** - * @brief Represents an peer connection to the FNE. - * @ingroup fne_network - */ - class HOST_SW_API FNEPeerConnection { - public: - auto operator=(FNEPeerConnection&) -> FNEPeerConnection& = delete; - auto operator=(FNEPeerConnection&&) -> FNEPeerConnection& = delete; - FNEPeerConnection(FNEPeerConnection&) = delete; - - /** - * @brief Initializes a new instance of the FNEPeerConnection class. - */ - FNEPeerConnection() : - m_id(0U), - m_ccPeerId(0U), - m_socketStorage(), - m_sockStorageLen(0U), - m_address(), - m_port(), - m_salt(0U), - m_connected(false), - m_connectionState(NET_STAT_INVALID), - m_pingsReceived(0U), - m_lastPing(0U), - m_missedACLUpdates(0U), - m_isExternalPeer(false), - m_isConventionalPeer(false), - m_isSysView(false), - m_isPeerLink(false), - m_config(), - m_streamSeqMutex(), - m_streamSeqNos() - { - /* stub */ - } - /** - * @brief Initializes a new instance of the FNEPeerConnection class. - * @param id Unique ID of this modem on the network. - * @param socketStorage - * @param sockStorageLen - */ - FNEPeerConnection(uint32_t id, sockaddr_storage& socketStorage, uint32_t sockStorageLen) : - m_id(id), - m_ccPeerId(0U), - m_socketStorage(socketStorage), - m_sockStorageLen(sockStorageLen), - m_address(udp::Socket::address(socketStorage)), - m_port(udp::Socket::port(socketStorage)), - m_salt(0U), - m_connected(false), - m_connectionState(NET_STAT_INVALID), - m_pingsReceived(0U), - m_lastPing(0U), - m_missedACLUpdates(0U), - m_isExternalPeer(false), - m_isConventionalPeer(false), - m_isSysView(false), - m_isPeerLink(false), - m_config(), - m_streamSeqMutex(), - m_streamSeqNos() - { - assert(id > 0U); - assert(sockStorageLen > 0U); - assert(!m_address.empty()); - assert(m_port > 0U); - } - - /** - * @brief Helper to return the current count of mapped RTP streams. - * @returns size_t - */ - size_t streamCount() - { - return m_streamSeqNos.size(); - } - - /** - * @brief Helper to determine if the stream ID has a stored RTP sequence. - * @param streamId Stream ID. - * @returns bool - */ - bool hasStreamPktSeq(uint64_t streamId) - { - bool ret = false; - bool locked = m_streamSeqMutex.try_lock_for(std::chrono::milliseconds(60)); - - // determine if the stream has a current sequence no and return - { - auto it = m_streamSeqNos.find(streamId); - if (it == m_streamSeqNos.end()) { - ret = false; - } - else { - ret = true; - } - } - - if (locked) - m_streamSeqMutex.unlock(); - - return ret; - } - - /** - * @brief Helper to get the stored RTP sequence for the given stream ID. - * @param streamId Stream ID. - * @returns uint16_t - */ - uint16_t getStreamPktSeq(uint64_t streamId) - { - bool locked = m_streamSeqMutex.try_lock_for(std::chrono::milliseconds(60)); - - // find the current sequence no and return - uint32_t pktSeq = 0U; - { - auto it = m_streamSeqNos.find(streamId); - if (it == m_streamSeqNos.end()) { - pktSeq = RTP_END_OF_CALL_SEQ; - } else { - pktSeq = m_streamSeqNos[streamId]; - } - } - - if (locked) - m_streamSeqMutex.unlock(); - - return pktSeq; - } - - /** - * @brief Helper to increment the stored RTP sequence for the given stream ID. - * @param streamId Stream ID. - * @param initialSeq Initial sequence number to set. - * @returns uint16_t - */ - uint16_t incStreamPktSeq(uint64_t streamId, uint16_t initialSeq) - { - bool locked = m_streamSeqMutex.try_lock_for(std::chrono::milliseconds(60)); - - // find the current sequence no, increment and return - uint32_t pktSeq = 0U; - { - auto it = m_streamSeqNos.find(streamId); - if (it == m_streamSeqNos.end()) { - m_streamSeqNos.insert({streamId, initialSeq}); - } else { - pktSeq = m_streamSeqNos[streamId]; - - ++pktSeq; - if (pktSeq > RTP_END_OF_CALL_SEQ) { - pktSeq = 0U; - } - - m_streamSeqNos[streamId] = pktSeq; - } - } - - if (locked) - m_streamSeqMutex.unlock(); - - return pktSeq; - } - /** - * @brief Helper to erase the stored RTP sequence for the given stream ID. - * @param streamId Stream ID. - * @returns uint16_t - */ - void eraseStreamPktSeq(uint64_t streamId) - { - bool locked = m_streamSeqMutex.try_lock_for(std::chrono::milliseconds(60)); - - // find the sequence no and erase - { - auto entry = m_streamSeqNos.find(streamId); - if (entry != m_streamSeqNos.end()) { - m_streamSeqNos.erase(streamId); - } - } - - if (locked) - m_streamSeqMutex.unlock(); - } - - public: - /** - * @brief Peer ID. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, id); - /** - * @brief Peer Identity. - */ - DECLARE_PROPERTY_PLAIN(std::string, identity); - - /** - * @brief Control Channel Peer ID. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, ccPeerId); - - /** - * @brief Unix socket storage containing the connected address. - */ - DECLARE_PROPERTY_PLAIN(sockaddr_storage, socketStorage); - /** - * @brief Length of the sockaddr_storage structure. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, sockStorageLen); - - /** - * @brief */ - DECLARE_PROPERTY_PLAIN(std::string, address); - /** - * @brief Port number peer connected with. - */ - DECLARE_PROPERTY_PLAIN(uint16_t, port); - - /** - * @brief Salt value used for peer authentication. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, salt); - - /** - * @brief Flag indicating whether or not the peer is connected. - */ - DECLARE_PROPERTY_PLAIN(bool, connected); - /** - * @brief Connection state. - */ - DECLARE_PROPERTY_PLAIN(NET_CONN_STATUS, connectionState); - - /** - * @brief Number of pings received. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, pingsReceived); - /** - * @brief Last ping received. - */ - DECLARE_PROPERTY_PLAIN(uint64_t, lastPing); - - /** - * @brief Number of missed ACL updates. - */ - DECLARE_PROPERTY_PLAIN(uint32_t, missedACLUpdates); - - /** - * @brief Flag indicating this connection is from an external peer. - */ - DECLARE_PROPERTY_PLAIN(bool, isExternalPeer); - /** - * @brief Flag indicating this connection is from an conventional peer. - */ - DECLARE_PROPERTY_PLAIN(bool, isConventionalPeer); - /** - * @brief Flag indicating this connection is from an SysView peer. - */ - DECLARE_PROPERTY_PLAIN(bool, isSysView); - - /** - * @brief Flag indicating this connection is from an external peer that is peer link enabled. - */ - DECLARE_PROPERTY_PLAIN(bool, isPeerLink); - - /** - * @brief JSON objecting containing peer configuration information. - */ - DECLARE_PROPERTY_PLAIN(json::object, config); - - private: - std::timed_mutex m_streamSeqMutex; - std::unordered_map m_streamSeqNos; - }; - // --------------------------------------------------------------------------- // Structure Declaration // --------------------------------------------------------------------------- /** - * @brief Represents the data required for a peer ACL update request thread. + * @brief Represents the data required for a network metadata update request thread. * @ingroup fne_network */ - struct ACLUpdateRequest : thread_t { - uint32_t peerId; //! Peer ID for this request. + struct MetadataUpdateRequest : thread_t { + uint32_t peerId; //!< Peer ID for this request. }; // --------------------------------------------------------------------------- @@ -389,16 +119,17 @@ namespace network * @ingroup fne_network */ struct NetPacketRequest : thread_t { - uint32_t peerId; //! Peer ID for this request. + uint32_t peerId; //!< Peer ID for this request. + void* diagObj; //!< Network diagnostics network object. - sockaddr_storage address; //! IP Address and Port. - uint32_t addrLen; //! - frame::RTPHeader rtpHeader; //! RTP Header - frame::RTPFNEHeader fneHeader; //! RTP FNE Header - int length = 0U; //! Length of raw data buffer - uint8_t* buffer = nullptr; //! Raw data buffer + sockaddr_storage address; //!< IP Address and Port. + uint32_t addrLen; //!< + frame::RTPHeader rtpHeader; //!< RTP Header + frame::RTPFNEHeader fneHeader; //!< RTP FNE Header + int length = 0U; //!< Length of raw data buffer + uint8_t* buffer = nullptr; //!< Raw data buffer - uint64_t pktRxTime; //! Packet receive time + uint64_t pktRxTime; //!< Packet receive time }; // --------------------------------------------------------------------------- @@ -418,7 +149,9 @@ namespace network * @param port Network port number. * @param peerId Unique ID on the network. * @param password Network authentication password. + * @param identity Textual identity of this FNE (this is used when peering with upstream FNEs). * @param debug Flag indicating whether network debug is enabled. + * @param kmfDebug Flag indicating whether P25 OTAR KMF services debug is enabled. * @param verbose Flag indicating whether network verbose logging is enabled. * @param reportPeerPing Flag indicating whether peer pinging is reported. * @param dmr Flag indicating whether DMR is enabled. @@ -434,8 +167,10 @@ namespace network * @param workerCnt Number of worker threads. */ FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, - bool debug, bool verbose, bool reportPeerPing, bool dmr, bool p25, bool nxdn, bool analog, uint32_t parrotDelay, bool parrotGrantDemand, - bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt); + std::string identity, bool debug, bool kmfDebug, bool verbose, bool reportPeerPing, + bool dmr, bool p25, bool nxdn, bool analog, + uint32_t parrotDelay, bool parrotGrantDemand, bool allowActivityTransfer, bool allowDiagnosticTransfer, + uint32_t pingTime, uint32_t updateLookupTime, uint16_t workerCnt); /** * @brief Finalizes a instance of the FNENetwork class. */ @@ -496,6 +231,25 @@ namespace network */ void processNetwork(); + /** + * @brief Process network tree disconnect notification. + * @param offendingPeerId Offending Peer ID. + */ + void processNetworkTreeDisconnect(uint32_t peerId, uint32_t offendingPeerId); + + /** + * @brief Helper to process an downstream peer In-Call Control message. + * @param command In-Call Control Command. + * @param subFunc Network Sub-Function. + * @param dstId Destination ID. + * @param slotNo Slot Number. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID. + */ + void processDownstreamInCallCtrl(network::NET_ICC::ENUM command, network::NET_SUBFUNC::ENUM subFunc, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + /** * @brief Updates the timer by the passed number of milliseconds. * @param ms Number of milliseconds. @@ -519,7 +273,7 @@ namespace network * @param conn FNE Peer Connection. * @return json::object */ - json::object fneConnObject(uint32_t peerId, FNEPeerConnection *conn); + json::object fneConnObject(uint32_t peerId, FNEPeerConnection* conn); /** * @brief Helper to reset a peer connection. @@ -528,6 +282,12 @@ namespace network */ bool resetPeer(uint32_t peerId); + /** + * @brief Helper to set the master is upstream peer replica flag. + * @param replica Flag indicating the master is a peer replica. + */ + void setPeerReplica(bool replica); + private: friend class DiagNetwork; friend class callhandler::TagDMRData; @@ -540,7 +300,10 @@ namespace network callhandler::TagNXDNData* m_tagNXDN; friend class callhandler::TagAnalogData; callhandler::TagAnalogData* m_tagAnalog; - + + friend class P25OTARService; + P25OTARService* m_p25OTARService; + friend class ::RESTAPI; HostFNE* m_host; @@ -549,6 +312,8 @@ namespace network std::string m_password; + bool m_isReplica; + bool m_dmrEnabled; bool m_p25Enabled; bool m_nxdnEnabled; @@ -559,6 +324,8 @@ namespace network bool m_parrotGrantDemand; bool m_parrotOnlyOriginating; + bool m_kmfServicesEnabled; + lookups::RadioIdLookup* m_ridLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; @@ -569,39 +336,33 @@ namespace network NET_CONN_STATUS m_status; typedef std::pair PeerMapPair; - concurrent::unordered_map m_peers; - concurrent::unordered_map m_peerLinkPeers; + concurrent::shared_unordered_map m_peers; + concurrent::unordered_map m_peerReplicaPeers; typedef std::pair PeerAffiliationMapPair; concurrent::unordered_map m_peerAffiliations; concurrent::unordered_map> m_ccPeerMap; - static std::timed_mutex m_keyQueueMutex; - std::unordered_map m_peerLinkKeyQueue; - - /** - * @brief Represents a packet buffer entry in a map. - */ - class PacketBufferEntry { - public: - /** - * @brief Stream ID of the packet. - */ - uint32_t streamId; + static std::timed_mutex s_keyQueueMutex; + std::unordered_map m_peerReplicaKeyQueue; - /** - * @brief Packet fragment buffer. - */ - PacketBuffer* buffer; + SpanningTree* m_treeRoot; + std::mutex m_treeLock; - bool locked; - }; - concurrent::unordered_map m_peerLinkActPkt; + concurrent::vector m_peerReplicaHAParams; + std::string m_advertisedHAAddress; + uint16_t m_advertisedHAPort; + bool m_haEnabled; Timer m_maintainenceTimer; Timer m_updateLookupTimer; + Timer m_haUpdateTimer; uint32_t m_softConnLimit; - bool m_callInProgress; + bool m_enableSpanningTree; + bool m_logSpanningTreeChanges; + bool m_spanningTreeFastReconnect; + + uint32_t m_callCollisionTimeout; bool m_disallowAdjStsBcast; bool m_disallowExtAdjStsBcast; @@ -609,7 +370,8 @@ namespace network bool m_disallowCallTerm; bool m_restrictGrantToAffOnly; bool m_restrictPVCallToRegOnly; - bool m_enableInCallCtrl; + bool m_enableRIDInCallCtrl; + bool m_disallowInCallCtrl; bool m_rejectUnknownRID; bool m_maskOutboundPeerID; @@ -638,9 +400,16 @@ namespace network bool m_verbosePacketData; bool m_logDenials; + bool m_logUpstreamCallStartEnd; bool m_reportPeerPing; bool m_verbose; + /** + * @brief Entry point to parrot handler thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadParrotHandler(void* arg); /** * @brief Entry point to process a given network packet. * @param req Instance of the NetPacketRequest structure. @@ -654,6 +423,12 @@ namespace network */ bool checkU2UDroppedPeer(uint32_t peerId); + /** + * @brief Helper to dump the current spanning tree configuration to the log. + * @param connection Instance of the FNEPeerConnection class. + */ + void logSpanningTree(FNEPeerConnection* connection = nullptr); + /** * @brief Erases a stream ID from the given peer ID connection. * @param peerId Peer ID. @@ -673,6 +448,12 @@ namespace network * @returns bool True, if the peer affiliations were deleted, otherwise false. */ bool erasePeerAffiliations(uint32_t peerId); + /** + * @brief Helper to disconnect a downstream peer. + * @param peerId Peer ID. + * @param connection Instance of the FNEPeerConnection class. + */ + void disconnectPeer(uint32_t peerId, FNEPeerConnection* connection); /** * @brief Helper to erase the peer from the peers list. * @note This does not delete or otherwise free the FNEConnection instance! @@ -680,6 +461,12 @@ namespace network * @returns bool True, if peer was deleted, otherwise false. */ void erasePeer(uint32_t peerId); + /** + * @brief Helper to determine if the peer is local to this master. + * @param peerId Peer ID. + * @returns bool True, if peer is local, otherwise false. + */ + bool isPeerLocal(uint32_t peerId); /** * @brief Helper to find the unit registration for the given source ID. @@ -704,60 +491,209 @@ namespace network void setupRepeaterLogin(uint32_t peerId, uint32_t streamId, FNEPeerConnection* connection); /** - * @brief Helper to send the ACL lists to the specified peer in a separate thread. + * @brief Helper to process an In-Call Control message. + * @param command In-Call Control Command. + * @param subFunc Network Sub-Function. + * @param dstId Destination ID. + * @param slotNo Slot Number. + * @param peerId Peer ID. + * @param ssrc RTP synchronization source ID. + * @param streamId Stream ID for this message. + */ + void processInCallCtrl(network::NET_ICC::ENUM command, network::NET_SUBFUNC::ENUM subFunc, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + + /** + * @brief Helper to send the network metadata to the specified peer in a separate thread. * @param peerId Peer ID. */ - void peerACLUpdate(uint32_t peerId); + void peerMetadataUpdate(uint32_t peerId); /** - * @brief Entry point to send the ACL lists to the specified peer in a separate thread. - * @param req Instance of the ACLUpdateRequest structure. + * @brief Entry point to send the network metadata to the specified peer in a separate thread. + * @param req Instance of the MetadataUpdateRequest structure. */ - static void taskACLUpdate(ACLUpdateRequest* req); + static void taskMetadataUpdate(MetadataUpdateRequest* req); + + /* + ** ACL Message Writing + */ /** * @brief Helper to send the list of whitelisted RIDs to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the active/whitelisted RIDs message. + * The message is variable bytes in length. This layout does not apply for peer replication + * messages, as those messages are a packet buffered message of the entire RID ACL file. + * + * The RID ACL is chunked and sent in blocks of a maximum of 50 RIDs per message. + * + * Each radio ID ACL entry is 4 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Radio ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param peerId Peer ID. * @param streamId Stream ID for this message. - * @param sendISSI Flag indicating the RID transfer is to an external peer via ISSI. + * @param sendReplica Flag indicating the RID transfer is to an neighbor replica peer. */ - void writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool sendISSI); + void writeWhitelistRIDs(uint32_t peerId, uint32_t streamId, bool sendReplica); /** * @brief Helper to send the list of blacklisted RIDs to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the deactivated/blacklisted RIDs message. + * The message is variable bytes in length. + * + * The RID ACL is chunked and sent in blocks of a maximum of 50 RIDs per message. + * + * Each radio ID ACL entry is 4 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Radio ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param peerId Peer ID. * @param streamId Stream ID for this message. */ void writeBlacklistRIDs(uint32_t peerId, uint32_t streamId); /** * @brief Helper to send the list of active TGIDs to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the active TGs message. + * The message is variable bytes in length. This layout does not apply for peer replication + * messages, as those messages are a packet buffered message of the entire talkgroup ACL file. + * + * Each talkgroup ACL entry is 5 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Talkgroup ID |N|A| Slot | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * N = Non-Preferred Flag + * A = Affiliated Flag + * + * \endcode * @param peerId Peer ID. * @param streamId Stream ID for this message. - * @param sendISSI Flag indicating the TGID transfer is to an external peer via ISSI. + * @param sendReplica Flag indicating the TGID transfer is to an neighbor replica peer. */ - void writeTGIDs(uint32_t peerId, uint32_t streamId, bool sendISSI); + void writeTGIDs(uint32_t peerId, uint32_t streamId, bool sendReplica); /** * @brief Helper to send the list of deactivated TGIDs to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the deactivated TGs message. + * The message is variable bytes in length. + * + * Each talkgroup ACL entry is 5 bytes. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Talkgroup ID | R | Slot | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param peerId Peer ID. * @param streamId Stream ID for this message. */ void writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId); /** * @brief Helper to send the list of peers to the specified peer. + * @note This doesn't have a data layout document because it is *only* sent as a packet buffered message. * @param peerId Peer ID. * @param streamId Stream ID for this message. */ void writePeerList(uint32_t peerId, uint32_t streamId); - + /** + * @brief Helper to send the HA parameters to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the HA parameters message. + * The message is variable bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Total length of all included entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: IP Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode + * @param peerId Peer ID. + * @param streamId Stream ID for this message. + * @param sendReplica Flag indicating the HA transfer is to an neighbor replica peer. + */ + void writeHAParameters(uint32_t peerId, uint32_t streamId, bool sendReplica); + + /** + * @brief Helper to send a network tree disconnect to the specified peer. + * This will cause the peer to issue a link disconnect to the offending peer to prevent network loops. + * \code{.unparsed} + * Below is the representation of the data layout for the tree disconnect message. + * The message is 4 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Offending Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode + * @param peerId Peer ID. + * @param offendingPeerId Offending Peer ID. + */ + void writeTreeDisconnect(uint32_t peerId, uint32_t offendingPeerId); + /** * @brief Helper to send a In-Call Control command to the specified peer. + * \code{.unparsed} + * Below is the representation of the data layout for the In-Call control message. + * The message is 15 bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Peer ID | ICC Command | Destination | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination ID | Slot | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode * @param peerId Peer ID. * @param streamId Stream ID for this message. * @param subFunc Network Sub-Function. * @param command In-Call Control Command. * @param dstId Destination ID. * @param slotNo DMR slot. + * @param systemReq Flag indicating the ICC request is a system generated one not a automatic RID rule generated one. + * @param toUpstream Flag indicating the ICC request is directed at an upstream peer. + * @param ssrc RTP synchronization source ID. */ bool writePeerICC(uint32_t peerId, uint32_t streamId, NET_SUBFUNC::ENUM subFunc = NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, - NET_ICC::ENUM command = NET_ICC::NOP, uint32_t dstId = 0U, uint8_t slotNo = 0U); + NET_ICC::ENUM command = NET_ICC::NOP, uint32_t dstId = 0U, uint8_t slotNo = 0U, bool systemReq = false, bool toUpstream = false, + uint32_t ssrc = 0U); + + /* + ** Generic Message Writing + */ /** * @brief Helper to send a data message to the specified peer with a explicit packet sequence. @@ -768,12 +704,26 @@ namespace network * @param length Length of buffer. * @param pktSeq RTP packet sequence for this message. * @param streamId Stream ID for this message. + * @param incPktSeq Flag indicating the message should increment the packet sequence after transmission. + */ + bool writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, + uint16_t pktSeq, uint32_t streamId, bool incPktSeq = false) const; + /** + * @brief Helper to queue a data message to the specified peer with a explicit packet sequence. + * @param[in] buffers Buffer to contain queued messages. + * @param peerId Destination Peer ID. + * @param ssrc RTP synchronization source ID. + * @param opcode FNE network opcode pair. + * @param[in] data Buffer containing message to send to peer. + * @param length Length of buffer. + * @param pktSeq RTP packet sequence for this message. + * @param streamId Stream ID for this message. * @param queueOnly Flag indicating this message should be queued for transmission. * @param incPktSeq Flag indicating the message should increment the packet sequence after transmission. * @param directWrite Flag indicating this message should be immediately directly written. */ - bool writePeer(uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, - uint16_t pktSeq, uint32_t streamId, bool queueOnly, bool incPktSeq = false, bool directWrite = false) const; + bool writePeerQueue(udp::BufferQueue* buffers, uint32_t peerId, uint32_t ssrc, FrameQueue::OpcodePair opcode, + const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool incPktSeq = false) const; /** * @brief Helper to send a command message to the specified peer. @@ -821,6 +771,10 @@ namespace network */ bool writePeerNAK(uint32_t peerId, const char* tag, NET_CONN_NAK_REASON reason, sockaddr_storage& addr, uint32_t addrLen); + /* + ** Internal KMM Callback. + */ + /** * @brief Helper to process a FNE KMM TEK response. * @param ki Key Item. diff --git a/src/fne/network/FNEPeerConnection.h b/src/fne/network/FNEPeerConnection.h new file mode 100644 index 000000000..55888717d --- /dev/null +++ b/src/fne/network/FNEPeerConnection.h @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file FNEPeerConnection.h + * @ingroup fne_network + */ +#if !defined(__FNE_PEER_CONNECTION_H__) +#define __FNE_PEER_CONNECTION_H__ + +#include "fne/Defines.h" +#include "common/network/BaseNetwork.h" + +#include +#include + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents an peer connection to the FNE. + * @ingroup fne_network + */ + class HOST_SW_API FNEPeerConnection : public RTPStreamMultiplex { + public: + auto operator=(FNEPeerConnection&) -> FNEPeerConnection& = delete; + auto operator=(FNEPeerConnection&&) -> FNEPeerConnection& = delete; + FNEPeerConnection(FNEPeerConnection&) = delete; + + /** + * @brief Initializes a new instance of the FNEPeerConnection class. + */ + FNEPeerConnection() : + RTPStreamMultiplex(), + m_id(0U), + m_masterId(0U), + m_ccPeerId(0U), + m_socketStorage(), + m_sockStorageLen(0U), + m_address(), + m_port(), + m_salt(0U), + m_connected(false), + m_connectionState(NET_STAT_INVALID), + m_pingsReceived(0U), + m_lastPing(0U), + m_missedMetadataUpdates(0U), + m_hasCallPriority(false), + m_isNeighborFNEPeer(false), + m_isReplica(false), + m_isConventionalPeer(false), + m_isSysView(false), + m_config(), + m_peerLockMtx() + { + /* stub */ + } + /** + * @brief Initializes a new instance of the FNEPeerConnection class. + * @param id Unique ID of this modem on the network. + * @param socketStorage + * @param sockStorageLen + */ + FNEPeerConnection(uint32_t id, sockaddr_storage& socketStorage, uint32_t sockStorageLen) : + RTPStreamMultiplex(), + m_id(id), + m_masterId(0U), + m_ccPeerId(0U), + m_socketStorage(socketStorage), + m_sockStorageLen(sockStorageLen), + m_address(udp::Socket::address(socketStorage)), + m_port(udp::Socket::port(socketStorage)), + m_salt(0U), + m_connected(false), + m_connectionState(NET_STAT_INVALID), + m_pingsReceived(0U), + m_lastPing(0U), + m_missedMetadataUpdates(0U), + m_hasCallPriority(false), + m_isNeighborFNEPeer(false), + m_isReplica(false), + m_isConventionalPeer(false), + m_isSysView(false), + m_config(), + m_peerLockMtx() + { + assert(id > 0U); + assert(sockStorageLen > 0U); + assert(!m_address.empty()); + assert(m_port > 0U); + } + + /** + * @brief Returns the identity with qualifier symbols. + * @return std::string Identity with qualifier. + */ + std::string identWithQualifier() const + { + if (isSysView()) + return "@" + identity(); + if (isReplica()) + return "%" + identity(); + if (isNeighborFNEPeer()) + return "+" + identity(); + + return " " + m_identity; + } + + /** + * @brief Lock the peer. + */ + inline void lock() const { m_peerLockMtx.lock(); } + /** + * @brief Unlock the peer. + */ + inline void unlock() const { m_peerLockMtx.unlock(); } + + public: + /** + * @brief Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, id); + /** + * @brief Master Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, masterId); + /** + * @brief Peer Identity. + */ + DECLARE_PROPERTY_PLAIN(std::string, identity); + + /** + * @brief Control Channel Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, ccPeerId); + + /** + * @brief Unix socket storage containing the connected address. + */ + DECLARE_PROPERTY_PLAIN(sockaddr_storage, socketStorage); + /** + * @brief Length of the sockaddr_storage structure. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, sockStorageLen); + + /** + * @brief */ + DECLARE_PROPERTY_PLAIN(std::string, address); + /** + * @brief Port number peer connected with. + */ + DECLARE_PROPERTY_PLAIN(uint16_t, port); + + /** + * @brief Salt value used for peer authentication. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, salt); + + /** + * @brief Flag indicating whether or not the peer is connected. + */ + DECLARE_PROPERTY_PLAIN(bool, connected); + /** + * @brief Connection state. + */ + DECLARE_PROPERTY_PLAIN(NET_CONN_STATUS, connectionState); + + /** + * @brief Number of pings received. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, pingsReceived); + /** + * @brief Last ping received. + */ + DECLARE_PROPERTY_PLAIN(uint64_t, lastPing); + + /** + * @brief Number of missed network metadata updates. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, missedMetadataUpdates); + + /** + * @brief Flag indicating this connection has call priority. + */ + DECLARE_PROPERTY_PLAIN(bool, hasCallPriority); + + /** + * @brief Flag indicating this connection is from an downstream neighbor FNE peer. + */ + DECLARE_PROPERTY_PLAIN(bool, isNeighborFNEPeer); + /** + * @brief Flag indicating this connection is from a neighbor FNE peer that is replica enabled. + */ + DECLARE_PROPERTY_PLAIN(bool, isReplica); + + /** + * @brief Flag indicating this connection is from an conventional peer. + */ + DECLARE_PROPERTY_PLAIN(bool, isConventionalPeer); + /** + * @brief Flag indicating this connection is from an SysView peer. + */ + DECLARE_PROPERTY_PLAIN(bool, isSysView); + + /** + * @brief JSON objecting containing peer configuration information. + */ + DECLARE_PROPERTY_PLAIN(json::object, config); + + private: + mutable std::mutex m_peerLockMtx; + }; +} // namespace network + +#endif // __FNE_PEER_CONNECTION_H__ diff --git a/src/fne/network/HAParameters.h b/src/fne/network/HAParameters.h new file mode 100644 index 000000000..42137eff7 --- /dev/null +++ b/src/fne/network/HAParameters.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file HAParameters.h + * @ingroup fne_network + */ +#if !defined(__HA_PARAMETERS_H__) +#define __HA_PARAMETERS_H__ + +#include "fne/Defines.h" + +namespace network +{ + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents high availiability parameter data. + * @ingroup fne_network + */ + struct HAParameters { + public: + /** + * @brief Initializes a new instance of the HAParameters class + */ + HAParameters() : + peerId(0U), + masterIP(0U), + masterPort(TRAFFIC_DEFAULT_PORT) + { + /* stub **/ + } + + /** + * @brief Initializes a new instance of the HAParameters class + * @param peerId Peer ID. + * @param ipAddr Master IP Address. + * @param port Master Port. + */ + HAParameters(uint32_t peerId, uint32_t ipAddr, uint16_t port) : + peerId(peerId), + masterIP(ipAddr), + masterPort(port) + { + /* stub */ + } + + /** + * @brief Equals operator. Copies this HAParameters to another HAParameters. + * @param data Instance of HAParameters to copy. + */ + HAParameters& operator=(const HAParameters& data) + { + if (this != &data) { + peerId = data.peerId; + masterIP = data.masterIP; + masterPort = data.masterPort; + } + + return *this; + } + + public: + uint32_t peerId; //!< Peer ID. + + uint32_t masterIP; //!< Master IP Address. + uint16_t masterPort; //!< Master Port. + }; +} // namespace network + +#endif // __HA_PARAMETERS_H__ diff --git a/src/fne/network/P25OTARService.cpp b/src/fne/network/P25OTARService.cpp new file mode 100644 index 000000000..3c957f671 --- /dev/null +++ b/src/fne/network/P25OTARService.cpp @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/p25/kmm/KMMFactory.h" +#include "common/Log.h" +#include "common/Thread.h" +#include "common/Utils.h" +#include "network/FNENetwork.h" +#include "network/P25OTARService.h" +#include "HostFNE.h" + +using namespace network; +using namespace network::callhandler; +using namespace network::callhandler::packetdata; +using namespace network::frame; +using namespace network::udp; +using namespace p25; +using namespace p25::defines; +using namespace p25::crypto; +using namespace p25::kmm; + +#include +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +// Macro helper to verbose log a generic KMM. +#define VERBOSE_LOG_KMM(_PCKT_STR, __LLID) \ + if (m_verbose) { \ + LogInfoEx(LOG_P25, "KMM, %s, llId = %u", _PCKT_STR.c_str(), __LLID); \ + } + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define MAX_THREAD_CNT 4U + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the P25OTARService class. */ + +P25OTARService::P25OTARService(FNENetwork* network, P25PacketData* packetData, bool debug, bool verbose) : + m_socket(nullptr), + m_frameQueue(nullptr), + m_threadPool(MAX_THREAD_CNT, "otar"), + m_network(network), + m_packetData(packetData), + m_rsiMessageNumber(), + m_allowNoUKEKRekey(false), + m_debug(debug), + m_verbose(verbose) +{ + assert(network != nullptr); + assert(packetData != nullptr); +} + +/* Finalizes a instance of the P25OTARService class. */ + +P25OTARService::~P25OTARService() +{ + if (m_frameQueue != nullptr) + delete m_frameQueue; + if (m_socket != nullptr) + delete m_socket; +} + +/* Helper used to process KMM frames from PDU data. */ + +bool P25OTARService::processDLD(const uint8_t* data, uint32_t len, uint32_t llId, uint8_t n, bool encrypted) +{ + m_packetData->write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, n, llId, false); + + if (m_debug) + Utils::dump(1U, "P25OTARService::processDLD(), KMM Network Message", data, len); + + uint32_t payloadSize = 0U; + UInt8Array pduUserData = processKMM(data, len, llId, encrypted, &payloadSize); + if (pduUserData == nullptr) + return false; + + // handle DLD encrypted KMM frame + if (encrypted) { + // read crypto parameters from KMM header + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { + mi[i] = data[i]; + } + + uint8_t algoId = data[9U]; + uint16_t kid = GET_UINT16(data, 10U); + + // re-encrypt the KMM response + pduUserData = cryptKMM(algoId, kid, mi, pduUserData.get(), payloadSize, true); + if (pduUserData == nullptr) { + LogError(LOG_P25, P25_KMM_STR ", unable to encrypt KMM response, algoId = $%02X, kID = $%04X", algoId, kid); + return false; + } + } + + m_packetData->write_PDU_KMM(pduUserData.get(), payloadSize, llId, encrypted); + return true; +} + +/* Updates the timer by the passed number of milliseconds. */ + +void P25OTARService::clock(uint32_t ms) +{ + if (m_socket != nullptr) { + sockaddr_storage address; + uint32_t addrLen; + int length = 0U; + + // read message + UInt8Array buffer = m_frameQueue->read(length, address, addrLen); + if (length > 0) { + if (m_debug) + Utils::dump(1U, "P25OTARService::clock(), KMM Network Message", buffer.get(), length); + + OTARPacketRequest* req = new OTARPacketRequest(); + req->obj = this; + + req->address = address; + req->addrLen = addrLen; + + req->length = length; + req->buffer = new uint8_t[length]; + ::memcpy(req->buffer, buffer.get(), length); + + // enqueue the task + if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { + LogError(LOG_P25, "Failed to task enqueue KMM network packet request, %s:%u", + udp::Socket::address(address).c_str(), udp::Socket::port(address)); + if (req != nullptr) { + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } + } + } + } +} + +/* Opens a connection to the OTAR port. */ + +bool P25OTARService::open(const std::string& address, uint16_t port) +{ + m_socket = new Socket(port); + m_frameQueue = new RawFrameQueue(m_socket, m_debug); + + sockaddr_storage addr; + uint32_t addrLen; + if (udp::Socket::lookup(address, port, addr, addrLen) != 0) + addrLen = 0U; + + if (addrLen > 0U) { + m_threadPool.start(); + + if (m_socket != nullptr) { + return m_socket->open(addr); + } + } + + return false; +} + +/* Closes the connection to the OTAR port. */ + +void P25OTARService::close() +{ + if (m_socket != nullptr) { + m_threadPool.stop(); + m_threadPool.wait(); + + m_socket->close(); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Process a data frames from the network. */ + +void P25OTARService::taskNetworkRx(OTARPacketRequest* req) +{ + if (req != nullptr) { + P25OTARService* network = static_cast(req->obj); + if (network == nullptr) { + delete req; + return; + } + + if (req->length >= 13) { + // read crypto parameters from KMM header + uint8_t mfId = req->buffer[1U]; + uint8_t algoId = req->buffer[2U]; + uint16_t kid = GET_UINT16(req->buffer, 3U); + + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { + mi[i] = req->buffer[4U + i]; + } + + // KMM frame + UInt8Array buffer = std::make_unique(req->length - 13U); + ::memset(buffer.get(), 0x00U, req->length - 13U); + ::memcpy(buffer.get(), req->buffer + 13U, req->length - 13U); + + bool encrypted = (mfId != ALGO_UNENCRYPT); + if (encrypted) { + buffer = network->cryptKMM(algoId, kid, mi, buffer.get(), req->length - 13U, false); + if (buffer == nullptr) { + LogError(LOG_P25, P25_KMM_STR ", unable to decrypt KMM, algoId = $%02X, kID = $%04X", algoId, kid); + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + return; + } + } + + uint32_t payloadSize = 0U; + UInt8Array pduUserData = network->processKMM(buffer.get(), req->length - 13U, 0U, false, &payloadSize); + + if (encrypted) { + // re-encrypt the KMM response + pduUserData = network->cryptKMM(algoId, kid, mi, pduUserData.get(), payloadSize, true); + if (pduUserData == nullptr) { + LogError(LOG_P25, P25_KMM_STR ", unable to encrypt KMM response, algoId = $%02X, kID = $%04X", algoId, kid); + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + return; + } + } + + network->m_frameQueue->write(pduUserData.get(), payloadSize, req->address, req->addrLen); + } + + if (req->buffer != nullptr) + delete[] req->buffer; + delete req; + } +} + +/* Encrypt/decrypt KMM frame. */ + +UInt8Array P25OTARService::cryptKMM(uint8_t algoId, uint16_t kid, uint8_t* mi, const uint8_t* buffer, uint32_t len, bool encrypt) +{ + assert(buffer != nullptr); + + P25Crypto crypto; + if (!encrypt) + crypto.setMI(mi); + else { + crypto.generateMI(); + crypto.getMI(mi); + } + + UInt8Array outBuffer = std::make_unique(len); + ::memset(outBuffer.get(), 0x00U, len); + ::memcpy(outBuffer.get(), buffer, len); + + if (algoId == P25DEF::ALGO_UNENCRYPT) + return outBuffer; + + /* + ** bryanb: Architecturally this is a problem. Because KMF services would essentially be limited to the local FNE + ** because we aren't performing FNE KEY_REQ's to upstream peer'ed FNEs to find the key used to encrypt the KMM. + */ + + ::EKCKeyItem keyItem = m_network->m_cryptoLookup->find(kid); + if (!keyItem.isInvalid()) { + uint8_t key[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + uint8_t keyLength = keyItem.getKey(key); + + if (m_network->m_debug) { + LogDebugEx(LOG_P25, "P25OTARService::cryptKMM()", "keyLength = %u", keyLength); + Utils::dump(1U, "P25OTARService::cryptKMM(), Key", key, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + + LogInfoEx(LOG_P25, P25_KMM_STR ", algId = $%02X, kID = $%04X", algoId, kid); + crypto.setKey(key, keyLength); + crypto.generateKeystream(); + + switch (algoId) { + case P25DEF::ALGO_AES_256: + crypto.cryptAES_PDU(outBuffer.get(), len); + return outBuffer; + default: + LogError(LOG_P25, "unsupported KEK algorithm, algoId = $%02X", algoId); + break; + } + } + + return nullptr; +} + +/* Helper used to process KMM frames. */ + +UInt8Array P25OTARService::processKMM(const uint8_t* data, uint32_t len, uint32_t llId, bool encrypted, uint32_t* payloadSize) +{ + if (payloadSize != nullptr) + *payloadSize = 0U; + + UInt8Array buffer = std::make_unique(len); + ::memset(buffer.get(), 0x00U, len); + ::memcpy(buffer.get(), data, len); + + // handle DLD encrypted KMM frame + if (encrypted) { + // read crypto parameters from KMM header + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + for (uint8_t i = 0; i < MI_LENGTH_BYTES; i++) { + mi[i] = data[i]; + } + + uint8_t algoId = data[9U]; + uint16_t kid = GET_UINT16(data, 10U); + + // decrypt frame before processing + buffer = cryptKMM(algoId, kid, mi, data + 10U, len - 10U, false); + if (buffer == nullptr) { + LogError(LOG_P25, P25_KMM_STR ", unable to decrypt KMM, algoId = $%02X, kID = $%04X", algoId, kid); + return nullptr; + } + + if (m_debug) + Utils::dump(1U, "P25OTARService::processKMM(), (Decrypted) KMM Network Message", buffer.get(), len); + } + + std::unique_ptr frame = KMMFactory::create(buffer.get()); + if (frame == nullptr) { + LogWarning(LOG_P25, P25_KMM_STR ", undecodable KMM packet"); + return nullptr; + } + + if (llId == 0U) { + llId = frame->getSrcLLId(); + } + + // does this llId have a RSI message number setup? + if (m_rsiMessageNumber.find(llId) == m_rsiMessageNumber.end()) { + m_rsiMessageNumber[llId] = 0U; + } else { + // roll message number + uint16_t mn = m_rsiMessageNumber[llId]; + mn++; + + m_rsiMessageNumber[llId] = mn; + } + + switch (frame->getMessageId()) { + case KMM_MessageType::HELLO: + { + KMMHello* kmm = static_cast(frame.get()); + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, flag = $%02X", kmm->toString().c_str(), + llId, kmm->getFlag()); + switch (kmm->getFlag()) { + case KMM_HelloFlag::REKEY_REQUEST_UKEK: + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, rekey requested with UKEK, llId = %u", kmm->toString().c_str(), llId); + break; + case KMM_HelloFlag::REKEY_REQUEST_NO_UKEK: + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, rekey requested with no UKEK, llId = %u", kmm->toString().c_str(), llId); + break; + } + } + + // respond with No-Service if KMF services are disabled +// if (!m_network->m_kmfServicesEnabled) + return write_KMM_NoService(llId, kmm->getSrcLLId(), payloadSize); + } + break; + + case KMM_MessageType::NAK: + { + KMMNegativeAck* kmm = static_cast(frame.get()); + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, messageId = $%02X, messageNo = %u, status = $%02X", kmm->toString().c_str(), + llId, kmm->getMessageId(), kmm->getMessageNumber(), kmm->getStatus()); + logResponseStatus(llId, kmm->toString(), kmm->getStatus()); + } + break; + + case KMM_MessageType::REKEY_ACK: + { + KMMRekeyAck* kmm = static_cast(frame.get()); + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, messageId = $%02X, numOfStatus = %u", kmm->toString().c_str(), + llId, kmm->getMessageId(), kmm->getNumberOfKeyStatus()); + + if (kmm->getNumberOfKeyStatus() > 0U) { + for (auto entry : kmm->getKeyStatus()) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, algId = $%02X, kId = $%04X, status = $%02X", kmm->toString().c_str(), + llId, entry.algId(), entry.kId(), entry.status()); + logResponseStatus(llId, kmm->toString(), entry.status()); + } + } + } + break; + + case KMM_MessageType::DEREG_CMD: + { + KMMDeregistrationCommand* kmm = static_cast(frame.get()); + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u", kmm->toString().c_str(), + llId); + + // respond with No-Service if KMF services are disabled + if (!m_network->m_kmfServicesEnabled) + return write_KMM_NoService(llId, kmm->getSrcLLId(), payloadSize); + else + return write_KMM_Dereg_Response(llId, kmm->getSrcLLId(), payloadSize); + } + break; + case KMM_MessageType::REG_RSP: + { + KMMRegistrationResponse* kmm = static_cast(frame.get()); + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, status = $%02X", kmm->toString().c_str(), + llId, kmm->getStatus()); + logResponseStatus(llId, kmm->toString(), kmm->getStatus()); + } + break; + + case KMM_MessageType::UNABLE_TO_DECRYPT: + { + KMMUnableToDecrypt* kmm = static_cast(frame.get()); + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, status = $%02X", kmm->toString().c_str(), + llId, kmm->getStatus()); + logResponseStatus(llId, kmm->toString(), kmm->getStatus()); + } + + default: + break; + } // switch (frame->getMessageId()) + + return nullptr; +} + +/* Helper used to return a Rekey-Command KMM to the calling SU. */ + +UInt8Array P25OTARService::write_KMM_Rekey_Command(uint32_t llId, uint32_t kmmRSI, uint8_t flags, uint32_t* payloadSize) +{ + uint8_t mi[MI_LENGTH_BYTES]; + ::memset(mi, 0x00U, MI_LENGTH_BYTES); + + uint16_t mn = 0U; + if (m_rsiMessageNumber.find(llId) != m_rsiMessageNumber.end()) { + mn = m_rsiMessageNumber[llId]; + } + + P25Crypto crypto; + crypto.generateMI(); + crypto.getMI(mi); + + KMMRekeyCommand outKmm = KMMRekeyCommand(); + + /* + ** bryanb: Architecturally this is a problem. Because KMF services would essentially be limited to the local FNE + ** because we aren't performing FNE KEY_REQ's to upstream peer'ed FNEs to find the key used to encrypt the KMM. + */ + + uint8_t kekKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(kekKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + + uint8_t kekAlgId = P25DEF::ALGO_UNENCRYPT; + uint16_t kekKId = 0U; + + ::EKCKeyItem keyItem = m_network->m_cryptoLookup->findUKEK(kmmRSI); + if (!keyItem.isInvalid()) { + uint8_t keyLength = keyItem.getKey(kekKey); + + kekAlgId = keyItem.algId(); + kekKId = keyItem.kId(); + + if (m_network->m_debug) { + LogDebugEx(LOG_P25, "P25OTARService::cryptKMM()", "keyLength = %u", keyLength); + Utils::dump(1U, "P25OTARService::cryptKMM(), Key", kekKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + } + } + else { + if (!m_allowNoUKEKRekey) { + LogError(LOG_P25, P25_KMM_STR", %s, aborting rekey, no KEK to keyload with, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + return nullptr; + } else { + LogWarning(LOG_P25, P25_KMM_STR", %s, WARNING WARNING WARNING, rekey without KEK enabled, WARNING WARNING WARNING, keys transmitted in the clear, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + } + } + + outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + outKmm.setSrcLLId(WUID_FNE); + outKmm.setDstLLId(kmmRSI); + + outKmm.setMACType(KMM_MAC::ENH_MAC); + outKmm.setMACAlgId(kekAlgId); + outKmm.setMACKId(kekKId); + outKmm.setMACFormat(KMM_MAC_FORMAT_CBC); + + outKmm.setMessageNumber(mn); + + outKmm.setAlgId(kekAlgId); + outKmm.setKId(kekKId); + + KeysetItem ks; + ks.keysetId(1U); + ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys + if (kekAlgId != P25DEF::ALGO_UNENCRYPT) + ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES); + else + ks.keyLength(P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + + for (EKCKeyItem keyItem : m_network->m_cryptoLookup->keys()) { + if (keyItem.algId() != ALGO_AES_256) { + LogWarning(LOG_P25, P25_KMM_STR", %s, ignoring kId = %u, is not an AES-256 key, llId = %u, RSI = %u", outKmm.toString().c_str(), + keyItem.kId(), outKmm.getSrcLLId(), outKmm.getDstLLId()); + continue; + } + + uint8_t key[P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES]; + ::memset(key, 0x00U, P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES); + uint8_t keyLength = keyItem.getKey(key); + + // encrypt key + UInt8Array wrappedKey = nullptr; + if (kekAlgId != P25DEF::ALGO_UNENCRYPT) { + wrappedKey = crypto.cryptAES_TEK(kekKey, key, keyLength); + keyLength = P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES; + } else { + wrappedKey = std::make_unique(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES); + ::memcpy(wrappedKey.get(), key, keyLength); + } + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(KEY_FORMAT_TEK); + ki.kId((uint16_t)keyItem.kId()); + ki.sln((uint16_t)keyItem.sln()); + ki.setKey(wrappedKey.get(), keyLength); + + ks.push_back(ki); + } + + if (ks.keys().size() == 0U) { + LogWarning(LOG_P25, P25_KMM_STR", %s, aborting rekey, no keys to keyload, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + return nullptr; + } + + std::vector keysets; + keysets.push_back(ks); + + outKmm.setKeysets(keysets); + + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u, keyCount = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId(), ks.keys().size()); + } + + if (payloadSize != nullptr) + *payloadSize = outKmm.length(); + + UInt8Array kmmFrame = std::make_unique(outKmm.length()); + outKmm.encode(kmmFrame.get()); + outKmm.generateMAC(kekKey, kmmFrame.get()); + return kmmFrame; +} + +/* Helper used to return a Registration-Command KMM to the calling SU. */ + +UInt8Array P25OTARService::write_KMM_Reg_Command(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize) +{ + KMMRegistrationCommand outKmm = KMMRegistrationCommand(); + outKmm.setSrcLLId(WUID_FNE); + outKmm.setDstLLId(kmmRSI); + + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + } + + if (payloadSize != nullptr) + *payloadSize = outKmm.length(); + + UInt8Array kmmFrame = std::make_unique(outKmm.length()); + outKmm.encode(kmmFrame.get()); + return kmmFrame; +} + +/* Helper used to return a Deregistration-Response KMM to the calling SU. */ + +UInt8Array P25OTARService::write_KMM_Dereg_Response(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize) +{ + KMMDeregistrationResponse outKmm = KMMDeregistrationResponse(); + outKmm.setSrcLLId(WUID_FNE); + outKmm.setDstLLId(kmmRSI); + outKmm.setStatus(KMM_Status::CMD_PERFORMED); + + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + } + + if (payloadSize != nullptr) + *payloadSize = outKmm.length(); + + UInt8Array kmmFrame = std::make_unique(outKmm.length()); + outKmm.encode(kmmFrame.get()); + return kmmFrame; +} + +/* Helper used to return a No-Service KMM to the calling SU. */ + +UInt8Array P25OTARService::write_KMM_NoService(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize) +{ + KMMNoService outKmm = KMMNoService(); + outKmm.setSrcLLId(WUID_FNE); + outKmm.setDstLLId(kmmRSI); + + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + } + + if (payloadSize != nullptr) + *payloadSize = outKmm.length(); + + UInt8Array kmmFrame = std::make_unique(outKmm.length()); + outKmm.encode(kmmFrame.get()); + return kmmFrame; +} + +/* Helper used to return a Zeroize KMM to the calling SU. */ + +UInt8Array P25OTARService::write_KMM_Zeroize(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize) +{ + KMMZeroize outKmm = KMMZeroize(); + outKmm.setSrcLLId(WUID_FNE); + outKmm.setDstLLId(kmmRSI); + + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, llId = %u, RSI = %u", outKmm.toString().c_str(), + outKmm.getSrcLLId(), outKmm.getDstLLId()); + } + + if (payloadSize != nullptr) + *payloadSize = outKmm.length(); + + UInt8Array kmmFrame = std::make_unique(outKmm.length()); + outKmm.encode(kmmFrame.get()); + return kmmFrame; +} + +/* Helper used to log a KMM response. */ + +void P25OTARService::logResponseStatus(uint32_t llId, std::string kmmString, uint8_t status) +{ + switch (status) { + case KMM_Status::CMD_PERFORMED: + if (m_verbose) { + LogInfoEx(LOG_P25, P25_KMM_STR ", %s, command performed, llId = %u", kmmString.c_str(), llId); + } + break; + case KMM_Status::CMD_NOT_PERFORMED: + LogWarning(LOG_P25, P25_KMM_STR ", %s, command not performed, llId = %u", kmmString.c_str(), llId); + break; + + case KMM_Status::ITEM_NOT_EXIST: + LogWarning(LOG_P25, P25_KMM_STR ", %s, item does not exist, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::INVALID_MSG_ID: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid message ID, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::INVALID_MAC: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid auth code, llId = %u", kmmString.c_str(), llId); + break; + + case KMM_Status::OUT_OF_MEMORY: + LogWarning(LOG_P25, P25_KMM_STR ", %s, out of memory, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::FAILED_TO_DECRYPT: + LogWarning(LOG_P25, P25_KMM_STR ", %s, failed to decrypt message, llId = %u", kmmString.c_str(), llId); + break; + + case KMM_Status::INVALID_MSG_NUMBER: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid message number, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::INVALID_KID: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid key ID, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::INVALID_ALGID: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid algorithm ID, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::INVALID_MFID: + LogWarning(LOG_P25, P25_KMM_STR ", %s, invalid manufacturer ID, llId = %u", kmmString.c_str(), llId); + break; + + case KMM_Status::MI_ALL_ZERO: + LogWarning(LOG_P25, P25_KMM_STR ", %s, message indicator was all zeros, llId = %u", kmmString.c_str(), llId); + break; + case KMM_Status::KEY_FAIL: + LogWarning(LOG_P25, P25_KMM_STR ", %s, key identified by algo/key is erased, llId = %u", kmmString.c_str(), llId); + break; + + case KMM_Status::UNKNOWN: + default: + LogWarning(LOG_P25, P25_KMM_STR ", llId = %u, status = $%02X; unknown status", llId, status); + break; + } +} diff --git a/src/fne/network/P25OTARService.h b/src/fne/network/P25OTARService.h new file mode 100644 index 000000000..2d8c53e62 --- /dev/null +++ b/src/fne/network/P25OTARService.h @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file P25OTARService.h + * @ingroup fne_network + * @file P25OTARService.cpp + * @ingroup fne_network + */ +#if !defined(__P25_OTAR_SERVICE_H__) +#define __P25_OTAR_SERVICE_H__ + +#include "fne/Defines.h" +#include "common/concurrent/unordered_map.h" +#include "common/p25/P25Defines.h" +#include "common/p25/Crypto.h" +#include "common/network/udp/Socket.h" +#include "common/network/RawFrameQueue.h" +#include "network/FNENetwork.h" +#include "network/callhandler/packetdata/P25PacketData.h" + +namespace network +{ + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents the data required for a OTAR network packet handler thread. + * @ingroup fne_network + */ + struct OTARPacketRequest : thread_t { + sockaddr_storage address; //!< IP Address and Port. + uint32_t addrLen; //!< + int length = 0U; //!< Length of raw data buffer + uint8_t *buffer; //!< Raw data buffer + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the P25 OTAR service. + * @ingroup fne_network + */ + class HOST_SW_API P25OTARService { + public: + /** + * @brief Initializes a new instance of the P25OTARService class. + * @param network Instance of the FNENetwork class. + * @param packetData Instance of the P25PacketData class. + * @param debug Flag indicating whether debug is enabled. + * @param verbose Flag indicating whether verbose logging is enabled. + */ + P25OTARService(FNENetwork* network, network::callhandler::packetdata::P25PacketData* packetData, bool debug, bool verbose); + /** + * @brief Finalizes a instance of the P25OTARService class. + */ + ~P25OTARService(); + + /** + * @brief Helper used to process KMM frames from PDU data. + * @param[in] data Network data buffer. + * @param len Length of data. + * @param encrypted Flag indicating whether or not the KMM frame is encrypted. + * @param llId Logical Link ID. + * @param n Send Sequence Number. + * @param encrypted Flag indicating whether or not the KMM frame is encrypted. + * @returns bool True, if KMM processed, otherwise false. + */ + bool processDLD(const uint8_t* data, uint32_t len, uint32_t llId, uint8_t n, bool encrypted); + + /** + * @brief Updates the timer by the passed number of milliseconds. + * @param ms Number of milliseconds. + */ + void clock(uint32_t ms); + + /** + * @brief Opens a connection to the OTAR port. + * @param address Hostname/IP address to listen on. + * @param port Port number. + * @returns bool True, if connection is opened, otherwise false. + */ + bool open(const std::string& address, uint16_t port); + + /** + * @brief Closes the connection to the OTAR port. + */ + void close(); + + private: + network::udp::Socket* m_socket; + network::RawFrameQueue* m_frameQueue; + + ThreadPool m_threadPool; + + FNENetwork* m_network; + network::callhandler::packetdata::P25PacketData* m_packetData; + + concurrent::unordered_map m_rsiMessageNumber; + + bool m_allowNoUKEKRekey; + + bool m_debug; + bool m_verbose; + + /** + * @brief Entry point to process a given network packet. + * @param arg Instance of the OTARPacketRequest structure. + */ + static void taskNetworkRx(OTARPacketRequest* req); + + /** + * @brief Encrypt/decrypt KMM frame. + * @param[in] algoId Algorithm ID. + * @param[in] kid Key ID. + * @param mi Message Indicator. + * @param[in] buffer KMM frame buffer. + * @param len Length of KMM frame buffer. + * @param encrypt True to encrypt, false to decrypt. + * @returns UInt8Array Buffer containing the encrypted/decrypted KMM frame. + */ + UInt8Array cryptKMM(uint8_t algoId, uint16_t kid, uint8_t* mi, const uint8_t* buffer, uint32_t len, bool encrypt = false); + + /** + * @brief Helper used to process KMM frames. + * @param[in] data Network data buffer. + * @param len Length of data. + * @param encrypted Flag indicating whether or not the KMM frame is encrypted. + * @param llId Logical Link ID. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array processKMM(const uint8_t* data, uint32_t len, uint32_t llId, bool encrypted, uint32_t* payloadSize); + + /** + * @brief Helper used to return a Rekey-Command KMM to the calling SU. + * @param llId Logical Link Address. + * @param kmmRSI KMM Radio Set Identifier. + * @param flags Hello KMM flags. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array write_KMM_Rekey_Command(uint32_t llId, uint32_t kmmRSI, uint8_t flags, uint32_t* payloadSize); + + /** + * @brief Helper used to return a Registration-Command KMM to the calling SU. + * @param llId Logical Link Address. + * @param kmmRSI KMM Radio Set Identifier. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array write_KMM_Reg_Command(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize); + + /** + * @brief Helper used to return a Deregistration-Response KMM to the calling SU. + * @param llId Logical Link Address. + * @param kmmRSI KMM Radio Set Identifier. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array write_KMM_Dereg_Response(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize); + + /** + * @brief Helper used to return a No-Service KMM to the calling SU. + * @param llId Logical Link Address. + * @param kmmRSI KMM Radio Set Identifier. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array write_KMM_NoService(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize); + + /** + * @brief Helper used to return a Zeroize KMM to the calling SU. + * @param llId Logical Link Address. + * @param kmmRSI KMM Radio Set Identifier. + * @param[out] payloadSize Size of the returned KMM payload. + * @returns UInt8Array Buffer containing the processed KMM frame (if any). + */ + UInt8Array write_KMM_Zeroize(uint32_t llId, uint32_t kmmRSI, uint32_t* payloadSize); + + /** + * @brief Helper used to log a KMM response. + * @param llId Logical Link Address. + * @param kmmString + * @param status Status. + */ + void logResponseStatus(uint32_t llId, std::string kmmString, uint8_t status); + }; +} // namespace network + +#endif // __P25_OTAR_SERVICE_H__ diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index e3070e68f..f7c4c0103 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -8,7 +8,7 @@ * */ #include "fne/Defines.h" -#include "common/network/json/json.h" +#include "common/json/json.h" #include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" @@ -41,18 +41,21 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc bool duplex, bool debug, bool dmr, bool p25, bool nxdn, bool analog, bool slot1, bool slot2, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : Network(address, port, localPort, peerId, password, duplex, debug, dmr, p25, nxdn, analog, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), m_attachedKeyRSPHandler(false), - m_blockTrafficToTable(), m_dmrCallback(nullptr), m_p25Callback(nullptr), m_nxdnCallback(nullptr), m_analogCallback(nullptr), + m_netTreeDiscCallback(nullptr), + m_peerReplicaCallback(nullptr), + m_masterPeerId(0U), m_pidLookup(nullptr), - m_peerLink(false), - m_peerLinkSavesACL(false), - m_tgidPkt(true, "Peer-Link, TGID List"), - m_ridPkt(true, "Peer-Link, RID List"), - m_pidPkt(true, "Peer-Link, PID List"), - m_threadPool(WORKER_CNT, "peer") + m_peerReplica(false), + m_peerReplicaSavesACL(false), + m_tgidPkt(true, "Peer Replication, TGID List"), + m_ridPkt(true, "Peer Replication, RID List"), + m_pidPkt(true, "Peer Replication, PID List"), + m_threadPool(WORKER_CNT, "peer"), + m_prevSpanningTreeChildren(0U) { assert(!address.empty()); assert(port > 0U); @@ -104,62 +107,120 @@ void PeerNetwork::close() Network::close(); } -/* Checks if the passed peer ID is blocked from sending to this peer. */ +/* Writes a complete update of this CFNE's active peer list to the network. */ -bool PeerNetwork::checkBlockedPeer(uint32_t peerId) +bool PeerNetwork::writePeerLinkPeers(json::array* peerList) { - if (!m_enabled) + if (peerList == nullptr) return false; - - if (m_blockTrafficToTable.empty()) + if (peerList->size() == 0) return false; - if (std::find(m_blockTrafficToTable.begin(), m_blockTrafficToTable.end(), peerId) != m_blockTrafficToTable.end()) { - if (m_debug) { - ::LogDebugEx(LOG_HOST, "PeerNetwork::checkBlockedPeer()", "PEER %u peerId = %u, blocking traffic", m_peerId, peerId); + if (peerList->size() > 0 && m_peerReplica) { + json::value v = json::value(*peerList); + std::string json = std::string(v.serialize()); + + size_t len = json.length() + 9U; + DECLARE_CHAR_ARRAY(buffer, len); + + ::memcpy(buffer + 0U, TAG_PEER_REPLICA, 4U); + ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); + + PacketBuffer pkt(true, "Peer Replication, Active Peer List"); + pkt.encode((uint8_t*)buffer, len); + + uint32_t streamId = createStreamId(); + LogInfoEx(LOG_REPL, "PEER %u Peer Replication, Active Peer List, blocks %u, streamId = %u", m_peerId, pkt.fragments.size(), streamId); + if (pkt.fragments.size() > 0U) { + for (auto frag : pkt.fragments) { + writeMaster({ NET_FUNC::REPL, NET_SUBFUNC::REPL_ACT_PEER_LIST }, + frag.second->data, FRAG_SIZE, RTP_END_OF_CALL_SEQ, streamId, true); + Thread::sleep(60U); // pace block transmission + } } + + pkt.clear(); return true; } - if (m_debug) { - ::LogDebugEx(LOG_HOST, "PeerNetwork::checkBlockedPeer()", "PEER %u peerId = %u, passing traffic", m_peerId, peerId); - } return false; } -/* Writes a complete update of this CFNE's active peer list to the network. */ +/* Writes a complete update of this CFNE's known spanning tree upstream to the network. */ -bool PeerNetwork::writePeerLinkPeers(json::array* peerList) +bool PeerNetwork::writeSpanningTree(SpanningTree* treeRoot) { - if (peerList == nullptr) + if (treeRoot == nullptr) return false; - if (peerList->size() == 0) + if (treeRoot->m_children.size() == 0 && m_prevSpanningTreeChildren == 0U) return false; - if (peerList->size() > 0 && m_peerLink) { - json::value v = json::value(*peerList); + if ((treeRoot->m_children.size() > 0) || (m_prevSpanningTreeChildren > 0U)) { + json::array jsonArray; + SpanningTree::serializeTree(treeRoot, jsonArray); + + json::value v = json::value(jsonArray); std::string json = std::string(v.serialize()); size_t len = json.length() + 9U; DECLARE_CHAR_ARRAY(buffer, len); - ::memcpy(buffer + 0U, TAG_PEER_LINK, 4U); + ::memcpy(buffer + 0U, TAG_PEER_REPLICA, 4U); ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str()); - PacketBuffer pkt(true, "Peer-Link, Active Peer List"); + PacketBuffer pkt(true, "Network Tree, Tree List"); pkt.encode((uint8_t*)buffer, len); uint32_t streamId = createStreamId(); - LogInfoEx(LOG_NET, "PEER %u Peer-Link, Active Peer List, blocks %u, streamId = %u", m_peerId, pkt.fragments.size(), streamId); + LogInfoEx(LOG_STP, "PEER %u Network Tree, Tree List, blocks %u, streamId = %u", m_peerId, pkt.fragments.size(), streamId); if (pkt.fragments.size() > 0U) { for (auto frag : pkt.fragments) { - writeMaster({ NET_FUNC::PEER_LINK, NET_SUBFUNC::PL_ACT_PEER_LIST }, - frag.second->data, FRAG_SIZE, RTP_END_OF_CALL_SEQ, streamId, false, true); + writeMaster({ NET_FUNC::NET_TREE, NET_SUBFUNC::NET_TREE_LIST }, + frag.second->data, FRAG_SIZE, RTP_END_OF_CALL_SEQ, streamId, true); Thread::sleep(60U); // pace block transmission } } pkt.clear(); + + m_prevSpanningTreeChildren = treeRoot->m_children.size(); + return true; + } + + m_prevSpanningTreeChildren = treeRoot->m_children.size(); + return false; +} + +/* Writes a complete update of this CFNE's HA parameters to the network. */ + +bool PeerNetwork::writeHAParams(std::vector& haParams) +{ + if (haParams.size() == 0) + return false; + + if (haParams.size() > 0 && m_peerReplica) { + uint32_t len = 4U + (haParams.size() * HA_PARAMS_ENTRY_LEN); + DECLARE_UINT8_ARRAY(buffer, len); + + SET_UINT32((len - 4U), buffer, 0U); + + uint32_t offs = 4U; + for (uint8_t i = 0U; i < haParams.size(); i++) { + uint32_t peerId = haParams[i].peerId; + uint32_t ipAddr = haParams[i].masterIP; + uint16_t port = haParams[i].masterPort; + + SET_UINT32(peerId, buffer, offs); + SET_UINT32(ipAddr, buffer, offs + 4U); + SET_UINT16(port, buffer, offs + 8U); + + offs += HA_PARAMS_ENTRY_LEN; + } + + // bryanb: this should probably be packet buffered + writeMaster({ NET_FUNC::REPL, NET_SUBFUNC::REPL_HA_PARAMS }, + buffer, len, RTP_END_OF_CALL_SEQ, createStreamId(), true); + return true; } @@ -196,7 +257,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // enqueue the task if (!m_threadPool.enqueue(new_pooltask(taskNetworkRx, req))) { - LogError(LOG_NET, "Failed to task enqueue network packet request, peerId = %u", peerId); + LogError(LOG_PEER, "Failed to task enqueue network packet request, peerId = %u", peerId); if (req != nullptr) { if (req->buffer != nullptr) delete[] req->buffer; @@ -206,17 +267,17 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_FUNC::PEER_LINK: // Peer Link + case NET_FUNC::REPL: // Peer Replication { switch (opcode.second) { - case NET_SUBFUNC::PL_TALKGROUP_LIST: // Talkgroup List + case NET_SUBFUNC::REPL_TALKGROUP_LIST: // Talkgroup List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; if (m_tgidPkt.decode(data, &decompressed, &decompressedLen)) { if (m_tidLookup == nullptr) { - LogError(LOG_NET, "Talkgroup ID lookup not available yet."); + LogError(LOG_PEER, "Talkgroup ID lookup not available yet."); m_tgidPkt.clear(); delete[] decompressed; break; @@ -229,7 +290,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // randomize filename std::ostringstream s; - if (!m_peerLinkSavesACL) { + if (!m_peerReplicaSavesACL) { std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); @@ -241,7 +302,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco std::string filename = s.str(); std::ofstream file(filename, std::ofstream::out); if (file.fail()) { - LogError(LOG_NET, "Cannot open the talkgroup ID lookup file - %s", filename.c_str()); + LogError(LOG_PEER, "Cannot open the talkgroup ID lookup file - %s", filename.c_str()); m_tgidPkt.clear(); delete[] decompressed; break; @@ -255,8 +316,10 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco m_tidLookup->filename(filename); m_tidLookup->reload(); - // flag this peer as Peer-Link enabled - m_peerLink = true; + // flag this peer as replica enabled + m_peerReplica = true; + if (m_peerReplicaCallback != nullptr) + m_peerReplicaCallback(this); // cleanup temporary file ::remove(filename.c_str()); @@ -266,14 +329,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_RID_LIST: // Radio ID List + case NET_SUBFUNC::REPL_RID_LIST: // Radio ID List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; if (m_ridPkt.decode(data, &decompressed, &decompressedLen)) { if (m_ridLookup == nullptr) { - LogError(LOG_NET, "Radio ID lookup not available yet."); + LogError(LOG_PEER, "Radio ID lookup not available yet."); m_ridPkt.clear(); delete[] decompressed; break; @@ -286,7 +349,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // randomize filename std::ostringstream s; - if (!m_peerLinkSavesACL) { + if (!m_peerReplicaSavesACL) { std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); @@ -298,7 +361,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco std::string filename = s.str(); std::ofstream file(filename, std::ofstream::out); if (file.fail()) { - LogError(LOG_NET, "Cannot open the radio ID lookup file - %s", filename.c_str()); + LogError(LOG_PEER, "Cannot open the radio ID lookup file - %s", filename.c_str()); m_ridPkt.clear(); delete[] decompressed; break; @@ -312,8 +375,10 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco m_ridLookup->filename(filename); m_ridLookup->reload(); - // flag this peer as Peer-Link enabled - m_peerLink = true; + // flag this peer as replica enabled + m_peerReplica = true; + if (m_peerReplicaCallback != nullptr) + m_peerReplicaCallback(this); // cleanup temporary file ::remove(filename.c_str()); @@ -323,14 +388,14 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_PEER_LIST: // Peer List + case NET_SUBFUNC::REPL_PEER_LIST: // Peer List { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; if (m_pidPkt.decode(data, &decompressed, &decompressedLen)) { if (m_pidLookup == nullptr) { - LogError(LOG_NET, "Peer ID lookup not available yet."); + LogError(LOG_PEER, "Peer ID lookup not available yet."); m_pidPkt.clear(); delete[] decompressed; break; @@ -343,7 +408,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco // randomize filename std::ostringstream s; - if (!m_peerLinkSavesACL) { + if (!m_peerReplicaSavesACL) { std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); @@ -355,7 +420,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco std::string filename = s.str(); std::ofstream file(filename, std::ofstream::out); if (file.fail()) { - LogError(LOG_NET, "Cannot open the peer ID lookup file - %s", filename.c_str()); + LogError(LOG_PEER, "Cannot open the peer ID lookup file - %s", filename.c_str()); m_pidPkt.clear(); delete[] decompressed; break; @@ -369,8 +434,10 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco m_pidLookup->filename(filename); m_pidLookup->reload(); - // flag this peer as Peer-Link enabled - m_peerLink = true; + // flag this peer as replica enabled + m_peerReplica = true; + if (m_peerReplicaCallback != nullptr) + m_peerReplicaCallback(this); // cleanup temporary file ::remove(filename.c_str()); @@ -386,6 +453,26 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; + case NET_FUNC::NET_TREE: // Network Tree + { + switch (opcode.second) { + case NET_SUBFUNC::NET_TREE_DISC: // Network Tree Disconnect + { + uint32_t offendingPeerId = GET_UINT32(data, 6U); + LogWarning(LOG_PEER, "PEER %u Network Tree Disconnect, requested from upstream master, possible duplicate connection for PEER %u", m_peerId, offendingPeerId); + + if (m_netTreeDiscCallback != nullptr) { + m_netTreeDiscCallback(this, offendingPeerId); + } + } + break; + + default: + break; + } + } + break; + default: Utils::dump("Unknown opcode from the master", data, length); break; @@ -435,8 +522,13 @@ bool PeerNetwork::writeConfig() config["rcon"].set(rcon); // Flags + /* + ** bryanb: don't change externalPeer to neighborPeer -- this will break backward + ** compat with older FNE versions (we're stuck with this naming :() + */ bool external = true; - config["externalPeer"].set(external); // External Peer Marker + config["externalPeer"].set(external); // External FNE Neighbor Peer Marker + config["masterPeerId"].set(m_masterPeerId); // Master Peer ID config["software"].set(std::string(software)); // Software ID @@ -484,9 +576,25 @@ void PeerNetwork::taskNetworkRx(PeerPacketRequest* req) // determine if this packet is late (i.e. are we processing this packet more than 200ms after it was received?) uint64_t dt = req->pktRxTime + PACKET_LATE_TIME; if (dt < now) { - LogWarning(LOG_NET, "PEER %u packet processing latency >200ms, dt = %u, now = %u", req->peerId, dt, now); + LogWarning(LOG_PEER, "PEER %u packet processing latency >200ms, dt = %u, now = %u", req->peerId, dt, now); } + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(req->streamId, req->rtpHeader.getSequence(), req->fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_PEER, "PEER %u stream %u possible lost frames; got %u, expected %u", req->fneHeader.getPeerId(), + req->streamId, req->rtpHeader.getSequence(), lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_PEER, "PEER %u stream %u out-of-order; got %u, expected >%u", req->fneHeader.getPeerId(), + req->streamId, req->rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_PEER, "PeerNetwork::taskNetworkRx()", "PEER %u valid mux, seq = %u, streamId = %u", req->fneHeader.getPeerId(), req->rtpHeader.getSequence(), req->streamId); + } +#endif // process incomfing message subfunction opcodes switch (req->subFunc) { case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 28c9d7008..48e745257 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -21,6 +21,8 @@ #include "common/network/Network.h" #include "common/network/PacketBuffer.h" #include "common/ThreadPool.h" +#include "fne/network/SpanningTree.h" +#include "fne/network/HAParameters.h" #include #include @@ -37,17 +39,17 @@ namespace network * @ingroup fne_network */ struct PeerPacketRequest : thread_t { - uint32_t peerId; //! Peer ID for this request. - uint32_t streamId; //! Stream ID for this request. + uint32_t peerId; //!< Peer ID for this request. + uint32_t streamId; //!< Stream ID for this request. - frame::RTPHeader rtpHeader; //! RTP Header - frame::RTPFNEHeader fneHeader; //! RTP FNE Header - int length = 0U; //! Length of raw data buffer - uint8_t* buffer = nullptr; //! Raw data buffer + frame::RTPHeader rtpHeader; //!< RTP Header + frame::RTPFNEHeader fneHeader; //!< RTP FNE Header + int length = 0U; //!< Length of raw data buffer + uint8_t* buffer = nullptr; //!< Raw data buffer - network::NET_SUBFUNC::ENUM subFunc; //! Sub-function of the packet + network::NET_SUBFUNC::ENUM subFunc; //!< Sub-function of the packet - uint64_t pktRxTime; //! Packet receive time + uint64_t pktRxTime; //!< Packet receive time }; // --------------------------------------------------------------------------- @@ -86,6 +88,11 @@ namespace network */ ~PeerNetwork() override; + /** + * @brief Set the peer ID of this FNE's master. + * @param masterPeerId Master Peer ID. + */ + void setMasterPeerId(uint32_t masterPeerId) { m_masterPeerId = masterPeerId; } /** * @brief Sets the instances of the Peer List lookup tables. * @param pidLookup Peer List Lookup Table Instance @@ -125,39 +132,127 @@ namespace network void setAnalogCallback(std::function&& callback) { m_analogCallback = callback; } /** - * @brief Gets the blocked traffic peer ID table. - * @returns std::vector List of peer IDs this peer network cannot send traffic to. - */ - std::vector blockTrafficTo() const { return m_blockTrafficToTable; } - /** - * @brief Adds an entry to the blocked traffic peer ID table. - * @param peerId Peer ID to add to the blocked traffic table. + * @brief Helper to set the network tree disconnect callback. + * @param callback */ - void addBlockedTrafficPeer(uint32_t peerId) { m_blockTrafficToTable.push_back(peerId); } + void setNetTreeDiscCallback(std::function&& callback) { m_netTreeDiscCallback = callback; } /** - * @brief Checks if the passed peer ID is blocked from sending to this peer. - * @returns bool True, if blocked peer table is cleared, otherwise false. + * @brief Helper to set the peer replica notification callback. + * @param callback */ - bool checkBlockedPeer(uint32_t peerId); + void setNotifyPeerReplicaCallback(std::function&& callback) { m_peerReplicaCallback = callback; } /** * @brief Writes a complete update of this CFNE's active peer list to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the active peer listmessage. + * The active peer list message is a JSON body, and is a packet buffer compressed message. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (REPL) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Variable Length JSON Payload ................................ | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The JSON payload is variable length and looks like this: + * { + * "peerId": , + * "address": "", + * "port": , + * "connected": , + * "connectionState": , + * "pingsReceived": , + * "lastPing": , + * "controlChannel": , + * "config": { + * + * }, + * "voiceChannels": [ + * , + * ] + * } + * \endcode * @param peerList List of active peers. * @returns bool True, if list was sent, otherwise false. */ bool writePeerLinkPeers(json::array* peerList); + /** + * @brief Writes a complete update of this CFNE's known spanning tree upstream to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the spanning tree message. + * The spanning tree message is a JSON body, and is a packet buffer compressed message. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protocol Tag (REPL) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Variable Length JSON Payload ................................ | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * The JSON payload is variable length and looks like this: + * { + * "id": , + * "masterId": , + * "identity": "", + * "children": [ + * "id": , + * "masterId": , + * "identity": "", + * "children": [], + * ] + * } + * \endcode + * @param treeRoot Root of the master tree. + * @returns bool True, if list was sent, otherwise false. + */ + bool writeSpanningTree(SpanningTree* treeRoot); + /** + * @brief Writes a complete update of this CFNE's HA parameters to the network. + * \code{.unparsed} + * Below is the representation of the data layout for the repeater/end point login message. + * The message is variable bytes in length. + * + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Total length of all included entries | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Peer ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: IP Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Entry: Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * \endcode + * @param haParams List of HA parameters. + * @returns bool True, if list was sent, otherwise false. + */ + bool writeHAParams(std::vector& haParams); /** - * @brief Returns flag indicating whether or not this peer connection is Peer-Link enabled. - * @returns bool True, if Peer-Link enabled, otherwise false. + * @brief Returns flag indicating whether or not this peer connection is peer replication enabled. + * @returns bool True, if peer replication enabled, otherwise false. */ - bool isPeerLink() const { return m_peerLink; } + bool isReplica() const { return m_peerReplica; } /** - * @brief Enables the option that will save the pushed peer link ACL data to the local ACL files. - * @param enabled Flag to enable ACL data saving. + * @brief Enables the option that will save replicated ACL data to the local ACL files. + * @param enabled Flag to enable replicated ACL data saving. */ - void setPeerLinkSaveACL(bool enabled) { m_peerLinkSavesACL = enabled; } + void setPeerReplicationSaveACL(bool enabled) { m_peerReplicaSavesACL = enabled; } + + /** + * @brief Gets the remote peer ID. + * @returns uint32_t Remote Peer ID. + */ + uint32_t getRemotePeerId() const { return m_remotePeerId; } public: /** @@ -166,8 +261,6 @@ namespace network DECLARE_PROPERTY(bool, attachedKeyRSPHandler, AttachedKeyRSPHandler); protected: - std::vector m_blockTrafficToTable; - /** * @brief DMR Protocol Callback. * (This is called when the master sends a DMR packet.) @@ -189,6 +282,15 @@ namespace network */ std::function m_analogCallback; + /** + * @brief Network Tree Disconnect Callback. + */ + std::function m_netTreeDiscCallback; + /** + * @brief Peer Replica Notification Callback. + */ + std::function m_peerReplicaCallback; + /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. * @param peerId Peer ID. @@ -209,9 +311,11 @@ namespace network bool writeConfig() override; private: + uint32_t m_masterPeerId; + lookups::PeerListLookup* m_pidLookup; - bool m_peerLink; - bool m_peerLinkSavesACL; + bool m_peerReplica; + bool m_peerReplicaSavesACL; PacketBuffer m_tgidPkt; PacketBuffer m_ridPkt; @@ -219,6 +323,8 @@ namespace network ThreadPool m_threadPool; + uint32_t m_prevSpanningTreeChildren; + /** * @brief Entry point to process a given network packet. * @param req Instance of the PeerPacketRequest structure. diff --git a/src/fne/network/SpanningTree.cpp b/src/fne/network/SpanningTree.cpp new file mode 100644 index 000000000..b25a64c82 --- /dev/null +++ b/src/fne/network/SpanningTree.cpp @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/SpanningTree.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +std::mutex SpanningTree::s_mutex; +std::unordered_map SpanningTree::s_spanningTrees = std::unordered_map(); +uint8_t SpanningTree::s_maxUpdatesBeforeReparent = 5U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the SpanningTree class. */ + +SpanningTree::SpanningTree(uint32_t id, uint32_t masterId, SpanningTree* parent) : + m_parent(parent), + m_children(), + m_identity("CHANGEME"), + m_id(id), + m_masterId(masterId), + m_updatesBeforeReparent(0U) +{ + s_spanningTrees[id] = this; + if (m_parent != nullptr) + m_parent->m_children.push_back(this); +} + +/* Finalizes a instance of the SpanningTree class. */ + +SpanningTree::~SpanningTree() = default; + +/* Find a peer tree by peer ID. */ + +SpanningTree* SpanningTree::findByPeerID(const uint32_t peerId) +{ + auto it = s_spanningTrees.find(peerId); + if (it != s_spanningTrees.end()) { + return it->second; + } + + return nullptr; +} + +/* Find a peer tree by master peer ID. */ + +SpanningTree* SpanningTree::findByMasterID(const uint32_t masterId) +{ + for (auto it : s_spanningTrees) { + SpanningTree* tree = it.second; + if (tree != nullptr) { + if (tree->masterId() == masterId) { + return tree; + } + } + } + + return nullptr; +} + +/* Count all children of a tree node. */ + +uint32_t SpanningTree::countChildren(SpanningTree* node) +{ + if (node == nullptr) + return 0U; + if (node->m_children.size() == 0) + return 0U; + + uint32_t count = 0U; + for (auto child : node->m_children) { + ++count; + count += countChildren(child); + } + + return count; +} + +/* Erase a peer from the tree. */ + +void SpanningTree::erasePeer(const uint32_t peerId) +{ + std::lock_guard guard(s_mutex); + internalErasePeer(peerId); +} + +/* Helper to recursively serialize tree node to JSON array. */ + +void SpanningTree::serializeTree(SpanningTree* node, json::array& jsonArray) +{ + if (node == nullptr) + return; + + json::object obj; + uint32_t id = node->id(); + obj["id"].set(id); + uint32_t masterId = node->masterId(); + obj["masterId"].set(masterId); + std::string identity = node->identity(); + obj["identity"].set(identity); + + json::array childArray; + for (auto child : node->m_children) { + serializeTree(child, childArray); + } + obj["children"].set(childArray); + + jsonArray.push_back(json::value(obj)); +} + +/* Helper to recursively deserialize tree node from JSON array. */ + +void SpanningTree::deserializeTree(json::array& jsonArray, SpanningTree* parent, std::vector* duplicatePeers) +{ + std::lock_guard guard(s_mutex); + internalDeserializeTree(jsonArray, parent, duplicatePeers); +} + +/* Helper to move the tree node to a different parent tree node. */ + +void SpanningTree::moveParent(SpanningTree* node, SpanningTree* parent) +{ + if (node == nullptr) + return; + if (parent == nullptr) + return; + + std::lock_guard guard(s_mutex); + internalMoveParent(node, parent); +} + +/* Helper to visualize the tree structure in the log. */ + +void SpanningTree::visualizeTreeToLog(SpanningTree* node, uint32_t level) +{ + if (node == nullptr) + return; + if (node->m_children.size() == 0) { + return; + } + + if (level == 0U) + LogInfoEx(LOG_STP, "Peer ID: %u, Master Peer ID: %u (%s), Children: %u, IsRoot: %u", + node->id(), node->masterId(), node->identity().c_str(), node->m_children.size(), node->isRoot()); + + std::string indent; + for (uint32_t i = 0U; i < level; i++) { + indent += " "; + } + + for (auto child : node->m_children) { + LogInfoEx(LOG_STP, "%s- Peer ID: %u, Master Peer ID: %u (%s), Children: %u, IsRoot: %u", + indent.c_str(), child->id(), child->masterId(), child->identity().c_str(), child->m_children.size(), child->isRoot()); + visualizeTreeToLog(child, level + 1U); + } +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- + +/* Helper to recursively deserialize tree node from JSON array. */ + +void SpanningTree::internalDeserializeTree(json::array& jsonArray, SpanningTree* parent, + std::vector* duplicatePeers, bool noReparent) +{ + for (auto& v : jsonArray) { + if (!v.is()) + continue; + + json::object obj = v.get(); + if (!obj["id"].is()) + continue; + if (!obj["masterId"].is()) + continue; + if (!obj["children"].is()) + continue; + + uint32_t id = obj["id"].get(); + uint32_t masterId = obj["masterId"].get(); + std::string identity = "* UNK *"; + if (obj["identity"].is()) + identity = obj["identity"].getDefault("* UNK *"); + + // check if this peer is already connected via another peer + SpanningTree* tree = SpanningTree::findByMasterID(masterId); + if (tree != nullptr) { + // is this a fast reconnect? (this happens when a connecting peer + // uses the same peer ID and master ID already announced in the tree, but + // the tree entry wasn't yet erased) + if (tree->id() != id) { + if (duplicatePeers != nullptr) + duplicatePeers->push_back(id); + continue; + } + } + + SpanningTree* existingNode = findByPeerID(id); + if (existingNode == nullptr) { + existingNode = new SpanningTree(id, masterId, parent); + existingNode->identity(identity); + } + else { + if (!noReparent) { + // are the parents different? if so, start counting down to reparenting + if (existingNode->m_parent != parent) { + if (existingNode->m_updatesBeforeReparent >= s_maxUpdatesBeforeReparent) { + existingNode->m_updatesBeforeReparent = 0U; + + // validate parent is still valid before reparenting + if (parent != nullptr) { + // check if parent still exists in the tree map + auto parentIt = s_spanningTrees.find(parent->id()); + if (parentIt != s_spanningTrees.end() && parentIt->second == parent) { + // reparent the node if necessary + internalMoveParent(existingNode, parent); + } else { + LogError(LOG_STP, "PEER %u (%s) cannot be reparented to invalid parent PEER %u, skipping reparent", + existingNode->id(), existingNode->identity().c_str(), parent->id()); + existingNode->m_updatesBeforeReparent = 0U; // reset counter + } + } + } else { + existingNode->m_updatesBeforeReparent++; + } + } + } + } + + // process children + json::array childArray = obj["children"].get(); + //LogDebugEx(LOG_STP, "SpanningTree::internalDeserializeTree()", "peerId = %u, masterId = %u, parent = %u, childrenCnt = %u", + // existingNode->id(), existingNode->masterId(), parent->id(), childArray.size()); + if (childArray.size() > 0U) { + internalDeserializeTree(childArray, existingNode, duplicatePeers, true); + + // does the announced array disagree with our current count of children? + if (childArray.size() < existingNode->m_children.size()) { + /* + ** bryanb: this is doing some array comparision/differencing non-sense and IMHO is probably + ** bad and slow as balls... + */ + + // enumerate the peer IDs in the announced children + std::vector announcedChildren; + for (auto& child : childArray) { + if (!child.is()) + continue; + + json::object obj = child.get(); + if (!obj["id"].is()) + continue; + + uint32_t childId = obj["id"].get(); + announcedChildren.push_back(childId); + } + + // iterate over the nodes children and remove entries + std::vector childrenToErase; + for (auto child : existingNode->m_children) { + if (child != nullptr) { + auto it = std::find(announcedChildren.begin(), announcedChildren.end(), child->id()); + if (it == announcedChildren.end()) { + childrenToErase.push_back(child->id()); + } + } + } + + if (childrenToErase.size() > 0) { + for (auto child : childrenToErase) { + internalErasePeer(child); + } + } + } + } + else { + // did the node report children at some point and is no longer reporting them? + if (childArray.size() == 0U && existingNode->hasChildren()) { + //LogDebugEx(LOG_STP, "SpanningTree::internalDeserializeTree()", "peerId = %u, masterId = %u, parent = %u, childrenCnt = %u, existingChildrenCnt = %u - erasing children", + // existingNode->id(), existingNode->masterId(), parent->id(), childArray.size(), existingNode->m_children.size()); + + // iterate over the nodes children and remove entries + std::vector childrenToErase; + for (auto child : existingNode->m_children) { + if (child != nullptr) { + childrenToErase.push_back(child->id()); + } + } + + if (childrenToErase.size() > 0) { + for (auto child : childrenToErase) { + internalErasePeer(child); + } + } + } + } + } +} + +/* Helper to move the tree node to a different parent tree node. */ + +void SpanningTree::internalMoveParent(SpanningTree* node, SpanningTree* parent) +{ + if (node == nullptr) + return; + if (parent == nullptr) + return; + + // validate that both node and parent exist in the tree map (not dangling pointers) + auto nodeIt = s_spanningTrees.find(node->id()); + if (nodeIt == s_spanningTrees.end() || nodeIt->second != node) { + LogError(LOG_STP, "PEER %u is not valid in tree map, cannot move parent. BUGBUG.", node->id()); + return; + } + + auto parentIt = s_spanningTrees.find(parent->id()); + if (parentIt == s_spanningTrees.end() || parentIt->second != parent) { + LogError(LOG_STP, "Parent PEER %u is not valid in tree map, cannot reparent PEER %u. BUGBUG.", + parent->id(), node->id()); + return; + } + + // the root node cannot be moved + if (node->m_parent == nullptr) { + LogError(LOG_STP, "PEER %u (%s) is a root tree node, can't be moved. BUGBUG.", node->id(), node->identity().c_str()); + return; + } + + if (node->m_parent != parent) { + // find the node in the parent children and remove it + SpanningTree* nodeParent = node->m_parent; + uint32_t nodeParentId = 0U; + bool hasReleasedFromParent = false; + + if (nodeParent != nullptr) { + nodeParentId = nodeParent->id(); + auto it = std::find_if(nodeParent->m_children.begin(), nodeParent->m_children.end(), [&](SpanningTree* childNode) { + if (childNode == node) + return true; + return false; + }); + if (it != nodeParent->m_children.end()) { + nodeParent->m_children.erase(it); + hasReleasedFromParent = true; + } else { + LogError(LOG_STP, "PEER %u (%s) failed to release ownership from PEER %u, tree is potentially inconsistent", + node->id(), node->identity().c_str(), nodeParent->id()); + } + } + + if (hasReleasedFromParent) { + // reparent existing node + node->m_parent = parent; + if (node->m_parent != nullptr) + node->m_parent->m_children.push_back(node); + + // reset update counter + if (node->m_updatesBeforeReparent > 0U) + node->m_updatesBeforeReparent = 0U; + + LogWarning(LOG_STP, "PEER %u (%s) ownership has changed from PEER %u to PEER %u; this normally shouldn't happen", + node->id(), node->identity().c_str(), nodeParentId, parent->id()); + } + } +} + +/* Erase a peer from the tree. */ + +void SpanningTree::internalErasePeer(const uint32_t peerId) +{ + auto it = s_spanningTrees.find(peerId); + if (it != s_spanningTrees.end()) { + SpanningTree* tree = it->second; + if (tree != nullptr) { + if (tree->m_parent != nullptr) { + auto& siblings = tree->m_parent->m_children; + siblings.erase(std::remove(siblings.begin(), siblings.end(), tree), siblings.end()); + } + + if (tree->hasChildren()) { + for (auto child : tree->m_children) { + if (child != nullptr) { + internalErasePeer(child->id()); + } + } + + tree->m_children.clear(); + } + + delete tree; + tree = nullptr; + } + + s_spanningTrees.erase(it); + } +} diff --git a/src/fne/network/SpanningTree.h b/src/fne/network/SpanningTree.h new file mode 100644 index 000000000..70733b76c --- /dev/null +++ b/src/fne/network/SpanningTree.h @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SpanningTree.h + * @ingroup fne_network + * @file SpanningTree.cpp + * @ingroup fne_network + */ +#if !defined(__SPANNING_TREE_H__) +#define __SPANNING_TREE_H__ + +#include "fne/Defines.h" +#include "common/json/json.h" + +#include +#include +#include +#include + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents an FNE spanning tree. + * @ingroup fne_network + * @remarks + * This class implements a extremely rudimentary spanning tree structure to represent + * the linked FNE tree topology. + * + * Each node represents a master FNE in the tree. The root node is the master FNE + * at the top of the tree. Each node contains a list of child nodes that are directly connected + * to it downstream. + * + * For example, consider the following tree structure: + * + * A + * / \ + * B C + * / \ \ + * D E F + * / \ + * G H + * + * In this example, A is the root node (master FNE), B and C are its children, + * D and E are children of B, F is a child of C, G is a child of D, and H is a child of F. + * + * Child nodes always send their data upstream to their parent node. The tree is always a top-down + * structure, with data flowing from the leaves up to the root. The root node does not have a parent. + * + * - Nodes can have multiple child nodes, and child nodes can have their own children, forming a hierarchical tree. + * - Nodes with child nodes can determine duplicate connections and enforce tree integrity. * + * - Each node in the tree assumes it is the root of its own subtree. For instance, B considers itself + * the root of the subtree containing B, D, E, and G. This allows for easy traversal and management of + * the tree structure. + */ + class HOST_SW_API SpanningTree { + public: + auto operator=(SpanningTree&) -> SpanningTree& = delete; + auto operator=(SpanningTree&&) -> SpanningTree& = delete; + SpanningTree(SpanningTree&) = delete; + + /** + * @brief Initializes a new instance of the SpanningTree class + * @param id Peer ID. + * @param parent Parent server tree node. + */ + SpanningTree(uint32_t id, uint32_t masterId, SpanningTree* parent); + /** + * @brief Finalizes a instance of the SpanningTree class + */ + ~SpanningTree(); + + /** + * @brief Flag indicating whether or not this server is a tree root. + * @return bool True, if server is the tree root, otherwise false. + */ + bool isRoot() const { return (m_parent == nullptr); } + /** + * @brief Flag indicating whether or not this server has child nodes. + * @return bool True, if server has child nodes, otherwise false. + */ + bool hasChildren() const { return !m_children.empty(); } + + /** + * @brief Find a peer tree by peer ID. + * @param peerId Peer ID. + * @return SpanningTree* Pointer to the SpanningTree instance, or nullptr if not found. + */ + static SpanningTree* findByPeerID(const uint32_t peerId); + /** + * @brief Find a peer tree by master peer ID. + * @param masterId Master Peer ID. + * @return SpanningTree* Pointer to the SpanningTree instance, or nullptr if not found. + */ + static SpanningTree* findByMasterID(const uint32_t masterId); + + /** + * @brief Count all children of a tree node. + * @param node Pointer to SpanningTree node. + * @return uint32_t Number of child nodes. + */ + static uint32_t countChildren(SpanningTree* node); + + /** + * @brief Erase a peer from the tree. + * @param peerId Peer ID. + */ + static void erasePeer(const uint32_t peerId); + + /** + * @brief Helper to recursively serialize tree node to JSON array. + * @param node Pointer to SpanningTree node. + * @param jsonArray JSON array to write node to. + */ + static void serializeTree(SpanningTree* node, json::array& jsonArray); + + /** + * @brief Helper to recursively deserialize tree node from JSON array. + * @param jsonArray JSON array to read node from. + * @param parent Pointer to parent SpanningTree node. + * @param duplicatePeers Pointer to vector to receive duplicate peer IDs found during deserialization. + */ + static void deserializeTree(json::array& jsonArray, SpanningTree* parent, std::vector* duplicatePeers); + + /** + * @brief Helper to move the tree node to a different parent tree node. + * @param node Pointer to a SpanningTree node. + * @param parent Pointer to new parent SpanningTree Node. + */ + static void moveParent(SpanningTree* node, SpanningTree* parent); + + /** + * @brief Helper to visualize the tree structure in the log. + * @param node Pointer to SpanningTree node. + * @param level Current tree level. + */ + static void visualizeTreeToLog(SpanningTree* node, uint32_t level = 0U); + + public: + SpanningTree* m_parent; //!< Parent tree node. (i.e. master FNE above this) + std::vector m_children; //!< Child tree nodes. (i.e. peer FNEs below this) + + static std::mutex s_mutex; + static std::unordered_map s_spanningTrees; //!< Static map of all trees by peer ID. + static uint8_t s_maxUpdatesBeforeReparent; //!< Maximum count of updates before allowing node reparenting. + + /** + * @brief Peer Identity. + */ + DECLARE_PROPERTY_PLAIN(std::string, identity); + /** + * @brief Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, id); + /** + * @brief Master Peer ID. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, masterId); + + private: + uint8_t m_updatesBeforeReparent; + + /** + * @brief Helper to recursively deserialize tree node from JSON array. + * @param jsonArray JSON array to read node from. + * @param parent Pointer to parent SpanningTree node. + * @param duplicatePeers Pointer to vector to receive duplicate peer IDs found during deserialization. + * @param noReparent If true, disables reparenting during deserialization. + */ + static void internalDeserializeTree(json::array& jsonArray, SpanningTree* parent, + std::vector* duplicatePeers, bool noReparent = false); + /** + * @brief Helper to move the tree node to a different parent tree node. + * @param node Pointer to a SpanningTree node. + * @param parent Pointer to new parent SpanningTree Node. + */ + static void internalMoveParent(SpanningTree* node, SpanningTree* parent); + /** + * @brief Erase a peer from the tree. + * @param peerId Peer ID. + */ + static void internalErasePeer(const uint32_t peerId); + }; +} // namespace network + +#endif // __SPANNING_TREE_H__ diff --git a/src/fne/network/callhandler/TagAnalogData.cpp b/src/fne/network/callhandler/TagAnalogData.cpp index c809ab17e..8933c2cd2 100644 --- a/src/fne/network/callhandler/TagAnalogData.cpp +++ b/src/fne/network/callhandler/TagAnalogData.cpp @@ -27,12 +27,6 @@ using namespace analog::defines; #include #include -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const uint32_t CALL_COLL_TIMEOUT = 10U; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -43,6 +37,10 @@ TagAnalogData::TagAnalogData(FNENetwork* network, bool debug) : m_network(network), m_parrotFrames(), m_parrotFramesReady(false), + m_parrotPlayback(false), + m_lastParrotPeerId(0U), + m_lastParrotSrcId(0U), + m_lastParrotDstId(0U), m_status(), m_debug(debug) { @@ -55,7 +53,7 @@ TagAnalogData::~TagAnalogData() = default; /* Process a data frame from the network. */ -bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -89,21 +87,21 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee // is the stream valid? if (validate(peerId, analogData, streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, analogData, streamId, external)) { + if (!isPeerPermitted(peerId, analogData, streamId, fromUpstream)) { return false; } // is this the end of the call stream? if (frameType == AudioFrameType::TERMINATOR) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "Analog, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "Analog, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -115,16 +113,19 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; - LogMessage(LOG_NET, "Analog, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); + LogInfoEx(LOG_NET, "Analog, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); m_network->m_parrotDelayTimer.start(); } } - LogMessage(LOG_NET, "Analog, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + #define CALL_END_LOG "Analog, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_END_LOG); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -141,18 +142,17 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee } m_network->eraseStreamPktSeq(peerId, streamId); - m_network->m_callInProgress = false; } } // is this a new call stream? if (frameType == AudioFrameType::VOICE_START) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "Analog, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "Analog, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -161,48 +161,115 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee }); if (it != m_status.end()) { RxStatus status = it->second; + + // is the call being taken over? + if (status.callTakeover) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "Analog, Call Source Switched (Takeover), peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status[dstId].callTakeover = false; // reset takeover flag + m_status.unlock(); + + status = m_status[dstId]; + } + if (streamId != status.streamId) { if (status.srcId != 0U && status.srcId != srcId) { - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "Analog, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status[dstId].reset(); - m_network->m_callInProgress = false; + bool hasCallPriority = false; + + // determine if the peer trying to transmit has call priority + if (m_network->m_callCollisionTimeout > 0U) { + m_network->m_peers.shared_lock(); + for (auto peer : m_network->m_peers) { + if (peerId == peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + hasCallPriority = conn->hasCallPriority(); + break; + } + } + } + m_network->m_peers.shared_unlock(); } - LogWarning(LOG_NET, "Analog, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - return false; + // perform standard call collision if the call collision timeout is set *and* + // the peer doesn't have call priority + if (m_network->m_callCollisionTimeout > 0U && !hasCallPriority) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "Analog, Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } + else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "Analog, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + return false; + } + } else { + if (hasCallPriority && !m_network->m_disallowInCallCtrl) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "Analog, Call Source Switched (Priority), peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + + // since we're gonna switch over the stream and interrupt the current call inprogress lets try to ICC the transmitting peer + if (m_network->isPeerLocal(m_status[dstId].ssrc)) + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, false, + m_status[dstId].ssrc); + else + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, true, + m_status[dstId].ssrc); + } + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } } } } else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { + m_parrotFrames.lock(false); for (auto& pkt : m_parrotFrames) { if (pkt.buffer != nullptr) { delete[] pkt.buffer; } } + m_parrotFrames.unlock(); m_parrotFrames.clear(); } } // this is a new call stream - // bryanb: this could be problematic and is naive, if a dstId appears on both slots (which shouldn't happen) + m_status.lock(false); m_status[dstId].callStartTime = pktTime; m_status[dstId].srcId = srcId; m_status[dstId].dstId = dstId; m_status[dstId].streamId = streamId; m_status[dstId].peerId = peerId; + m_status[dstId].ssrc = ssrc; m_status[dstId].activeCall = true; + m_status.unlock(); - LogMessage(LOG_NET, "Analog, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); - - m_network->m_callInProgress = true; + #define CALL_START_LOG "Analog, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_START_LOG); } } @@ -230,12 +297,23 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee } } + m_status.lock(false); m_status[dstId].lastPacket = hrc::now(); + m_status.unlock(); + + /* + ** MASTER TRAFFIC + */ - // repeat traffic to the connected peers + // repeat traffic to nodes peered to us as master if (m_network->m_peers.size() > 0U) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { + if (peer.second == nullptr) + continue; if (peerId != peer.first) { if (ssrc == peer.first) { // skip the peer if it is the source peer @@ -247,9 +325,9 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee continue; } - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); @@ -258,45 +336,43 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, dstId); - m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeerQueue(&queue, peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - ssrc, peerId, peer.first, seqNo, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_ANALOG, "TagAnalogData::processFrame()", "Master, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, fromUpstream = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, len, pktSeq, streamId, fromUpstream); } - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; i++; } } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to master nodes we have connected to as a peer if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { if (ssrc == dstPeerId) { // skip the peer if it is the source peer continue; } - // is this peer ignored? - if (!isPeerPermitted(dstPeerId, analogData, streamId, true)) { - continue; - } - - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { continue; } - // skip peer if it isn't enabled - if (!peer.second->isEnabled()) { + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, analogData, streamId, true)) { continue; } @@ -306,18 +382,15 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, dstId); - // are we a peer link? - if (peer.second->isPeerLink()) - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + // are we a replica peer? + if (peer.second->isReplica()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId, false, 0U, ssrc); else peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "Analog, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - ssrc, peerId, dstPeerId, seqNo, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_ANALOG, "TagAnalogData::processFrame()", "Peers, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, len = %u, pktSeq = %u, stream = %u, fromUpstream = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, len, pktSeq, streamId, fromUpstream); } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; } } } @@ -328,38 +401,78 @@ bool TagAnalogData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee return false; } +/* Helper to trigger a call takeover from a In-Call control event. */ + +void TagAnalogData::triggerCallTakeover(uint32_t dstId) +{ + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status.lock(false); + m_status[dstId].callTakeover = true; + m_status.unlock(); + } +} + /* Helper to playback a parrot frame to the network. */ void TagAnalogData::playbackParrot() { if (m_parrotFrames.size() == 0) { m_parrotFramesReady = false; + m_parrotPlayback = false; return; } + m_parrotPlayback = true; + auto& pkt = m_parrotFrames[0]; + m_parrotFrames.lock(); if (pkt.buffer != nullptr) { + m_lastParrotPeerId = pkt.peerId; + m_lastParrotSrcId = pkt.srcId; + m_lastParrotDstId = pkt.dstId; + if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_ANALOG, "TagAnalogData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } } else { // repeat traffic to the connected peers + uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); + } + + m_network->writePeerQueue(&queue, peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "Analog, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_ANALOG, "TagAnalogData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } + + i++; } + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } delete[] pkt.buffer; } Thread::sleep(60); + m_parrotFrames.unlock(); m_parrotFrames.pop_front(); } @@ -414,7 +527,7 @@ bool TagAnalogData::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound) /* Helper to determine if the peer is permitted for traffic. */ -bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external) +bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool fromUpstream) { if (!data.getGroup()) { if (m_network->m_disallowU2U) @@ -431,10 +544,10 @@ bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32 connection = m_network->m_peers[peerId]; } - // is this peer a Peer-Link peer? + // is this peer a replica peer? if (connection != nullptr) { - if (connection->isPeerLink()) { - return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + if (connection->isReplica()) { + return true; // replica peers are *always* allowed to receive traffic and no other rules may filter // these peers } } @@ -475,8 +588,8 @@ bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32 if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { if (connection->isConventionalPeer()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for conventional peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for conventional peers } } } @@ -484,14 +597,14 @@ bool TagAnalogData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32 // is this peer a SysView peer? if (connection != nullptr) { if (connection->isSysView()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for SysView peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for SysView peers } } // is this a TG that requires affiliations to repeat? - // NOTE: external peers *always* repeat traffic regardless of affiliation - if (tg.config().affiliated() && !external) { + // NOTE: neighbor FNE peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !fromUpstream) { uint32_t lookupPeerId = peerId; if (connection != nullptr) { if (connection->ccPeerId() > 0U) @@ -538,6 +651,9 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_ANALOG, INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); return false; @@ -574,6 +690,9 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_ANALOG, INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, data.getSrcId(), data.getDstId()); + return false; } } @@ -589,12 +708,13 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_ANALOG, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); @@ -620,6 +740,9 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_ANALOG, INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); return false; @@ -646,12 +769,13 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .tag("streamId", std::to_string(streamId)) .tag("srcId", std::to_string(data.getSrcId())) .tag("dstId", std::to_string(data.getDstId())) - .field("message", std::string(INFLUXDB_ERRSTR_DISABLED_SRC_RID)) + .field("message", std::string(INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS)) .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) .requestAsync(m_network->m_influxServer); } - LogWarning(LOG_NET, "Analog, illegal/unknown RID attempted access, srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); + if (m_network->m_logDenials) + LogWarning(LOG_ANALOG, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); @@ -673,6 +797,9 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_ANALOG, INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); return false; @@ -698,6 +825,9 @@ bool TagAnalogData::validate(uint32_t peerId, data::NetData& data, uint32_t stre .requestAsync(m_network->m_influxServer); } + if (m_network->m_logDenials) + LogError(LOG_ANALOG, INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, data.getSrcId(), data.getDstId()); + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG, NET_ICC::REJECT_TRAFFIC, data.getDstId()); return false; diff --git a/src/fne/network/callhandler/TagAnalogData.h b/src/fne/network/callhandler/TagAnalogData.h index c7ae6624c..f01a841b5 100644 --- a/src/fne/network/callhandler/TagAnalogData.h +++ b/src/fne/network/callhandler/TagAnalogData.h @@ -59,10 +59,15 @@ namespace network * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); + + /** + * @brief Helper to trigger a call takeover from a In-Call control event. + */ + void triggerCallTakeover(uint32_t dstId); /** * @brief Helper to playback a parrot frame to the network. @@ -74,6 +79,38 @@ namespace network */ bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + /** + * @brief Helper to determine if the parrot is playing back frames. + * @returns True, if parrot playback was started, otherwise false. + */ + bool isParrotPlayback() const { return m_parrotPlayback; } + /** + * @brief Helper to clear the parrot playback flag. + */ + void clearParrotPlayback() + { + m_parrotPlayback = false; + m_lastParrotPeerId = 0U; + m_lastParrotSrcId = 0U; + m_lastParrotDstId = 0U; + } + + /** + * @brief Returns the last processed peer ID for a parrot frame. + * @return uint32_t Peer ID. + */ + uint32_t lastParrotPeerId() const { return m_lastParrotPeerId; } + /** + * @brief Returns the last processed source ID for a parrot frame. + * @return uint32_t Source ID. + */ + uint32_t lastParrotSrcId() const { return m_lastParrotSrcId; } + /** + * @brief Returns the last processed destination ID for a parrot frame. + * @return uint32_t Destination ID. + */ + uint32_t lastParrotDstId() const { return m_lastParrotDstId; } + private: FNENetwork* m_network; @@ -109,6 +146,10 @@ namespace network }; concurrent::deque m_parrotFrames; bool m_parrotFramesReady; + bool m_parrotPlayback; + uint32_t m_lastParrotPeerId; + uint32_t m_lastParrotSrcId; + uint32_t m_lastParrotDstId; /** * @brief Represents the receive status of a call. @@ -133,10 +174,18 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Synchronization Source. + */ + uint32_t ssrc; /** * @brief Flag indicating this call is active with traffic currently in progress. */ bool activeCall; + /** + * @brief Flag indicating the metadata for the call on the next frame will be overwritten. + */ + bool callTakeover; /** * @brief Helper to reset call status. @@ -147,7 +196,9 @@ namespace network dstId = 0U; streamId = 0U; peerId = 0U; + ssrc = 0U; activeCall = false; + callTakeover = false; } }; typedef std::pair StatusMapPair; @@ -177,10 +228,10 @@ namespace network * @param peerId Peer ID. * @param data Instance of data::NetData Analog data container class. * @param streamId Stream ID. - * @param external Flag indicating this traffic came from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if valid, otherwise false. */ - bool isPeerPermitted(uint32_t peerId, analog::data::NetData& data, uint32_t streamId, bool external = false); + bool isPeerPermitted(uint32_t peerId, analog::data::NetData& data, uint32_t streamId, bool fromUpstream = false); /** * @brief Helper to validate the DMR call stream. * @param peerId Peer ID. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 87e0c7930..f32b87b21 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -19,6 +19,7 @@ #include "network/FNENetwork.h" #include "network/callhandler/TagDMRData.h" #include "HostFNE.h" +#include "FNEMain.h" using namespace system_clock; using namespace network; @@ -30,12 +31,6 @@ using namespace dmr::defines; #include #include -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const uint32_t CALL_COLL_TIMEOUT = 10U; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -46,6 +41,10 @@ TagDMRData::TagDMRData(FNENetwork* network, bool debug) : m_network(network), m_parrotFrames(), m_parrotFramesReady(false), + m_parrotPlayback(false), + m_lastParrotPeerId(0U), + m_lastParrotSrcId(0U), + m_lastParrotDstId(0U), m_status(), m_statusPVCall(), m_debug(debug) @@ -64,7 +63,7 @@ TagDMRData::~TagDMRData() /* Process a data frame from the network. */ -bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -115,7 +114,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId (dataType == DataType::RATE_1_DATA))) { if (m_network->m_disablePacketData) return false; - return m_packetData->processFrame(data, len, peerId, pktSeq, streamId, external); + return m_packetData->processFrame(data, len, peerId, pktSeq, streamId, fromUpstream); } uint8_t frame[DMR_FRAME_LENGTH_BYTES]; @@ -134,28 +133,28 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is the stream valid? if (validate(peerId, dmrData, csbk.get(), streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, dmrData, streamId, external)) { + if (!isPeerPermitted(peerId, dmrData, streamId, fromUpstream)) { return false; } // is this the end of the call stream? if (dataSync && (dataType == DataType::TERMINATOR_WITH_LC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, slotNo, streamId, external); + LogWarning(LOG_NET, "DMR, invalid TERMINATOR, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, slotNo, streamId, fromUpstream); return false; } RxStatus status; { - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId && x.second.slotNo == slotNo) { return true; } return false; }); if (it == m_status.end()) { - LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, streamId, external); + LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, streamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, fromUpstream); } else { status = it->second; @@ -164,7 +163,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId uint64_t duration = hrc::diff(pktTime, status.callStartTime); - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId && x.second.slotNo == slotNo) { if (x.second.activeCall) return true; @@ -176,16 +175,16 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; - LogMessage(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); + LogInfoEx(LOG_NET, "DMR, Parrot Playback will Start, peer = %u, ssrc = %u, srcId = %u", peerId, ssrc, srcId); m_network->m_parrotDelayTimer.start(); } } // is this a private call? - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -194,12 +193,19 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); - LogMessage(LOG_NET, "DMR, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); + #define PRV_CALL_END_LOG "DMR, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); + } + else { + #define CALL_END_LOG "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_END_LOG); } - else - LogMessage(LOG_NET, "DMR, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slot = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -217,20 +223,19 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } m_network->eraseStreamPktSeq(peerId, streamId); - m_network->m_callInProgress = false; } } // is this a new call stream? if (dataSync && (dataType == DataType::VOICE_LC_HEADER)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "DMR, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "DMR, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId && x.second.slotNo == slotNo) { if (x.second.activeCall) return true; @@ -239,76 +244,158 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_status.end()) { RxStatus status = it->second; + + // is the call being taken over? + if (status.callTakeover) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Call Source Switched (Takeover), peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, fromUpstream); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status[dstId].callTakeover = false; // reset takeover flag + m_status.unlock(); + + status = m_status[dstId]; + } + if (streamId != status.streamId) { // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch // from one source to another (primarily from bridge resources) if (switchOver && status.slotNo == slotNo) { - status.streamId = streamId; - status.srcId = srcId; - LogMessage(LOG_NET, "DMR, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].ssrc = ssrc; + if (status.srcId == 0U) + m_status[dstId].srcId = srcId; + if (status.srcId != srcId) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, fromUpstream); + m_status[dstId].srcId = srcId; + } + m_status.unlock(); } + else { + if (status.srcId != 0U && status.srcId != srcId) { + bool hasCallPriority = false; + + // determine if the peer trying to transmit has call priority + if (m_network->m_callCollisionTimeout > 0U) { + m_network->m_peers.shared_lock(); + for (auto peer : m_network->m_peers) { + if (peerId == peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + hasCallPriority = conn->hasCallPriority(); + break; + } + } + } + m_network->m_peers.shared_unlock(); + } - if (status.srcId != 0U && status.srcId != srcId) { - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "DMR, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status[dstId].reset(); - m_network->m_callInProgress = false; - } + // perform standard call collision if the call collision timeout is set *and* + // the peer doesn't have call priority + if (m_network->m_callCollisionTimeout > 0U && !hasCallPriority) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, fromUpstream); + return false; + } + } else { + if (hasCallPriority && !m_network->m_disallowInCallCtrl) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Call Source Switched (Priority), peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, fromUpstream); + + // since we're gonna switch over the stream and interrupt the current call inprogress lets try to ICC the transmitting peer + if (m_network->isPeerLocal(m_status[dstId].ssrc)) + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, false, + m_status[dstId].ssrc); + else + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, true, + m_status[dstId].ssrc); + } - LogWarning(LOG_NET, "DMR, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, slotNo = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, slotNo, streamId, status.peerId, status.srcId, status.dstId, status.slotNo, status.streamId, external); - return false; + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } + } } } } else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { + m_parrotFrames.lock(false); for (auto& pkt : m_parrotFrames) { if (pkt.buffer != nullptr) { delete[] pkt.buffer; } } + m_parrotFrames.unlock(); m_parrotFrames.clear(); } } // this is a new call stream // bryanb: this could be problematic and is naive, if a dstId appears on both slots (which shouldn't happen) + m_status.lock(false); m_status[dstId].callStartTime = pktTime; m_status[dstId].srcId = srcId; m_status[dstId].dstId = dstId; m_status[dstId].slotNo = slotNo; m_status[dstId].streamId = streamId; m_status[dstId].peerId = peerId; + m_status[dstId].ssrc = ssrc; m_status[dstId].activeCall = true; + m_status.unlock(); // is this a private call? if (flco == FLCO::PRIVATE) { + m_statusPVCall.lock(false); m_statusPVCall[dstId].callStartTime = pktTime; m_statusPVCall[dstId].srcId = srcId; m_statusPVCall[dstId].dstId = dstId; m_statusPVCall[dstId].slotNo = slotNo; m_statusPVCall[dstId].streamId = streamId; m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].ssrc = ssrc; m_statusPVCall[dstId].activeCall = true; // find the SSRC of the peer that registered this unit uint32_t regSSRC = m_network->findPeerUnitReg(srcId); m_statusPVCall[dstId].dstPeerId = regSSRC; + m_statusPVCall.unlock(); - LogMessage(LOG_NET, "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, external); + #define PRV_CALL_START_LOG "DMR, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); + } + else { + #define CALL_START_LOG "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_START_LOG); } - else - LogMessage(LOG_NET, "DMR, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); - - m_network->m_callInProgress = true; } } @@ -343,7 +430,9 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } + m_status.lock(false); m_status[dstId].lastPacket = hrc::now(); + m_status.unlock(); bool noConnectedPeerRepeat = false; bool privateCallInProgress = false; @@ -361,14 +450,14 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId privateCallInProgress = false; // trick the system to repeat everywhere } else { // if this is a private call, check if the destination peer is one directly connected to us, if not - // flag the call so it only repeats to external peers + // flag the call so it only repeats to upstream neighbor peers if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { noConnectedPeerRepeat = true; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (conn != nullptr) { - if (conn->isExternalPeer()) { + if (conn->isNeighborFNEPeer()) { continue; } } @@ -384,10 +473,19 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - // repeat traffic to the connected peers + /* + ** MASTER TRAFFIC + */ + + // repeat traffic to nodes peered to us as master if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { + if (peer.second == nullptr) + continue; if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { @@ -396,16 +494,16 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } if (m_network->m_restrictPVCallToRegOnly) { - // is this peer an external peer? - bool external = false; + // is this peer an upstream neighbor peer? + bool neighbor = false; if (conn != nullptr) { - external = conn->isExternalPeer(); + neighbor = conn->isNeighborFNEPeer(); } // is this a private call? - if ((flco == FLCO::PRIVATE) && !external) { + if ((flco == FLCO::PRIVATE) && !neighbor) { // is this a private call? if so only repeat to the peer that registered the unit - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -425,9 +523,9 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); @@ -436,51 +534,49 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, dmrData, dataType, dstId, slotNo); - m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeerQueue(&queue, peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - ssrc, peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebugEx(LOG_DMR, "TagDMRData::processFrame()", "Master, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, fromUpstream = %u", + ssrc, peerId, peer.first, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, fromUpstream); } - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; i++; } } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } // if this is a private call, and we have already repeated to the connected peer that registered - // the unit, don't repeat to any external peers + // the unit, don't repeat to any neighbor FNE peers if (privateCallInProgress && !noConnectedPeerRepeat) { return true; } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to master nodes we have connected to as a peer if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { if (ssrc == dstPeerId) { // skip the peer if it is the source peer continue; } - // is this peer ignored? - if (!isPeerPermitted(dstPeerId, dmrData, streamId, true)) { - continue; - } - - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { continue; } - // skip peer if it isn't enabled - if (!peer.second->isEnabled()) { + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, dmrData, streamId, true)) { continue; } @@ -490,18 +586,15 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, dmrData, dataType, dstId, slotNo); - // are we a peer link? - if (peer.second->isPeerLink()) - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + // are we a replica peer? + if (peer.second->isReplica()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId, false, 0U, ssrc); else peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, external = %u", - ssrc, peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, external); + LogDebugEx(LOG_DMR, "TagDMRData::processFrame()", "Peers, ssrc = %u, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u, pktSeq = %u, stream = %u, fromUpstream = %u", + ssrc, peerId, dstPeerId, seqNo, srcId, dstId, flco, slotNo, len, pktSeq, streamId, fromUpstream); } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; } } } @@ -517,7 +610,7 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId/* && x.second.slotNo == slot*/) { if (x.second.activeCall) return true; @@ -559,6 +652,24 @@ bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, b return true; } +/* Helper to trigger a call takeover from a In-Call control event. */ + +void TagDMRData::triggerCallTakeover(uint32_t dstId) +{ + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status.lock(false); + m_status[dstId].callTakeover = true; + m_status.unlock(); + } +} + /* Helper to playback a parrot frame to the network. */ void TagDMRData::playbackParrot() @@ -568,29 +679,50 @@ void TagDMRData::playbackParrot() return; } + m_parrotPlayback = true; + auto& pkt = m_parrotFrames[0]; + m_parrotFrames.lock(); if (pkt.buffer != nullptr) { + m_lastParrotPeerId = pkt.peerId; + m_lastParrotSrcId = pkt.srcId; + m_lastParrotDstId = pkt.dstId; + if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_DMR, "TagDMRData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } } else { // repeat traffic to the connected peers + uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); + } + + m_network->writePeerQueue(&queue, peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_DMR, "TagDMRData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } + + i++; } + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } delete[] pkt.buffer; } Thread::sleep(60); + m_parrotFrames.unlock(); m_parrotFrames.pop_front(); } @@ -604,7 +736,7 @@ void TagDMRData::write_Ext_Func(uint32_t peerId, uint8_t slot, uint32_t func, ui csbk->setSrcId(arg); csbk->setDstId(dstId); - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, DT_CSBK, %s, op = $%02X, arg = %u, tgt = %u", slot, csbk->toString().c_str(), func, arg, dstId); write_CSBK(peerId, slot, csbk.get()); @@ -619,7 +751,7 @@ void TagDMRData::write_Call_Alrt(uint32_t peerId, uint8_t slot, uint32_t srcId, csbk->setSrcId(srcId); csbk->setDstId(dstId); - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, DT_CSBK, %s, srcId = %u, dstId = %u", slot, csbk->toString().c_str(), srcId, dstId); write_CSBK(peerId, slot, csbk.get()); @@ -656,7 +788,7 @@ void TagDMRData::routeRewrite(uint8_t* buffer, uint32_t peerId, dmr::data::NetDa lc::FullLC fullLC; std::unique_ptr lc = fullLC.decode(data + 2U, dataType); if (lc == nullptr) { - LogWarning(LOG_NET, "DMR Slot %u, bad LC received from the network, replacing", slotNo); + LogWarning(LOG_DMR, "DMR Slot %u, bad LC received from the network, replacing", slotNo); lc = std::make_unique(dmrData.getFLCO(), dmrData.getSrcId(), rewriteDstId); } @@ -671,7 +803,7 @@ void TagDMRData::routeRewrite(uint8_t* buffer, uint32_t peerId, dmr::data::NetDa lc::FullLC fullLC; std::unique_ptr lc = fullLC.decodePI(data + 2U); if (lc == nullptr) { - LogWarning(LOG_NET, "DMR Slot %u, DT_VOICE_PI_HEADER, bad LC received, replacing", slotNo); + LogWarning(LOG_DMR, "DMR Slot %u, DT_VOICE_PI_HEADER, bad LC received, replacing", slotNo); lc = std::make_unique(); } @@ -758,11 +890,11 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat lc::csbk::CSBK_BROADCAST* osp = static_cast(csbk.get()); if (osp->getAnncType() == BroadcastAnncType::ANN_WD_TSCC) { if (m_network->m_disallowAdjStsBcast) { - // LogWarning(LOG_NET, "PEER %u, passing BroadcastAnncType::ANN_WD_TSCC to internal peers is prohibited, dropping", peerId); + // LogWarning(LOG_DMR, "PEER %u, passing BroadcastAnncType::ANN_WD_TSCC to internal peers is prohibited, dropping", peerId); return false; } else { if (m_network->m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u, peerId = %u", dmrData.getSlotNo(), csbk->toString().c_str(), + LogInfoEx(LOG_DMR, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u, peerId = %u", dmrData.getSlotNo(), csbk->toString().c_str(), osp->getSystemId(), osp->getLogicalCh1(), peerId); } } @@ -774,7 +906,7 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat } } else { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogWarning(LOG_NET, "PEER %u (%s), passing CSBK that failed to decode? csbk == nullptr", peerId, peerIdentity.c_str()); + LogWarning(LOG_DMR, "PEER %u (%s), passing CSBK that failed to decode? csbk == nullptr", peerId, peerIdentity.c_str()); } } @@ -783,8 +915,12 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::NetDat /* Helper to determine if the peer is permitted for traffic. */ -bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool external) +bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t streamId, bool fromUpstream) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + if (data.getFLCO() == FLCO::PRIVATE) { if (m_network->m_disallowU2U) return false; @@ -800,10 +936,10 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t connection = m_network->m_peers[peerId]; } - // is this peer a Peer-Link peer? + // is this peer a replica peer? if (connection != nullptr) { - if (connection->isPeerLink()) { - return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + if (connection->isReplica()) { + return true; // replica peers are *always* allowed to receive traffic and no other rules may filter // these peers } } @@ -844,8 +980,8 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { if (connection->isConventionalPeer()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for conventional peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for conventional peers } } } @@ -853,14 +989,14 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t // is this peer a SysView peer? if (connection != nullptr) { if (connection->isSysView()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for SysView peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for SysView peers } } // is this a TG that requires affiliations to repeat? - // NOTE: external peers *always* repeat traffic regardless of affiliation - if (tg.config().affiliated() && !external) { + // NOTE: neighbor FNE peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !fromUpstream) { uint32_t lookupPeerId = peerId; if (connection != nullptr) { if (connection->ccPeerId() > 0U) @@ -889,6 +1025,10 @@ bool TagDMRData::isPeerPermitted(uint32_t peerId, data::NetData& data, uint32_t bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, uint32_t streamId) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + // is the source ID a blacklisted ID? bool rejectUnknownBadCall = false; lookups::RadioId rid = m_network->m_ridLookup->find(data.getSrcId()); @@ -909,7 +1049,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -973,7 +1113,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, case ExtendedFunctions::UNINHIBIT: { if (!pid.peerDefault() && !pid.canIssueInhibit()) { - LogWarning(LOG_NET, "DMR, PEER %u attempted inhibit/unhibit, not authorized", peerId); + LogWarning(LOG_DMR, "DMR, PEER %u attempted inhibit/unhibit, not authorized", peerId); return false; } } @@ -1010,7 +1150,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); return false; } @@ -1034,7 +1174,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + LogWarning(LOG_DMR, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1062,7 +1202,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1097,7 +1237,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogWarning(LOG_NET, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); + LogWarning(LOG_DMR, "DMR slot %s, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", data.getSlotNo(), data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1121,7 +1261,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_SLOT ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_INV_SLOT ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1145,7 +1285,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1173,7 +1313,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, lc::CSBK* csbk, } if (m_network->m_logDenials) - LogError(LOG_NET, "DMR Slot %u, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); + LogError(LOG_DMR, "DMR Slot %u, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", data.getSlotNo(), peerId, data.getSrcId(), data.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); @@ -1205,7 +1345,7 @@ bool TagDMRData::write_CSBK_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[peerId]; if (aff == nullptr) { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); return false; // this will cause no traffic to pass for this peer now...I'm not sure this is good behavior } else { @@ -1222,7 +1362,7 @@ bool TagDMRData::write_CSBK_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI csbk->setSlotNo(slot); if (m_network->m_verbose) { - LogMessage(LOG_NET, "DMR, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u, peerId = %u", + LogInfoEx(LOG_DMR, "DMR, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u, peerId = %u", csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId, peerId); } @@ -1238,7 +1378,7 @@ bool TagDMRData::write_CSBK_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI csbk->setSlotNo(slot); if (m_network->m_verbose) { - LogMessage(LOG_NET, "DMR, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u, peerId = %u", + LogInfoEx(LOG_DMR, "DMR, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u, peerId = %u", csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId, peerId); } @@ -1263,7 +1403,7 @@ void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t sl csbk->setDstId(dstId); if (m_network->m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", slot, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), csbk->getSrcId(), csbk->getDstId()); } @@ -1312,35 +1452,39 @@ void TagDMRData::write_CSBK(uint32_t peerId, uint8_t slot, lc::CSBK* csbk) } if (peerId > 0U) { - m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId); } else { // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } - m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, true); + m_network->writePeerQueue(&queue, peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u", + LogDebugEx(LOG_DMR, "TagDMRData::write_CSBK()", "peer = %u, slotNo = %u, len = %u, stream = %u", peer.first, slot, messageLength, streamId); } i++; } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } - // repeat traffic to external peers + // repeat traffic to neighbor FNE peers if (m_network->m_host->m_peerNetworks.size() > 0U) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, peer = %u, slotNo = %u, len = %u, stream = %u", + LogDebugEx(LOG_DMR, "TagDMRData::write_CSBK()", "peer = %u, slotNo = %u, len = %u, stream = %u", dstPeerId, slot, messageLength, streamId); } } diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index c1313b2e3..1786531ad 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -59,10 +59,10 @@ namespace network * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -76,6 +76,11 @@ namespace network */ bool processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + /** + * @brief Helper to trigger a call takeover from a In-Call control event. + */ + void triggerCallTakeover(uint32_t dstId); + /** * @brief Helper to playback a parrot frame to the network. */ @@ -86,6 +91,38 @@ namespace network */ bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + /** + * @brief Helper to determine if the parrot is playing back frames. + * @returns True, if parrot playback was started, otherwise false. + */ + bool isParrotPlayback() const { return m_parrotPlayback; } + /** + * @brief Helper to clear the parrot playback flag. + */ + void clearParrotPlayback() + { + m_parrotPlayback = false; + m_lastParrotPeerId = 0U; + m_lastParrotSrcId = 0U; + m_lastParrotDstId = 0U; + } + + /** + * @brief Returns the last processed peer ID for a parrot frame. + * @return uint32_t Peer ID. + */ + uint32_t lastParrotPeerId() const { return m_lastParrotPeerId; } + /** + * @brief Returns the last processed source ID for a parrot frame. + * @return uint32_t Source ID. + */ + uint32_t lastParrotSrcId() const { return m_lastParrotSrcId; } + /** + * @brief Returns the last processed destination ID for a parrot frame. + * @return uint32_t Destination ID. + */ + uint32_t lastParrotDstId() const { return m_lastParrotDstId; } + /** * @brief Helper to write a extended function packet on the RF interface. * @param peerId Peer ID. @@ -150,6 +187,10 @@ namespace network }; concurrent::deque m_parrotFrames; bool m_parrotFramesReady; + bool m_parrotPlayback; + uint32_t m_lastParrotPeerId; + uint32_t m_lastParrotSrcId; + uint32_t m_lastParrotDstId; /** * @brief Represents the receive status of a call. @@ -178,6 +219,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Synchronization Source. + */ + uint32_t ssrc; /** * @brief Destination Peer ID. */ @@ -186,6 +231,10 @@ namespace network * @brief Flag indicating this call is active with traffic currently in progress. */ bool activeCall; + /** + * @brief Flag indicating the metadata for the call on the next frame will be overwritten. + */ + bool callTakeover; /** * @brief Helper to reset call status. @@ -197,7 +246,9 @@ namespace network slotNo = 0U; streamId = 0U; peerId = 0U; + ssrc = 0U; activeCall = false; + callTakeover = false; } }; typedef std::pair StatusMapPair; @@ -244,10 +295,10 @@ namespace network * @param peerId Peer ID. * @param data Instance of data::NetData DMR data container class. * @param streamId Stream ID. - * @param external Flag indicating this traffic came from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if valid, otherwise false. */ - bool isPeerPermitted(uint32_t peerId, dmr::data::NetData& data, uint32_t streamId, bool external = false); + bool isPeerPermitted(uint32_t peerId, dmr::data::NetData& data, uint32_t streamId, bool fromUpstream = false); /** * @brief Helper to validate the DMR call stream. * @param peerId Peer ID. diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index cfd7a9ae4..487016722 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -23,6 +23,7 @@ #include "network/FNENetwork.h" #include "network/callhandler/TagNXDNData.h" #include "HostFNE.h" +#include "FNEMain.h" using namespace system_clock; using namespace network; @@ -33,12 +34,6 @@ using namespace nxdn::defines; #include #include -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const uint32_t CALL_COLL_TIMEOUT = 10U; - // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -49,6 +44,10 @@ TagNXDNData::TagNXDNData(FNENetwork* network, bool debug) : m_network(network), m_parrotFrames(), m_parrotFramesReady(false), + m_parrotPlayback(false), + m_lastParrotPeerId(0U), + m_lastParrotSrcId(0U), + m_lastParrotDstId(0U), m_status(), m_statusPVCall(), m_debug(debug) @@ -62,7 +61,7 @@ TagNXDNData::~TagNXDNData() = default; /* Process a data frame from the network. */ -bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -185,7 +184,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is the stream valid? if (validate(peerId, lc, messageType, streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, lc, messageType, streamId, external)) { + if (!isPeerPermitted(peerId, lc, messageType, streamId, fromUpstream)) { return false; } @@ -196,14 +195,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this the end of the call stream? if (messageType == MessageType::RTCH_TX_REL || messageType == MessageType::RTCH_TX_REL_EX) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid TX_REL, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } RxStatus status = m_status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -215,16 +214,16 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; - LogMessage(LOG_NET, "NXDN, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + LogInfoEx(LOG_NET, "NXDN, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); m_network->m_parrotDelayTimer.start(); } } // is this a private call? - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -233,12 +232,19 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); - LogMessage(LOG_NET, "NXDN, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); + #define PRV_CALL_END_LOG "NXDN, Private Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); + } + else { + #define CALL_END_LOG "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_END_LOG); } - else - LogMessage(LOG_NET, "NXDN, Call End, peer = %u, ssrc = %u, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -255,20 +261,19 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } m_network->eraseStreamPktSeq(peerId, streamId); - m_network->m_callInProgress = false; } } // is this a new call stream? if ((messageType != MessageType::RTCH_TX_REL && messageType != MessageType::RTCH_TX_REL_EX)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "NXDN, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -277,74 +282,155 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; + + // is the call being taken over? + if (status.callTakeover) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "NXDN, Call Source Switched (Takeover), peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status[dstId].callTakeover = false; // reset takeover flag + m_status.unlock(); + + status = m_status[dstId]; + } + if (streamId != status.streamId) { // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch // from one source to another (primarily from bridge resources) if (switchOver) { - status.streamId = streamId; - status.srcId = srcId; - LogMessage(LOG_NET, "NXDN, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].ssrc = ssrc; + if (status.srcId == 0U) + m_status[dstId].srcId = srcId; + if (status.srcId != srcId) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "NXDN, Call Source Switched, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + m_status[dstId].srcId = srcId; + } + m_status.unlock(); } + else { + if (status.srcId != 0U && status.srcId != srcId) { + bool hasCallPriority = false; + + // determine if the peer trying to transmit has call priority + if (m_network->m_callCollisionTimeout > 0U) { + m_network->m_peers.shared_lock(); + for (auto peer : m_network->m_peers) { + if (peerId == peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + hasCallPriority = conn->hasCallPriority(); + break; + } + } + } + m_network->m_peers.shared_unlock(); + } - if (status.srcId != 0U && status.srcId != srcId) { - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "NXDN, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status[dstId].reset(); - m_network->m_callInProgress = false; - } + // perform standard call collision if the call collision timeout is set *and* + // the peer doesn't have call priority + if (m_network->m_callCollisionTimeout > 0U && !hasCallPriority) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "NXDN, Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "NXDN, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + return false; + } + } else { + if (hasCallPriority && !m_network->m_disallowInCallCtrl) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "NXDN, Call Source Switched (Priority), peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + + // since we're gonna switch over the stream and interrupt the current call inprogress lets try to ICC the transmitting peer + if (m_network->isPeerLocal(m_status[dstId].ssrc)) + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, false, + m_status[dstId].ssrc); + else + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, true, + m_status[dstId].ssrc); + } - LogWarning(LOG_NET, "NXDN, Call Collision, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - return false; + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } + } } } } else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { + m_parrotFrames.lock(false); for (auto& pkt : m_parrotFrames) { if (pkt.buffer != nullptr) { delete[] pkt.buffer; } } + m_parrotFrames.unlock(); m_parrotFrames.clear(); } } // this is a new call stream + m_status.lock(false); m_status[dstId].callStartTime = pktTime; m_status[dstId].srcId = srcId; m_status[dstId].dstId = dstId; m_status[dstId].streamId = streamId; m_status[dstId].peerId = peerId; + m_status[dstId].ssrc = ssrc; m_status[dstId].activeCall = true; + m_status.unlock(); // is this a private call? if (!group) { + m_statusPVCall.lock(false); m_statusPVCall[dstId].callStartTime = pktTime; m_statusPVCall[dstId].srcId = srcId; m_statusPVCall[dstId].dstId = dstId; m_statusPVCall[dstId].streamId = streamId; m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].ssrc = ssrc; m_statusPVCall[dstId].activeCall = true; // find the SSRC of the peer that registered this unit uint32_t regSSRC = m_network->findPeerUnitReg(srcId); m_statusPVCall[dstId].dstPeerId = regSSRC; + m_statusPVCall.unlock(); - LogMessage(LOG_NET, "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, external); + #define PRV_CALL_START_LOG "NXDN, Private Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); + } + else { + #define CALL_START_LOG "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_START_LOG); } - else - LogMessage(LOG_NET, "NXDN, Call Start, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, srcId, dstId, streamId, external); - - m_network->m_callInProgress = true; } } } @@ -373,7 +459,9 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } } + m_status.lock(false); m_status[dstId].lastPacket = hrc::now(); + m_status.unlock(); bool noConnectedPeerRepeat = false; bool privateCallInProgress = false; @@ -391,14 +479,14 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI privateCallInProgress = false; // trick the system to repeat everywhere } else { // if this is a private call, check if the destination peer is one directly connected to us, if not - // flag the call so it only repeats to external peers + // flag the call so it only repeats to neighbor FNE peers if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { noConnectedPeerRepeat = true; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (conn != nullptr) { - if (conn->isExternalPeer()) { + if (conn->isNeighborFNEPeer()) { continue; } } @@ -414,10 +502,19 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } } - // repeat traffic to the connected peers + /* + ** MASTER TRAFFIC + */ + + // repeat traffic to nodes peered to us as master if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { + if (peer.second == nullptr) + continue; if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { @@ -426,16 +523,16 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI } if (m_network->m_restrictPVCallToRegOnly) { - // is this peer an external peer? - bool external = false; + // is this peer an upstream neighbor peer? + bool neighbor = false; if (conn != nullptr) { - external = conn->isExternalPeer(); + neighbor = conn->isNeighborFNEPeer(); } // is this a private call? - if (!group && !external) { + if (!group && !neighbor) { // is this a private call? if so only repeat to the peer that registered the unit - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -455,9 +552,9 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI continue; } - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); @@ -466,51 +563,49 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, messageType, dstId); - m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeerQueue(&queue, peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - ssrc, peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_NXDN, "TagNXDNData::processFrame()", "Master, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, fromUpstream = %u", + ssrc, peerId, peer.first, messageType, srcId, dstId, len, pktSeq, streamId, fromUpstream); } - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; i++; } } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } // if this is a private call, and we have already repeated to the connected peer that registered - // the unit, don't repeat to any external peers + // the unit, don't repeat to any neighbor FNE peers if (privateCallInProgress && !noConnectedPeerRepeat) { return true; } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to master nodes we have connected to as a peer if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { if (ssrc == dstPeerId) { // skip the peer if it is the source peer continue; } - // is this peer ignored? - if (!isPeerPermitted(dstPeerId, lc, messageType, streamId, true)) { - continue; - } - - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { continue; } - // skip peer if it isn't enabled - if (!peer.second->isEnabled()) { + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, lc, messageType, streamId, true)) { continue; } @@ -520,18 +615,15 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, messageType, dstId); - // are we a peer link? - if (peer.second->isPeerLink()) - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + // are we a replica peer? + if (peer.second->isReplica()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId, false, 0U, ssrc); else peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - ssrc, peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_NXDN, "TagNXDNData::processFrame()", "Peers, ssrc = %u, srcPeer = %u, dstPeer = %u, messageType = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, fromUpstream = %u", + ssrc, peerId, dstPeerId, messageType, srcId, dstId, len, pktSeq, streamId, fromUpstream); } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; } } } @@ -547,7 +639,7 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -589,6 +681,24 @@ bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUni return true; } +/* Helper to trigger a call takeover from a In-Call control event. */ + +void TagNXDNData::triggerCallTakeover(uint32_t dstId) +{ + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status.lock(false); + m_status[dstId].callTakeover = true; + m_status.unlock(); + } +} + /* Helper to playback a parrot frame to the network. */ void TagNXDNData::playbackParrot() @@ -598,29 +708,50 @@ void TagNXDNData::playbackParrot() return; } + m_parrotPlayback = true; + auto& pkt = m_parrotFrames[0]; + m_parrotFrames.lock(); if (pkt.buffer != nullptr) { + m_lastParrotPeerId = pkt.peerId; + m_lastParrotSrcId = pkt.srcId; + m_lastParrotDstId = pkt.dstId; + if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_NXDN, "TagNXDNData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } } else { // repeat traffic to the connected peers + uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); + } + + m_network->writePeerQueue(&queue, peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "NXDN, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_NXDN, "TagNXDNData::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } + + i++; } + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } delete[] pkt.buffer; } Thread::sleep(60); + m_parrotFrames.unlock(); m_parrotFrames.pop_front(); } @@ -675,8 +806,12 @@ bool TagNXDNData::peerRewrite(uint32_t peerId, uint32_t& dstId, bool outbound) /* Helper to determine if the peer is permitted for traffic. */ -bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool external) +bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool fromUpstream) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + if (!lc.getGroup()) { if (m_network->m_disallowU2U) return false; @@ -692,10 +827,10 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message connection = m_network->m_peers[peerId]; } - // is this peer a Peer-Link peer? + // is this peer a replica peer? if (connection != nullptr) { - if (connection->isPeerLink()) { - return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + if (connection->isReplica()) { + return true; // replica peers are *always* allowed to receive traffic and no other rules may filter // these peers } } @@ -736,8 +871,8 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { if (connection->isConventionalPeer()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for conventional peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for conventional peers } } } @@ -745,14 +880,14 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message // is this peer a SysView peer? if (connection != nullptr) { if (connection->isSysView()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for SysView peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for SysView peers } } // is this a TG that requires affiliations to repeat? - // NOTE: external peers *always* repeat traffic regardless of affiliation - if (tg.config().affiliated() && !external) { + // NOTE: neighbor FNE peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !fromUpstream) { uint32_t lookupPeerId = peerId; if (connection != nullptr) { if (connection->ccPeerId() > 0U) @@ -781,6 +916,10 @@ bool TagNXDNData::isPeerPermitted(uint32_t peerId, lc::RTCH& lc, uint8_t message bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, uint32_t streamId) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + // is the source ID a blacklisted ID? bool rejectUnknownBadCall = false; lookups::RadioId rid = m_network->m_ridLookup->find(lc.getSrcId()); @@ -800,7 +939,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + LogError(LOG_NXDN, INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -839,7 +978,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + LogError(LOG_NXDN, INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -864,7 +1003,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + LogWarning(LOG_NXDN, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -893,7 +1032,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + LogError(LOG_NXDN, INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -927,7 +1066,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogWarning(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); + LogWarning(LOG_NXDN, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -950,7 +1089,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + LogError(LOG_NXDN, INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -978,7 +1117,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } if (m_network->m_logDenials) - LogError(LOG_NET, "NXDN, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); + LogError(LOG_NXDN, INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, lc.getSrcId(), lc.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); @@ -1004,7 +1143,7 @@ bool TagNXDNData::write_Message_Grant(uint32_t peerId, uint32_t srcId, uint32_t lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[peerId]; if (aff == nullptr) { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); return false; // this will cause no traffic to pass for this peer now...I'm not sure this is good behavior } else { @@ -1024,7 +1163,7 @@ bool TagNXDNData::write_Message_Grant(uint32_t peerId, uint32_t srcId, uint32_t rcch->setPriority(priority); if (m_network->m_verbose) { - LogMessage(LOG_NET, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u, peerId = %u", + LogInfoEx(LOG_NXDN, "%s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u, peerId = %u", rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), rcch->getSrcId(), rcch->getDstId(), peerId); } @@ -1051,7 +1190,7 @@ void TagNXDNData::write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t d rcch->setDstId(dstId); if (m_network->m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_NXDN, "MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } @@ -1102,5 +1241,5 @@ void TagNXDNData::write_Message(uint32_t peerId, lc::RCCH* rcch) } uint32_t streamId = m_network->createStreamId(); - m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false); + m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId); } diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index f48328cd0..427cf5ba8 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -58,10 +58,10 @@ namespace network * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -74,6 +74,11 @@ namespace network */ bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + /** + * @brief Helper to trigger a call takeover from a In-Call control event. + */ + void triggerCallTakeover(uint32_t dstId); + /** * @brief Helper to playback a parrot frame to the network. */ @@ -84,6 +89,38 @@ namespace network */ bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + /** + * @brief Helper to determine if the parrot is playing back frames. + * @returns True, if parrot playback was started, otherwise false. + */ + bool isParrotPlayback() const { return m_parrotPlayback; } + /** + * @brief Helper to clear the parrot playback flag. + */ + void clearParrotPlayback() + { + m_parrotPlayback = false; + m_lastParrotPeerId = 0U; + m_lastParrotSrcId = 0U; + m_lastParrotDstId = 0U; + } + + /** + * @brief Returns the last processed peer ID for a parrot frame. + * @return uint32_t Peer ID. + */ + uint32_t lastParrotPeerId() const { return m_lastParrotPeerId; } + /** + * @brief Returns the last processed source ID for a parrot frame. + * @return uint32_t Source ID. + */ + uint32_t lastParrotSrcId() const { return m_lastParrotSrcId; } + /** + * @brief Returns the last processed destination ID for a parrot frame. + * @return uint32_t Destination ID. + */ + uint32_t lastParrotDstId() const { return m_lastParrotDstId; } + private: FNENetwork* m_network; @@ -119,6 +156,10 @@ namespace network }; concurrent::deque m_parrotFrames; bool m_parrotFramesReady; + bool m_parrotPlayback; + uint32_t m_lastParrotPeerId; + uint32_t m_lastParrotSrcId; + uint32_t m_lastParrotDstId; /** * @brief Represents the receive status of a call. @@ -143,6 +184,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Synchronization Source. + */ + uint32_t ssrc; /** * @brief Destination Peer ID. */ @@ -151,6 +196,10 @@ namespace network * @brief Flag indicating this call is active with traffic currently in progress. */ bool activeCall; + /** + * @brief Flag indicating the metadata for the call on the next frame will be overwritten. + */ + bool callTakeover; /** * @brief Helper to reset call status. @@ -161,7 +210,9 @@ namespace network dstId = 0U; streamId = 0U; peerId = 0U; + ssrc = 0U; activeCall = false; + callTakeover = false; } }; typedef std::pair StatusMapPair; @@ -194,10 +245,10 @@ namespace network * @param lc Instance of nxdn::lc::RTCH. * @param messageType Message Type. * @param streamId Stream ID. - * @param external Flag indicating this traffic came from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if permitted, otherwise false. */ - bool isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool external = false); + bool isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId, bool fromUpstream = false); /** * @brief Helper to validate the NXDN call stream. * @param peerId Peer ID. diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 4728f7d4c..af1f09ea3 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -18,6 +18,7 @@ #include "network/FNENetwork.h" #include "network/callhandler/TagP25Data.h" #include "HostFNE.h" +#include "FNEMain.h" using namespace system_clock; using namespace network; @@ -34,7 +35,6 @@ using namespace p25::defines; // --------------------------------------------------------------------------- const uint32_t GRANT_TIMER_TIMEOUT = 15U; -const uint32_t CALL_COLL_TIMEOUT = 10U; // --------------------------------------------------------------------------- // Public Class Members @@ -47,6 +47,10 @@ TagP25Data::TagP25Data(FNENetwork* network, bool debug) : m_parrotFrames(), m_parrotFramesReady(false), m_parrotFirstFrame(true), + m_parrotPlayback(false), + m_lastParrotPeerId(0U), + m_lastParrotSrcId(0U), + m_lastParrotDstId(0U), m_status(), m_statusPVCall(), m_packetData(nullptr), @@ -66,7 +70,7 @@ TagP25Data::~TagP25Data() /* Process a data frame from the network. */ -bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external) +bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -100,7 +104,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (duid == DUID::PDU) { if (m_network->m_disablePacketData) return false; - return m_packetData->processFrame(data, len, peerId, pktSeq, streamId, external); + return m_packetData->processFrame(data, len, peerId, pktSeq, streamId, fromUpstream); } // perform TGID route rewrites if configured @@ -131,7 +135,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } if (m_debug) { - LogDebug(LOG_NET, P25_HDU_STR ", HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", dstId, algId, kid); + LogDebug((fromUpstream) ? LOG_PEER : LOG_MASTER, P25_HDU_STR ", HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", dstId, algId, kid); if (algId != ALGO_UNENCRYPT) { LogDebug(LOG_NET, P25_HDU_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", @@ -173,16 +177,49 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is the stream valid? if (validate(peerId, control, duid, tsbk.get(), streamId)) { // is this peer ignored? - if (!isPeerPermitted(peerId, control, duid, streamId, external)) { + if (!isPeerPermitted(peerId, control, duid, streamId, fromUpstream)) { return false; } + // special case: if we've received a TSDU and its an LC_CALL_TERM; lets validate the source peer ID, + // LC_CALL_TERMs should only be sourced from the peer that initiated the call; other peers should not be + // transmitting LC_CALL_TERMs for the call + if (duid == DUID::TSDU && tsbk->getLCO() == LCO::CALL_TERM) { + if (dstId == 0U) { + LogWarning(LOG_NET, "P25, invalid TSDU, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); + return false; + } + + RxStatus status = m_status[dstId]; + + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + if (status.peerId != peerId) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Illegal Call Termination, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + return false; + } else { + #define REQ_CALL_END_LOG "P25, Requested Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, REQ_CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, REQ_CALL_END_LOG); + } + } + } + // specifically only check the following logic for end of call or voice frames if (duid != DUID::TSDU && duid != DUID::PDU) { // is this the end of the call stream? if ((duid == DUID::TDU) || (duid == DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, ssrc, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid TDU, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, srcId, dstId, streamId, fromUpstream); return false; } @@ -200,7 +237,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -209,26 +246,26 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_status.end()) { if (grantDemand && !switchOver) { - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Grant Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); return false; } else { m_status[dstId].reset(); - // is this a parrot talkgroup? if so, clear any remaining frames from the buffer + // is this a parrot talkgroup? if so, reset parrot states lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { if (m_parrotFrames.size() > 0) { m_parrotFramesReady = true; m_parrotFirstFrame = true; - LogMessage(LOG_NET, "P25, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); + LogInfoEx(LOG_NET, "P25, Parrot Playback will Start, peer = %u, srcId = %u", peerId, srcId); m_network->m_parrotDelayTimer.start(); } } // is this a private call? - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -237,12 +274,19 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_statusPVCall.end()) { m_statusPVCall[dstId].reset(); - LogMessage(LOG_NET, "P25, Private Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); + #define PRV_CALL_END_LOG "P25, Private Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_END_LOG); + } + else { + #define CALL_END_LOG "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_END_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_END_LOG); } - else - LogMessage(LOG_NET, "P25, Call End, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, duration = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, duration / 1000, streamId, external); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -259,7 +303,6 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } m_network->eraseStreamPktSeq(peerId, streamId); - m_network->m_callInProgress = false; } } } @@ -267,13 +310,13 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // is this a new call stream? if ((duid != DUID::TDU) && (duid != DUID::TDULC)) { if (srcId == 0U && dstId == 0U) { - LogWarning(LOG_NET, "P25, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", peerId, srcId, dstId, streamId, external); + LogWarning(LOG_NET, "P25, invalid call, peer = %u, ssrc = %u, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, srcId, dstId, streamId, fromUpstream); return false; } bool switchOver = (data[14U] & network::NET_CTRL_SWITCH_OVER) == network::NET_CTRL_SWITCH_OVER; - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -282,74 +325,155 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId }); if (it != m_status.end()) { RxStatus status = m_status[dstId]; + + // is the call being taken over? + if (status.callTakeover) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Source Switched (Takeover), peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSsrc = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.ssrc, status.srcId, status.dstId, status.streamId, fromUpstream); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status[dstId].callTakeover = false; // reset takeover flag + m_status.unlock(); + + status = m_status[dstId]; + } + if (streamId != status.streamId && ((duid != DUID::TDU) && (duid != DUID::TDULC))) { // perform TG switch over -- this can happen in special conditions where a TG may rapidly switch // from one source to another (primarily from bridge resources) if (switchOver) { - status.streamId = streamId; - status.srcId = srcId; - LogMessage(LOG_NET, "P25, Call Source Switched, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - } - - if (status.srcId != 0U && status.srcId != srcId) { - uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); - if ((lastPktDuration / 1000) > CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "P25, Call Collision, lasted more then %us with no further updates, forcibly ending call"); - m_status[dstId].reset(); - m_network->m_callInProgress = false; + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].ssrc = ssrc; + if (status.srcId == 0U) + m_status[dstId].srcId = srcId; + if (status.srcId != srcId) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Source Switched, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + m_status[dstId].srcId = srcId; } + m_status.unlock(); + } else { + if (status.srcId != 0U && status.srcId != srcId) { + bool hasCallPriority = false; + + // determine if the peer trying to transmit has call priority + if (m_network->m_callCollisionTimeout > 0U) { + m_network->m_peers.shared_lock(); + for (auto peer : m_network->m_peers) { + if (peerId == peer.first) { + FNEPeerConnection* conn = peer.second; + if (conn != nullptr) { + hasCallPriority = conn->hasCallPriority(); + break; + } + } + } + m_network->m_peers.shared_unlock(); + } - LogWarning(LOG_NET, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, external); - return false; + // perform standard call collision if the call collision timeout is set *and* + // the peer doesn't have call priority + if (m_network->m_callCollisionTimeout > 0U && !hasCallPriority) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status.lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } + else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Collision, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.srcId, status.dstId, status.streamId, fromUpstream); + return false; + } + } else { + if (hasCallPriority && !m_network->m_disallowInCallCtrl) { + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Call Source Switched (Priority), peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, rxPeer = %u, rxSsrc = %u, rxSrcId = %u, rxDstId = %u, rxStreamId = %u, fromUpstream = %u", + peerId, ssrc, sysId, netId, srcId, dstId, streamId, status.peerId, status.ssrc, status.srcId, status.dstId, status.streamId, fromUpstream); + + // since we're gonna switch over the stream and interrupt the current call inprogress lets try to ICC the transmitting peer + if (m_network->isPeerLocal(m_status[dstId].ssrc)) + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, false, + m_status[dstId].ssrc); + else + m_network->writePeerICC(m_status[dstId].peerId, m_status[dstId].streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, dstId, 0U, true, true, + m_status[dstId].ssrc); + } + + m_status.lock(false); + m_status[dstId].streamId = streamId; + m_status[dstId].srcId = srcId; + m_status[dstId].ssrc = ssrc; + m_status.unlock(); + } + } } } } else { // is this a parrot talkgroup? if so, clear any remaining frames from the buffer lookups::TalkgroupRuleGroupVoice tg = m_network->m_tidLookup->find(dstId); - if (tg.config().parrot()) { + if (tg.config().parrot() && !m_parrotPlayback) { m_parrotFramesReady = false; if (m_parrotFrames.size() > 0) { + m_parrotFrames.lock(false); for (auto& pkt : m_parrotFrames) { if (pkt.buffer != nullptr) { delete[] pkt.buffer; } } + m_parrotFrames.unlock(); m_parrotFrames.clear(); } } // this is a new call stream + m_status.lock(false); m_status[dstId].callStartTime = pktTime; m_status[dstId].srcId = srcId; m_status[dstId].dstId = dstId; m_status[dstId].streamId = streamId; m_status[dstId].peerId = peerId; + m_status[dstId].ssrc = ssrc; m_status[dstId].activeCall = true; + m_status.unlock(); // is this a private call? if (lco == LCO::PRIVATE) { + m_statusPVCall.lock(false); m_statusPVCall[dstId].callStartTime = pktTime; m_statusPVCall[dstId].srcId = srcId; m_statusPVCall[dstId].dstId = dstId; m_statusPVCall[dstId].streamId = streamId; m_statusPVCall[dstId].peerId = peerId; + m_statusPVCall[dstId].ssrc = ssrc; m_statusPVCall[dstId].activeCall = true; // find the SSRC of the peer that registered this unit uint32_t regSSRC = m_network->findPeerUnitReg(dstId); m_statusPVCall[dstId].dstPeerId = regSSRC; + m_statusPVCall.unlock(); - LogMessage(LOG_NET, "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); + #define PRV_CALL_START_LOG "P25, Private Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, PRV_CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, PRV_CALL_START_LOG); + } + else { + #define CALL_START_LOG "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, fromUpstream = %u", peerId, ssrc, sysId, netId, srcId, dstId, streamId, fromUpstream + if (m_network->m_logUpstreamCallStartEnd && fromUpstream) + LogInfoEx(LOG_PEER, CALL_START_LOG); + else if (!fromUpstream) + LogInfoEx(LOG_MASTER, CALL_START_LOG); } - else - LogMessage(LOG_NET, "P25, Call Start, peer = %u, ssrc = %u, sysId = $%03X, netId = $%05X, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, ssrc, sysId, netId, srcId, dstId, streamId, external); - - m_network->m_callInProgress = true; } } } @@ -383,7 +507,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId return false; } + m_status.lock(false); m_status[dstId].lastPacket = hrc::now(); + m_status.unlock(); bool noConnectedPeerRepeat = false; bool privateCallInProgress = false; @@ -392,7 +518,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId if (m_network->m_restrictPVCallToRegOnly) { if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { // is this a private call? if so only repeat to the peer that registered the unit - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == control.getDstId()) { if (x.second.activeCall) return true; @@ -415,14 +541,14 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId privateCallInProgress = false; // trick the system to repeat everywhere } else { // if this is a private call, check if the destination peer is one directly connected to us, if not - // flag the call so it only repeats to external peers + // flag the call so it only repeats to neighbor FNE peers if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { noConnectedPeerRepeat = true; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (conn != nullptr) { - if (conn->isExternalPeer()) { + if (conn->isNeighborFNEPeer()) { continue; } } @@ -438,10 +564,19 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } } - // repeat traffic to the connected peers + /* + ** MASTER TRAFFIC + */ + + // repeat traffic to nodes connected to us as peers if (m_network->m_peers.size() > 0U && !noConnectedPeerRepeat) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { + if (peer.second == nullptr) + continue; if (peerId != peer.first) { FNEPeerConnection* conn = peer.second; if (ssrc == peer.first) { @@ -450,16 +585,16 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId } if (m_network->m_restrictPVCallToRegOnly) { - // is this peer an external peer? - bool external = false; + // is this peer an upstream neighbor peer? + bool neighbor = false; if (conn != nullptr) { - external = conn->isExternalPeer(); + neighbor = conn->isNeighborFNEPeer(); } // is this a private call? - if ((lco == LCO::PRIVATE) && !external) { + if ((lco == LCO::PRIVATE) && !neighbor) { // is this a private call? if so only repeat to the peer that registered the unit - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -484,9 +619,9 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId continue; } - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } DECLARE_UINT8_ARRAY(outboundPeerBuffer, len); @@ -495,51 +630,49 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, peer.first, duid, dstId); - m_network->writePeer(peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, true); + m_network->writePeerQueue(&queue, peer.first, ssrc, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - ssrc, peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_P25, "TagP25Data::processFrame()", "Master, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, fromUpstream = %u", + ssrc, peerId, peer.first, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, fromUpstream); } - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; i++; } } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } // if this is a private call, and we have already repeated to the connected peer that registered - // the unit, don't repeat to any external peers + // the unit, don't repeat to any neighbor FNE peers if (privateCallInProgress && !noConnectedPeerRepeat) { return true; } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to master nodes we have connected to as a peer if (m_network->m_host->m_peerNetworks.size() > 0U && !tg.config().parrot()) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { if (ssrc == dstPeerId) { // skip the peer if it is the source peer continue; } - // is this peer ignored? - if (!isPeerPermitted(dstPeerId, control, duid, streamId, true)) { - continue; - } - - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { continue; } - // skip peer if it isn't enabled - if (!peer.second->isEnabled()) { + // is this peer ignored? + if (!isPeerPermitted(dstPeerId, control, duid, streamId, true)) { continue; } @@ -549,21 +682,18 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId // perform TGID route rewrites if configured routeRewrite(outboundPeerBuffer, dstPeerId, duid, dstId); - // process TSDUs going to external peers - if (processTSDUToExternal(outboundPeerBuffer, peerId, dstPeerId, duid)) { - // are we a peer link? - if (peer.second->isPeerLink()) - peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, false, false, 0U, ssrc); + // process TSDUs going to neighbor FNE peers + if (processTSDUToNeighbor(outboundPeerBuffer, peerId, dstPeerId, duid)) { + // are we a replica peer? + if (peer.second->isReplica()) + peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId, false, 0U, ssrc); else peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, outboundPeerBuffer, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, external = %u", - ssrc, peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, external); + LogDebugEx(LOG_P25, "TagP25Data::processFrame()", "Peers, ssrc = %u, srcPeer = %u, dstPeer = %u, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u, pktSeq = %u, streamId = %u, fromUpstream = %u", + ssrc, peerId, dstPeerId, duid, lco, MFId, srcId, dstId, len, pktSeq, streamId, fromUpstream); } } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; } } } @@ -579,7 +709,7 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { // if we have an Rx status for the destination deny the grant - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { if (x.second.dstId == dstId) { if (x.second.activeCall) return true; @@ -621,6 +751,24 @@ bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit return true; } +/* Helper to trigger a call takeover from a In-Call control event. */ + +void TagP25Data::triggerCallTakeover(uint32_t dstId) +{ + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair& x) { + if (x.second.dstId == dstId) { + if (x.second.activeCall) + return true; + } + return false; + }); + if (it != m_status.end()) { + m_status.lock(false); + m_status[dstId].callTakeover = true; + m_status.unlock(); + } +} + /* Helper to playback a parrot frame to the network. */ void TagP25Data::playbackParrot() @@ -628,10 +776,14 @@ void TagP25Data::playbackParrot() if (m_parrotFrames.size() == 0) { m_parrotFramesReady = false; m_parrotFirstFrame = true; + m_parrotPlayback = false; return; } + m_parrotPlayback = true; + auto& pkt = m_parrotFrames[0]; + m_parrotFrames.lock(); if (pkt.buffer != nullptr) { if (m_parrotFirstFrame) { if (m_network->m_parrotGrantDemand) { @@ -653,15 +805,15 @@ void TagP25Data::playbackParrot() UInt8Array message = m_network->createP25_TDUMessage(messageLength, control, lsd, controlByte); if (message != nullptr) { if (m_network->m_parrotOnlyOriginating) { - LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", pkt.peerId, srcId, dstId); + LogInfoEx(LOG_P25, "Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", pkt.peerId, srcId, dstId); m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, - RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); + RTP_END_OF_CALL_SEQ, m_network->createStreamId()); } else { // repeat traffic to the connected peers for (auto peer : m_network->m_peers) { - LogMessage(LOG_NET, "P25, Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", peer.first, srcId, dstId); + LogInfoEx(LOG_P25, "Parrot Grant Demand, peer = %u, srcId = %u, dstId = %u", peer.first, srcId, dstId); m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, - RTP_END_OF_CALL_SEQ, m_network->createStreamId(), false); + RTP_END_OF_CALL_SEQ, m_network->createStreamId()); } } } @@ -670,26 +822,44 @@ void TagP25Data::playbackParrot() m_parrotFirstFrame = false; } + m_lastParrotPeerId = pkt.peerId; + m_lastParrotSrcId = pkt.srcId; + m_lastParrotDstId = pkt.dstId; + if (m_network->m_parrotOnlyOriginating) { - m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + m_network->writePeer(pkt.peerId, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebugEx(LOG_P25, "TagP25Data::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", pkt.peerId, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } } else { // repeat traffic to the connected peers + uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId, false); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); + } + + m_network->writePeerQueue(&queue, peer.first, pkt.peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, pkt.buffer, pkt.bufferLen, pkt.pktSeq, pkt.streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", + LogDebug(LOG_P25, "TagP25Data::playbackParrot()", "Parrot, dstPeer = %u, len = %u, pktSeq = %u, streamId = %u", peer.first, pkt.bufferLen, pkt.pktSeq, pkt.streamId); } + + i++; } + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } delete[] pkt.buffer; } Thread::sleep(180); + m_parrotFrames.unlock(); m_parrotFrames.pop_front(); } @@ -701,7 +871,7 @@ void TagP25Data::write_TSDU_Call_Alrt(uint32_t peerId, uint32_t srcId, uint32_t iosp->setSrcId(srcId); iosp->setDstId(dstId); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId); + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId); write_TSDU(peerId, iosp.get()); } @@ -715,7 +885,7 @@ void TagP25Data::write_TSDU_Radio_Mon(uint32_t peerId, uint32_t srcId, uint32_t iosp->setDstId(dstId); iosp->setTxMult(txMult); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId, txMult); + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId, txMult); write_TSDU(peerId, iosp.get()); } @@ -734,7 +904,7 @@ void TagP25Data::write_TSDU_Ext_Func(uint32_t peerId, uint32_t func, uint32_t ar iosp->setMFId(MFG_MOT); } - LogMessage(LOG_NET, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); write_TSDU(peerId, iosp.get()); @@ -748,7 +918,7 @@ void TagP25Data::write_TSDU_Grp_Aff_Q(uint32_t peerId, uint32_t dstId) osp->setSrcId(WUID_FNE); osp->setDstId(dstId); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, dstId = %u", osp->toString().c_str(), dstId); + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, dstId = %u", osp->toString().c_str(), dstId); write_TSDU(peerId, osp.get()); } @@ -761,7 +931,7 @@ void TagP25Data::write_TSDU_U_Reg_Cmd(uint32_t peerId, uint32_t dstId) osp->setSrcId(WUID_FNE); osp->setDstId(dstId); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, dstId = %u", osp->toString().c_str(), dstId); + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, dstId = %u", osp->toString().c_str(), dstId); write_TSDU(peerId, osp.get()); } @@ -795,7 +965,7 @@ void TagP25Data::routeRewrite(uint8_t* buffer, uint32_t peerId, uint8_t duid, ui switch (tsbk->getLCO()) { case TSBKO::IOSP_GRP_VCH: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchId(), tsbk->getGrpVchNo(), srcId, rewriteDstId); tsbk->setDstId(rewriteDstId); @@ -904,13 +1074,13 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) case TSBKO::OSP_ADJ_STS_BCAST: { if (m_network->m_disallowAdjStsBcast) { - // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to internal peers is prohibited, dropping", peerId); + // LogWarning(LOG_P25, "PEER %u, passing ADJ_STS_BCAST to internal peers is prohibited, dropping", peerId); return false; } else { lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); if (m_network->m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass(), peerId); } @@ -918,7 +1088,7 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) lookups::AdjPeerMapEntry adjPeerMap = m_network->m_adjSiteMapLookup->find(peerId); if (!adjPeerMap.isEmpty()) { if (!adjPeerMap.active()) { - // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to other peers is disabled, dropping", peerId); + // LogWarning(LOG_P25, "PEER %u, passing ADJ_STS_BCAST to other peers is disabled, dropping", peerId); return false; } else { // if the peer is mapped, we can repeat the ADJ_STS_BCAST to other peers @@ -942,7 +1112,7 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) } } else { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogWarning(LOG_NET, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", peerId, peerIdentity.c_str()); + LogWarning(LOG_P25, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", peerId, peerIdentity.c_str()); } } @@ -966,7 +1136,7 @@ bool TagP25Data::processTSDUFrom(uint8_t* buffer, uint32_t peerId, uint8_t duid) } else { // bryanb: should these be logged? //std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - //LogWarning(LOG_NET, "PEER %u (%s), passing TDULC that failed to decode? tdulc == nullptr", peerId, peerIdentity.c_str()); + //LogWarning(LOG_P25, "PEER %u (%s), passing TDULC that failed to decode? tdulc == nullptr", peerId, peerIdentity.c_str()); } } @@ -1011,14 +1181,14 @@ bool TagP25Data::processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid) lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[lookupPeerId]; if (aff == nullptr) { std::string peerIdentity = m_network->resolvePeerIdentity(lookupPeerId); - //LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", lookupPeerId, peerIdentity.c_str()); + //LogError(LOG_P25, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", lookupPeerId, peerIdentity.c_str()); return false; // this will cause no TSDU to pass for this peer now...I'm not sure this is good behavior } else { if (!aff->hasGroupAff(dstId)) { if (m_debug) { std::string peerIdentity = m_network->resolvePeerIdentity(lookupPeerId); - LogDebug(LOG_NET, "PEER %u (%s) can fuck off there's no affiliations.", lookupPeerId, peerIdentity.c_str()); // just so Faulty can see more "salty" log messages + LogDebug(LOG_P25, "PEER %u (%s) can fuck off there's no affiliations.", lookupPeerId, peerIdentity.c_str()); // just so Faulty can see more "salty" log messages } return false; } @@ -1032,16 +1202,16 @@ bool TagP25Data::processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid) } } else { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogWarning(LOG_NET, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", peerId, peerIdentity.c_str()); + LogWarning(LOG_P25, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", peerId, peerIdentity.c_str()); } } return true; } -/* Helper to process TSDUs being passed to an external peer. */ +/* Helper to process TSDUs being passed to a neighbor FNE peer. */ -bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint32_t dstPeerId, uint8_t duid) +bool TagP25Data::processTSDUToNeighbor(uint8_t* buffer, uint32_t srcPeerId, uint32_t dstPeerId, uint8_t duid) { // are we receiving a TSDU? if (duid == DUID::TSDU) { @@ -1057,13 +1227,13 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint case TSBKO::OSP_ADJ_STS_BCAST: { if (m_network->m_disallowExtAdjStsBcast) { - // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to external peers is prohibited, dropping", dstPeerId); + // LogWarning(LOG_NET, "PEER %u, passing ADJ_STS_BCAST to neighbor peers is prohibited, dropping", dstPeerId); return false; } else { lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); if (m_network->m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X, peerId = %u", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass(), srcPeerId); } } @@ -1074,7 +1244,7 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint } } else { std::string peerIdentity = m_network->resolvePeerIdentity(srcPeerId); - LogWarning(LOG_NET, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", srcPeerId, peerIdentity.c_str()); + LogWarning(LOG_P25, "PEER %u (%s), passing TSBK that failed to decode? tsbk == nullptr", srcPeerId, peerIdentity.c_str()); } } @@ -1083,8 +1253,12 @@ bool TagP25Data::processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint /* Helper to determine if the peer is permitted for traffic. */ -bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, uint32_t streamId, bool external) +bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, uint32_t streamId, bool fromUpstream) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + if (control.getLCO() == LCO::PRIVATE) { if (m_network->m_disallowU2U) return false; @@ -1107,10 +1281,10 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, connection = m_network->m_peers[peerId]; } - // is this peer a Peer-Link peer? + // is this peer a replica peer? if (connection != nullptr) { - if (connection->isPeerLink()) { - return true; // Peer Link peers are *always* allowed to receive traffic and no other rules may filter + if (connection->isReplica()) { + return true; // replica peers are *always* allowed to receive traffic and no other rules may filter // these peers } } @@ -1196,8 +1370,8 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, if (m_network->m_allowConvSiteAffOverride) { if (connection != nullptr) { if (connection->isConventionalPeer()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for conventional peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for conventional peers } } } @@ -1205,14 +1379,14 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, // is this peer a SysView peer? if (connection != nullptr) { if (connection->isSysView()) { - external = true; // we'll just set the external flag to disable the affiliation check - // for SysView peers + fromUpstream = true; // we'll just set the fromUpstream flag to disable the affiliation check + // for SysView peers } } // is this a TG that requires affiliations to repeat? - // NOTE: external peers *always* repeat traffic regardless of affiliation - if (tg.config().affiliated() && !external) { + // NOTE: neighbor FNE peers *always* repeat traffic regardless of affiliation + if (tg.config().affiliated() && !fromUpstream) { uint32_t lookupPeerId = peerId; if (connection != nullptr) { if (connection->ccPeerId() > 0U) @@ -1240,12 +1414,16 @@ bool TagP25Data::isPeerPermitted(uint32_t peerId, lc::LC& control, DUID::E duid, bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const p25::lc::TSBK* tsbk, uint32_t streamId) { + // promiscuous hub mode performs no ACL checking and will pass all traffic + if (g_promiscuousHub) + return true; + bool skipRidCheck = false; if ((control.getMFId() == MFG_MOT && control.getSrcId() == 0U) || control.getSrcId() > WUID_FNE) { skipRidCheck = true; } - //LogDebug(LOG_NET, "P25, duid = $%02X, mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u", duid, control.getMFId(), control.getLCO(), control.getSrcId(), control.getDstId()); + //LogDebugEx(LOG_P25, "TagP25Data::validate()", "duid = $%02X, mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u", duid, control.getMFId(), control.getLCO(), control.getSrcId(), control.getDstId()); // is the source ID a blacklisted ID? bool rejectUnknownBadCall = false; @@ -1267,7 +1445,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + LogError(LOG_P25, INFLUXDB_ERRSTR_DISABLED_SRC_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1291,7 +1469,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const if (m_network->m_filterTerminators) { if ((duid == DUID::TDU || duid == DUID::TDULC) && control.getDstId() != 0U) { // is this a private call? - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == control.getDstId()) { if (x.second.activeCall) return true; @@ -1313,7 +1491,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const return true; } - //LogDebugEx(LOG_NET, "TagP25Data::validate()", "TDU for invalid destination, dropped, dstId = %u", control.getDstId()); + //LogDebugEx(LOG_P25, "TagP25Data::validate()", "TDU for invalid destination, dropped, dstId = %u", control.getDstId()); return false; } @@ -1328,7 +1506,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const bool privateCallInProgress = false; if ((control.getLCO() != LCO::PRIVATE) && !control.getGroup()) { // is this a private call? if so only repeat to the peer that registered the unit - auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair x) { + auto it = std::find_if(m_statusPVCall.begin(), m_statusPVCall.end(), [&](StatusMapPair& x) { if (x.second.dstId == control.getDstId()) { if (x.second.activeCall) return true; @@ -1360,7 +1538,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + LogError(LOG_P25, INFLUXDB_ERRSTR_DISABLED_DST_RID ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1385,7 +1563,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + LogWarning(LOG_P25, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1429,7 +1607,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const case ExtendedFunctions::UNINHIBIT: { if (!pid.peerDefault() && !pid.canIssueInhibit()) { - LogWarning(LOG_NET, "P25, PEER %u attempted inhibit/unhibit, not authorized", peerId); + LogWarning(LOG_P25, "PEER %u attempted inhibit/unhibit, not authorized", peerId); return false; } } @@ -1482,7 +1660,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + LogError(LOG_P25, INFLUXDB_ERRSTR_INV_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1516,7 +1694,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogWarning(LOG_NET, "P25, " INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); + LogWarning(LOG_P25, INFLUXDB_ERRSTR_ILLEGAL_RID_ACCESS ", srcId = %u, dstId = %u", control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1539,7 +1717,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + LogError(LOG_P25, INFLUXDB_ERRSTR_DISABLED_TALKGROUP ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1567,7 +1745,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const } if (m_network->m_logDenials) - LogError(LOG_NET, "P25, " INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); + LogError(LOG_P25, INFLUXDB_ERRSTR_RID_NOT_PERMITTED ", peer = %u, srcId = %u, dstId = %u", peerId, control.getSrcId(), control.getDstId()); // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, streamId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); @@ -1595,7 +1773,7 @@ bool TagP25Data::write_TSDU_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI lookups::AffiliationLookup* aff = m_network->m_peerAffiliations[peerId]; if (aff == nullptr) { std::string peerIdentity = m_network->resolvePeerIdentity(peerId); - LogError(LOG_NET, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); + LogError(LOG_MASTER, "PEER %u (%s) has an invalid affiliations lookup? This shouldn't happen BUGBUG.", peerId, peerIdentity.c_str()); return false; // this will cause no traffic to pass for this peer now...I'm not sure this is good behavior } else { @@ -1615,7 +1793,7 @@ bool TagP25Data::write_TSDU_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI iosp->setPriority(priority); if (m_network->m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u, peerId = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u, peerId = %u", iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchId(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId(), peerId); } @@ -1632,7 +1810,7 @@ bool TagP25Data::write_TSDU_Grant(uint32_t peerId, uint32_t srcId, uint32_t dstI iosp->setPriority(priority); if (m_network->m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u, peerId = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u, peerId = %u", iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchId(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId(), peerId); } @@ -1655,7 +1833,7 @@ void TagP25Data::write_TSDU_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), osp->getSrcId(), osp->getDstId()); } @@ -1676,7 +1854,7 @@ void TagP25Data::write_TSDU_Queue(uint32_t peerId, uint32_t srcId, uint32_t dstI osp->setGroup(grp); if (m_network->m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_P25, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), osp->getSrcId(), osp->getDstId()); } @@ -1705,7 +1883,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) P25Utils::setStatusBitsStartIdle(data); if (m_debug) { - LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", + LogDebug(LOG_P25, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", tsbk->getLCO(), tsbk->getMFId(), tsbk->getLastBlock(), tsbk->getAIV(), tsbk->getEX(), tsbk->getSrcId(), tsbk->getDstId(), tsbk->getSysId(), tsbk->getNetId()); @@ -1727,35 +1905,40 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) uint32_t streamId = m_network->createStreamId(); if (peerId > 0U) { m_network->writePeer(peerId, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, - RTP_END_OF_CALL_SEQ, streamId, false); + RTP_END_OF_CALL_SEQ, streamId); } else { // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { uint32_t i = 0U; + udp::BufferQueue queue = udp::BufferQueue(); + + m_network->m_peers.shared_lock(); for (auto peer : m_network->m_peers) { - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); + // every MAX_QUEUED_PEER_MSGS peers flush the queue + if (i % MAX_QUEUED_PEER_MSGS == 0U) { + m_network->m_frameQueue->flushQueue(&queue); } - m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, - RTP_END_OF_CALL_SEQ, streamId, true); + m_network->writePeerQueue(&queue, peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, + RTP_END_OF_CALL_SEQ, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u", + LogDebugEx(LOG_P25, "TagP25Data::write_TSDU()", "P25, peer = %u, len = %u, streamId = %u", peer.first, messageLength, streamId); } + i++; } - m_network->m_frameQueue->flushQueue(); + m_network->m_frameQueue->flushQueue(&queue); + m_network->m_peers.shared_unlock(); } - // repeat traffic to external peers + // repeat traffic to neighbor FNE peers if (m_network->m_host->m_peerNetworks.size() > 0U) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, peer = %u, len = %u, streamId = %u", + LogDebugEx(LOG_P25, "TagP25Data::write_TSDU()", "peer = %u, len = %u, streamId = %u", dstPeerId, messageLength, streamId); } } diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 12de5c16b..53fa700ea 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -64,10 +64,10 @@ namespace network * @param ssrc RTP Synchronization Source ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint32_t ssrc, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); /** * @brief Process a grant request frame from the network. * @param srcId Source Radio ID. @@ -80,6 +80,11 @@ namespace network */ bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + /** + * @brief Helper to trigger a call takeover from a In-Call control event. + */ + void triggerCallTakeover(uint32_t dstId); + /** * @brief Helper to playback a parrot frame to the network. */ @@ -90,6 +95,38 @@ namespace network */ bool hasParrotFrames() const { return m_parrotFramesReady && !m_parrotFrames.empty(); } + /** + * @brief Helper to determine if the parrot is playing back frames. + * @returns True, if parrot playback was started, otherwise false. + */ + bool isParrotPlayback() const { return m_parrotPlayback; } + /** + * @brief Helper to clear the parrot playback flag. + */ + void clearParrotPlayback() + { + m_parrotPlayback = false; + m_lastParrotPeerId = 0U; + m_lastParrotSrcId = 0U; + m_lastParrotDstId = 0U; + } + + /** + * @brief Returns the last processed peer ID for a parrot frame. + * @return uint32_t Peer ID. + */ + uint32_t lastParrotPeerId() const { return m_lastParrotPeerId; } + /** + * @brief Returns the last processed source ID for a parrot frame. + * @return uint32_t Source ID. + */ + uint32_t lastParrotSrcId() const { return m_lastParrotSrcId; } + /** + * @brief Returns the last processed destination ID for a parrot frame. + * @return uint32_t Destination ID. + */ + uint32_t lastParrotDstId() const { return m_lastParrotDstId; } + /** * @brief Helper to write a call alert packet. * @param peerId Peer ID. @@ -168,6 +205,10 @@ namespace network concurrent::deque m_parrotFrames; bool m_parrotFramesReady; bool m_parrotFirstFrame; + bool m_parrotPlayback; + uint32_t m_lastParrotPeerId; + uint32_t m_lastParrotSrcId; + uint32_t m_lastParrotDstId; /** * @brief Represents the receive status of a call. @@ -192,6 +233,10 @@ namespace network * @brief Peer ID. */ uint32_t peerId; + /** + * @brief Synchronization Source. + */ + uint32_t ssrc; /** * @brief Destination Peer ID. */ @@ -200,6 +245,10 @@ namespace network * @brief Flag indicating this call is active with traffic currently in progress. */ bool activeCall; + /** + * @brief Flag indicating the metadata for the call on the next frame will be overwritten. + */ + bool callTakeover; /** * @brief Helper to reset call status. @@ -210,7 +259,9 @@ namespace network dstId = 0U; streamId = 0U; peerId = 0U; + ssrc = 0U; activeCall = false; + callTakeover = false; } }; typedef std::pair StatusMapPair; @@ -257,14 +308,14 @@ namespace network */ bool processTSDUTo(uint8_t* buffer, uint32_t peerId, uint8_t duid); /** - * @brief Helper to process TSDUs being passed to an external peer. + * @brief Helper to process TSDUs being passed to a neighbor FNE peer. * @param buffer Frame buffer. * @param srcPeerId Source Peer ID. * @param dstPeerID Destination Peer ID. * @param duid DUID. * @returns bool True, if allowed to pass, otherwise false. */ - bool processTSDUToExternal(uint8_t* buffer, uint32_t srcPeerId, uint32_t dstPeerId, uint8_t duid); + bool processTSDUToNeighbor(uint8_t* buffer, uint32_t srcPeerId, uint32_t dstPeerId, uint8_t duid); /** * @brief Helper to determine if the peer is permitted for traffic. @@ -272,10 +323,10 @@ namespace network * @param control Instance of p25::lc::LC. * @param duid DUID. * @param streamId Stream ID. - * @param external Flag indicating this traffic came from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if permitted, otherwise false. */ - bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, P25DEF::DUID::E duid, uint32_t streamId, bool external = false); + bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, P25DEF::DUID::E duid, uint32_t streamId, bool fromUpstream = false); /** * @brief Helper to validate the P25 call stream. * @param peerId Peer ID. diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index 89aaa829f..7079b7a43 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -58,7 +58,7 @@ DMRPacketData::~DMRPacketData() = default; /* Process a data frame from the network. */ -bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -91,128 +91,156 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee uint8_t frame[DMR_FRAME_LENGTH_BYTES]; dmrData.getData(frame); - // is the stream valid? - if (m_tag->validate(peerId, dmrData, nullptr, streamId)) { - // is this peer ignored? - if (!m_tag->isPeerPermitted(peerId, dmrData, streamId)) { - return false; + if (dataSync) { + auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second->peerId == peerId; }); + if (it == m_status.end()) { + // this is a new call stream + m_status.lock(); + RxStatus* status = new RxStatus(); + status->callStartTime = pktTime; + status->srcId = srcId; + status->dstId = dstId; + status->slotNo = slotNo; + status->streamId = streamId; + status->peerId = peerId; + m_status.unlock(); + + m_status.insert(peerId, status); } - auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second->peerId == peerId; }); - if (it != m_status.end()) { - RxStatus* status = m_status[peerId]; - if (streamId != status->streamId) { - LogWarning(LOG_NET, "DMR, Data Call Collision, peer = %u, streamId = %u, rxPeer = %u, rxLlId = %u, rxSlotNo = %u, rxStreamId = %u, external = %u", - peerId, streamId, status->peerId, status->srcId, status->slotNo, status->streamId, external); + RxStatus* status = m_status[peerId]; + if ((status->streamId != 0U && streamId != status->streamId) || status->callBusy) { + if (m_network->m_callCollisionTimeout > 0U) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status->lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + status->streamId = streamId; + status->callBusy = false; + m_status.unlock(); + } + else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call Collision, peer = %u, slot = %u, streamId = %u, rxPeer = %u, rxStreamId = %u, fromUpstream = %u", + peerId, slotNo, streamId, status->peerId, status->streamId, fromUpstream); + return false; + } + } else { + m_status.lock(false); + status->streamId = streamId; + m_status.unlock(); + } + } - uint64_t duration = hrc::diff(pktTime, status->callStartTime); + if (status->callBusy) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call Lockout, cannot process data packets while data call in progress, peer = %u, slot = %u, streamId = %u, fromUpstream = %u", + peerId, slotNo, streamId, fromUpstream); + return false; + } - if ((duration / 1000) > DATA_CALL_COLL_TIMEOUT) { - LogWarning(LOG_NET, "DMR, force clearing stuck data call, timeout, peer = %u, streamId = %u, rxPeer = %u, rxLlId = %u, rxStreamId = %u, external = %u", - peerId, streamId, status->peerId, status->srcId, status->slotNo, status->streamId, external); + m_status.lock(false); + status->lastPacket = hrc::now(); + m_status.unlock(); - delete status; - m_status.erase(peerId); - } + if (dataType == DataType::DATA_HEADER) { + bool ret = status->header.decode(frame); + if (!ret) { + LogError(LOG_DMR, "DMR Slot %u, DataType::DATA_HEADER, unable to decode the network data header", status->slotNo); + Utils::dump(1U, "DMR, Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); + status->streamId = 0U; return false; } - } else { - if (dataSync && (dataType == DataType::DATA_HEADER)) { - // this is a new call stream - RxStatus* status = new RxStatus(); - status->callStartTime = pktTime; - status->srcId = srcId; - status->dstId = dstId; - status->slotNo = slotNo; - status->streamId = streamId; - status->peerId = peerId; - - bool ret = status->header.decode(frame); - if (!ret) { - LogError(LOG_NET, "DMR Slot %u, DataType::DATA_HEADER, unable to decode the network data header", status->slotNo); - Utils::dump(1U, "DMR, Unfixable PDU Data", frame, DMR_FRAME_LENGTH_BYTES); - delete status; - m_status.erase(peerId); - return false; - } + status->frames = status->header.getBlocksToFollow(); + status->dataBlockCnt = 0U; + status->hasRxHeader = true; - status->frames = status->header.getBlocksToFollow(); - status->dataBlockCnt = 0U; + bool gi = status->header.getGI(); + uint32_t srcId = status->header.getSrcId(); + uint32_t dstId = status->header.getDstId(); - bool gi = status->header.getGI(); - uint32_t srcId = status->header.getSrcId(); - uint32_t dstId = status->header.getDstId(); + LogInfoEx(LOG_DMR, DMR_DT_DATA_HEADER ", peerId = %u, slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", + peerId, status->slotNo, status->header.getDPF(), status->header.getA(), status->header.getSAP(), status->header.getFullMesage(), status->header.getBlocksToFollow(), status->header.getPadLength(), status->header.getPacketLength(dataType), + status->header.getFSN(), dstId, srcId, gi); - LogMessage(LOG_NET, DMR_DT_DATA_HEADER ", peerId = %u, slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", - peerId, status->slotNo, status->header.getDPF(), status->header.getA(), status->header.getSAP(), status->header.getFullMesage(), status->header.getBlocksToFollow(), status->header.getPadLength(), status->header.getPacketLength(), - status->header.getFSN(), dstId, srcId, gi); + // make sure we don't get a PDU with more blocks then we support + if (status->header.getBlocksToFollow() >= MAX_PDU_COUNT) { + LogError(LOG_DMR, DMR_DT_DATA_HEADER ", too many PDU blocks to process, %u > %u", status->header.getBlocksToFollow(), MAX_PDU_COUNT); + status->streamId = 0U; + return false; + } - // make sure we don't get a PDU with more blocks then we support - if (status->header.getBlocksToFollow() >= MAX_PDU_COUNT) { - LogError(LOG_NET, P25_PDU_STR ", too many PDU blocks to process, %u > %u", status->header.getBlocksToFollow(), MAX_PDU_COUNT); - return false; - } + m_status[peerId] = status; - m_status[peerId] = status; - - LogMessage(LOG_NET, "DMR, Data Call Start, peer = %u, slot = %u, srcId = %u, dstId = %u, group = %u, streamId = %u, external = %u", peerId, status->slotNo, status->srcId, status->dstId, gi, streamId, external); - dispatchToFNE(peerId, dmrData, data, len, seqNo, pktSeq, streamId); + dispatchToFNE(peerId, dmrData, data, len, seqNo, pktSeq, streamId); + // a PDU header only with no blocks to follow is usually a response header + if (status->header.getBlocksToFollow() == 0U) { + status->streamId = 0U; return true; - } else { - return false; } - } - - RxStatus* status = m_status[peerId]; - - // a PDU header only with no blocks to follow is usually a response header - if (status->header.getBlocksToFollow() == 0U) { - LogMessage(LOG_NET, "DMR, Data Call End, peer = %u, slot = %u, srcId = %u, dstId = %u, streamId = %u, external = %u", - peerId, status->slotNo, status->srcId, status->dstId, streamId, external); - delete status; - m_status.erase(peerId); + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call Start, peer = %u, slot = %u, srcId = %u, dstId = %u, group = %u, streamId = %u, fromUpstream = %u", peerId, status->slotNo, status->srcId, status->dstId, gi, streamId, fromUpstream); return true; } - data::DataBlock dataBlock; - dataBlock.setDataType(dataType); + if ((dataType == DataType::RATE_34_DATA) || + (dataType == DataType::RATE_12_DATA) || + (dataType == DataType::RATE_1_DATA)) { + dispatchToFNE(peerId, dmrData, data, len, seqNo, pktSeq, streamId); - bool ret = dataBlock.decode(frame, status->header); - if (ret) { - uint32_t blockLen = dataBlock.getData(status->pduUserData + status->pduDataOffset); - status->pduDataOffset += blockLen; + data::DataBlock dataBlock; + dataBlock.setDataType(dataType); - status->frames--; - if (status->frames == 0U) - dataBlock.setLastBlock(true); + bool ret = dataBlock.decode(frame, status->header); + if (ret) { + uint32_t blockLen = dataBlock.getData(status->pduUserData + status->pduDataOffset); + status->pduDataOffset += blockLen; - if (dataType == DataType::RATE_34_DATA) { - LogMessage(LOG_NET, DMR_DT_RATE_34_DATA ", ISP, block %u, peer = %u, dataType = $%02X, dpf = $%02X", status->dataBlockCnt, peerId, dataBlock.getDataType(), dataBlock.getFormat()); - } else if (dataType == DataType::RATE_12_DATA) { - LogMessage(LOG_NET, DMR_DT_RATE_12_DATA ", ISP, block %u, peer = %u, dataType = $%02X, dpf = $%02X", status->dataBlockCnt, peerId, dataBlock.getDataType(), dataBlock.getFormat()); - } - else { - LogMessage(LOG_NET, DMR_DT_RATE_1_DATA ", ISP, block %u, peer = %u, dataType = $%02X, dpf = $%02X", status->dataBlockCnt, peerId, dataBlock.getDataType(), dataBlock.getFormat()); + status->frames--; + if (status->frames == 0U) + dataBlock.setLastBlock(true); + status->dataBlockCnt++; } - - dispatchToFNE(peerId, dmrData, data, len, seqNo, pktSeq, streamId); - status->dataBlockCnt++; } // dispatch the PDU data - if (status->dataBlockCnt > 0U && status->frames == 0U) { + if (status->hasRxHeader && status->dataBlockCnt > 0U && status->frames == 0U) { + // is the source ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(status->header.getSrcId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(status->header.getSrcId())) + .tag("dstId", std::to_string(status->header.getDstId())) + .field("message", INFLUXDB_ERRSTR_DISABLED_SRC_RID) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } + + m_status.erase(peerId); + delete status; + status = nullptr; + return false; + } + } + + status->callBusy = true; + dispatch(peerId, dmrData, data, len); uint64_t duration = hrc::diff(pktTime, status->callStartTime); bool gi = status->header.getGI(); uint32_t srcId = status->header.getSrcId(); uint32_t dstId = status->header.getDstId(); - LogMessage(LOG_NET, "P25, Data Call End, peer = %u, slot = %u, srcId = %u, dstId = %u, group = %u, blocks = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, gi, status->header.getBlocksToFollow(), duration / 1000, streamId, external); + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call End, peer = %u, slot = %u, srcId = %u, dstId = %u, group = %u, blocks = %u, duration = %u, streamId = %u, fromUpstream = %u", + peerId, srcId, dstId, gi, status->header.getBlocksToFollow(), duration / 1000, streamId, fromUpstream); // report call event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -229,14 +257,49 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee .requestAsync(m_network->m_influxServer); } - delete status; m_status.erase(peerId); + delete status; + status = nullptr; + } else { + status->callBusy = false; } } return true; } +/* Helper to cleanup any call's left in a dangling state without any further updates. */ + +void DMRPacketData::cleanupStale() +{ + // check to see if any peers have been quiet (no ping) longer than allowed + std::vector peersToRemove = std::vector(); + m_status.lock(false); + for (auto peerStatus : m_status) { + uint32_t id = peerStatus.first; + RxStatus* status = peerStatus.second; + if (status != nullptr) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status->lastPacket); + if ((lastPktDuration / 1000) > 10U) { + LogWarning(LOG_DMR, "DMR, Data Call Timeout, lasted more then %us with no further updates", 10U); + status->callBusy = true; // force flag the call busy + peersToRemove.push_back(id); + } + } + } + m_status.unlock(); + + // remove any peers + for (uint32_t peerId : peersToRemove) { + RxStatus* status = m_status[peerId]; + if (status != nullptr) { + m_status.erase(peerId); + delete status; + status = nullptr; + } + } +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -248,9 +311,22 @@ void DMRPacketData::dispatch(uint32_t peerId, dmr::data::NetData& dmrData, const RxStatus *status = m_status[peerId]; if (status->header.getBlocksToFollow() > 0U && status->frames == 0U) { - bool crcRet = edac::CRC::checkCRC32(status->pduUserData, status->pduDataOffset); + // ooookay -- lets do the insane, and ridiculously stupid, ETSI Big-Endian reversed byte ordering bullshit for the CRC-32 + uint8_t crcBytes[MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U]; + ::memset(crcBytes, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U); + for (uint8_t i = 0U; i < status->pduDataOffset - 4U; i += 2U) { + crcBytes[i + 1U] = status->pduUserData[i]; + crcBytes[i] = status->pduUserData[i + 1U]; + } + + crcBytes[status->pduDataOffset - 1U] = status->pduUserData[status->pduDataOffset - 4U]; + crcBytes[status->pduDataOffset - 2U] = status->pduUserData[status->pduDataOffset - 3U]; + crcBytes[status->pduDataOffset - 3U] = status->pduUserData[status->pduDataOffset - 2U]; + crcBytes[status->pduDataOffset - 4U] = status->pduUserData[status->pduDataOffset - 1U]; + + bool crcRet = edac::CRC::checkInvertedCRC32(crcBytes, status->pduDataOffset); if (!crcRet) { - LogWarning(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduDataOffset); + LogWarning(LOG_DMR, "DMR Data, failed CRC-32 check, blocks %u, len %u", status->header.getBlocksToFollow(), status->pduDataOffset); } if (m_network->m_dumpPacketData) { @@ -268,9 +344,12 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, uint32_t srcId = status->header.getSrcId(); uint32_t dstId = status->header.getDstId(); + /* + ** MASTER TRAFFIC + */ + // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { - uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { // is this peer ignored? @@ -278,56 +357,42 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, continue; } - // every 5 peers flush the queue - if (i % 5U == 0U) { - m_network->m_frameQueue->flushQueue(); - } - - m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId, true); + m_network->writePeer(peer.first, peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", + LogDebugEx(LOG_DMR, "DMRPacketData::dispatchToFNE()", "Master, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", peerId, peer.first, seqNo, srcId, dstId, status->slotNo, len, pktSeq, streamId); } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; - i++; } } - m_network->m_frameQueue->flushQueue(); } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to neighbor FNE peers if (m_network->m_host->m_peerNetworks.size() > 0U) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { - // is this peer ignored? - if (!m_tag->isPeerPermitted(dstPeerId, dmrData, streamId, true)) { - continue; - } - - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { + // skip peer if it isn't enabled + if (!peer.second->isEnabled()) { continue; } - // skip peer if it isn't enabled - if (!peer.second->isEnabled()) { + // is this peer ignored? + if (!m_tag->isPeerPermitted(dstPeerId, dmrData, streamId, true)) { continue; } peer.second->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, data, len, pktSeq, streamId); if (m_network->m_debug) { - LogDebug(LOG_NET, "DMR, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", + LogDebugEx(LOG_DMR, "DMRPacketData::dispatchToFNE()", "Peers, srcPeer = %u, dstPeer = %u, seqNo = %u, srcId = %u, dstId = %u, slotNo = %u, len = %u, pktSeq = %u, stream = %u", peerId, dstPeerId, seqNo, srcId, dstId, status->slotNo, len, pktSeq, streamId); } - - if (!m_network->m_callInProgress) - m_network->m_callInProgress = true; } } } diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.h b/src/fne/network/callhandler/packetdata/DMRPacketData.h index d1481029d..a484fe7a1 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.h +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -63,10 +63,15 @@ namespace network * @param peerId Peer ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); + + /** + * @brief Helper to cleanup any call's left in a dangling state without any further updates. + */ + void cleanupStale(); private: FNENetwork* m_network; @@ -78,6 +83,7 @@ namespace network class RxStatus { public: system_clock::hrc::hrc_t callStartTime; + system_clock::hrc::hrc_t lastPacket; uint32_t srcId; uint32_t dstId; uint8_t slotNo; @@ -85,9 +91,12 @@ namespace network uint32_t peerId; dmr::data::DataHeader header; + bool hasRxHeader; uint8_t dataBlockCnt; uint8_t frames; + bool callBusy; + uint8_t* pduUserData; uint32_t pduDataOffset; @@ -101,7 +110,10 @@ namespace network streamId(0U), peerId(0U), header(), + hasRxHeader(false), dataBlockCnt(0U), + frames(0U), + callBusy(false), pduUserData(nullptr), pduDataOffset(0U) { diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index 53fa07728..c8663c446 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -17,6 +17,7 @@ #include "common/Thread.h" #include "common/Utils.h" #include "network/FNENetwork.h" +#include "network/P25OTARService.h" #include "network/callhandler/packetdata/P25PacketData.h" #include "HostFNE.h" @@ -41,7 +42,7 @@ using namespace p25::sndcp; // --------------------------------------------------------------------------- const uint8_t DATA_CALL_COLL_TIMEOUT = 60U; -const uint8_t MAX_PKT_RETRY_CNT = 5U; +const uint8_t MAX_PKT_RETRY_CNT = 2U; const uint32_t INTERPACKET_DELAY = 100U; // milliseconds const uint32_t ARP_RETRY_MS = 5000U; // milliseconds @@ -56,6 +57,7 @@ const uint32_t SUBSCRIBER_READY_RETRY_MS = 1000U; // milliseconds P25PacketData::P25PacketData(FNENetwork* network, TagP25Data* tag, bool debug) : m_network(network), m_tag(tag), + m_assembler(nullptr), m_queuedFrames(), m_status(), m_arpTable(), @@ -65,15 +67,38 @@ P25PacketData::P25PacketData(FNENetwork* network, TagP25Data* tag, bool debug) : { assert(network != nullptr); assert(tag != nullptr); + + data::Assembler::setVerbose(network->m_verbose); + data::Assembler::setDumpPDUData(network->m_dumpPacketData); + + m_assembler = new data::Assembler(); + m_assembler->setBlockWriter([](const void* userContext, const uint8_t currentBlock, const uint8_t *data, uint32_t len, bool lastBlock) { + const UserContext* context = static_cast(userContext); + if (context == nullptr) { + return; + } + + P25PacketData* packetData = static_cast(context->obj); + if (packetData == nullptr) { + return; + } + + packetData->writeNetwork(context->peerId, context->srcPeerId, context->peerNet, *(context->header), + currentBlock, data, len, context->pktSeq, context->streamId); + }); } /* Finalizes a instance of the P25PacketData class. */ -P25PacketData::~P25PacketData() = default; +P25PacketData::~P25PacketData() +{ + if (m_assembler != nullptr) + delete m_assembler; +} /* Process a data frame from the network. */ -bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external) +bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromUpstream) { hrc::hrc_t pktTime = hrc::now(); @@ -89,32 +114,10 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee ::memcpy(buffer, data + 24U, P25_PDU_FEC_LENGTH_BYTES); auto it = std::find_if(m_status.begin(), m_status.end(), [&](StatusMapPair x) { return x.second->peerId == peerId; }); - if (it != m_status.end()) { - RxStatus* status = m_status[peerId]; - if (streamId != status->streamId) { - LogWarning(LOG_NET, "P25, Data Call Collision, peer = %u, streamId = %u, rxPeer = %u, rxLlId = %u, rxStreamId = %u, external = %u", - peerId, streamId, status->peerId, status->llId, status->streamId, external); - - LogWarning(LOG_NET, "P25, clearing previous data call, timeout, peer = %u, streamId = %u, rxPeer = %u, rxLlId = %u, rxStreamId = %u, external = %u", - peerId, streamId, status->peerId, status->llId, status->streamId, external); - - delete status; - m_status.erase(peerId); - - // create a new status entry - m_status.lock(true); - RxStatus *status = new RxStatus(); - status->callStartTime = pktTime; - status->streamId = streamId; - status->peerId = peerId; - m_status.unlock(); - - m_status.insert(peerId, status); - } - } else { + if (it == m_status.end()) { // create a new status entry m_status.lock(true); - RxStatus *status = new RxStatus(); + RxStatus* status = new RxStatus(); status->callStartTime = pktTime; status->streamId = streamId; status->peerId = peerId; @@ -124,219 +127,152 @@ bool P25PacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee } RxStatus* status = m_status[peerId]; + if ((status->streamId != 0U && streamId != status->streamId) || status->callBusy) { + LogDebugEx(LOG_NET, "P25PacketData::processFrame()", "streamId = %u, status->streamId = %u, status->callBusy = %u", streamId, status->streamId, status->callBusy); + if (m_network->m_callCollisionTimeout > 0U) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status->lastPacket); + if ((lastPktDuration / 1000) > m_network->m_callCollisionTimeout) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Data Call Collision, lasted more then %us with no further updates, resetting call source", m_network->m_callCollisionTimeout); + + m_status.lock(false); + status->streamId = streamId; + status->callBusy = false; + m_status.unlock(); + } + else { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Data Call Collision, peer = %u, streamId = %u, rxPeer = %u, rxStreamId = %u, fromUpstream = %u", + peerId, streamId, status->peerId, status->streamId, fromUpstream); + return false; + } + } else { + m_status.lock(false); + status->streamId = streamId; + m_status.unlock(); + } + } + + if (status->callBusy) { + LogWarning((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Data Call Lockout, cannot process data packets while data call in progress, peer = %u, streamId = %u, fromUpstream = %u", + peerId, streamId, fromUpstream); + return false; + } + + m_status.lock(false); + status->lastPacket = hrc::now(); + m_status.unlock(); // make sure we don't get a PDU with more blocks then we support if (currentBlock >= P25_MAX_PDU_BLOCKS) { - LogError(LOG_NET, P25_PDU_STR ", too many PDU blocks to process, %u > %u", currentBlock, P25_MAX_PDU_BLOCKS); - - delete status; - m_status.erase(peerId); + LogError(LOG_P25, P25_PDU_STR ", too many PDU blocks to process, %u > %u", currentBlock, P25_MAX_PDU_BLOCKS); return false; } // block 0 is always the PDU header block if (currentBlock == 0U) { - bool ret = status->header.decode(buffer); + bool ret = status->assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); if (!ret) { - LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - - delete status; - m_status.erase(peerId); - return true; + status->streamId = 0U; + return false; } - LogMessage(LOG_NET, P25_PDU_STR ", peerId = %u, ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", - peerId, status->header.getAckNeeded(), status->header.getOutbound(), status->header.getFormat(), status->header.getSAP(), status->header.getFullMessage(), - status->header.getBlocksToFollow(), status->header.getPadLength(), status->header.getPacketLength(), status->header.getSynchronize(), status->header.getNs(), status->header.getFSN(), - status->header.getHeaderOffset(), status->header.getLLId()); + LogInfoEx(LOG_P25, P25_PDU_STR ", peerId = %u, ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", + peerId, status->assembler.dataHeader.getAckNeeded(), status->assembler.dataHeader.getOutbound(), status->assembler.dataHeader.getFormat(), status->assembler.dataHeader.getSAP(), status->assembler.dataHeader.getFullMessage(), + status->assembler.dataHeader.getBlocksToFollow(), status->assembler.dataHeader.getPadLength(), status->assembler.dataHeader.getPacketLength(), status->assembler.dataHeader.getSynchronize(), status->assembler.dataHeader.getNs(), + status->assembler.dataHeader.getFSN(), status->assembler.dataHeader.getHeaderOffset(), status->assembler.dataHeader.getLLId()); // make sure we don't get a PDU with more blocks then we support - if (status->header.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { - LogError(LOG_NET, P25_PDU_STR ", too many PDU blocks to process, %u > %u", status->header.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); - - delete status; - m_status.erase(peerId); + if (status->assembler.dataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { + LogError(LOG_P25, P25_PDU_STR ", too many PDU blocks to process, %u > %u", status->assembler.dataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); + status->streamId = 0U; return false; } status->hasRxHeader = true; - status->llId = status->header.getLLId(); + status->llId = status->assembler.dataHeader.getLLId(); m_readyForNextPkt[status->llId] = true; // is this a response header? - if (status->header.getFormat() == PDUFormatType::RSP) { + if (status->assembler.dataHeader.getFormat() == PDUFormatType::RSP) { dispatch(peerId); - - delete status; - m_status.erase(peerId); + status->streamId = 0U; return true; } - LogMessage(LOG_NET, "P25, Data Call Start, peer = %u, llId = %u, streamId = %u, external = %u", peerId, status->llId, streamId, external); + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Data Call Start, peer = %u, llId = %u, streamId = %u, fromUpstream = %u", peerId, status->llId, streamId, fromUpstream); return true; } - ::memcpy(status->netPDU + status->dataOffset, data + 24U, blockLength); - status->dataOffset += blockLength; - status->netPDUCount++; - status->dataBlockCnt++; - - if (status->hasRxHeader && (status->dataBlockCnt >= status->header.getBlocksToFollow())) { - // is the source ID a blacklisted ID? - lookups::RadioId rid = m_network->m_ridLookup->find(status->header.getLLId()); - if (!rid.radioDefault()) { - if (!rid.radioEnabled()) { - // report error event to InfluxDB - if (m_network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("call_error_event") - .tag("peerId", std::to_string(peerId)) - .tag("streamId", std::to_string(streamId)) - .tag("srcId", std::to_string(status->header.getLLId())) - .tag("dstId", std::to_string(status->header.getLLId())) - .field("message", INFLUXDB_ERRSTR_DISABLED_SRC_RID) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .requestAsync(m_network->m_influxServer); - } - - delete status; - m_status.erase(peerId); - return false; - } - } - - uint32_t blocksToFollow = status->header.getBlocksToFollow(); - uint32_t offset = 0U; - uint32_t dataOffset = 0U; - - uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; - - status->dataBlockCnt = 0U; - - // process all blocks in the data stream - status->pduUserData = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; - ::memset(status->pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); - - // process second header if we're using enhanced addressing - if (status->header.getSAP() == PDUSAP::EXT_ADDR && - status->header.getFormat() == PDUFormatType::UNCONFIRMED) { - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - ::memcpy(buffer, status->netPDU, P25_PDU_FEC_LENGTH_BYTES); - - bool ret = status->header.decodeExtAddr(buffer); - if (!ret) { - LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); - - delete status; - m_status.erase(peerId); - - return false; - } - - LogMessage(LOG_NET, P25_PDU_STR ", ISP, extended address, sap = $%02X, srcLlId = %u", - status->header.getEXSAP(), status->header.getSrcLLId()); - - status->extendedAddress = true; - status->llId = status->header.getSrcLLId(); - - offset += P25_PDU_FEC_LENGTH_BYTES; - blocksToFollow--; - - // if we are using a secondary header place it in the PDU user data buffer - status->header.getExtAddrData(status->pduUserData + dataOffset); - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - status->pduUserDataLength += P25_PDU_HEADER_LENGTH_BYTES; - } - - // decode data blocks - for (uint32_t i = 0U; i < blocksToFollow; i++) { - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - ::memcpy(buffer, status->netPDU + offset, P25_PDU_FEC_LENGTH_BYTES); - - bool ret = status->blockData[i].decode(buffer, status->header); - if (ret) { - // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks - // set this block as the last block for full packet CRC - if ((status->header.getFormat() == PDUFormatType::CONFIRMED) || (status->header.getFormat() == PDUFormatType::UNCONFIRMED)) { - if ((status->dataBlockCnt + 1U) == blocksToFollow) { - status->blockData[i].setLastBlock(true); + status->callBusy = true; + bool ret = status->assembler.disassemble(data + 24U, blockLength); + if (!ret) { + status->callBusy = false; + return false; + } + else { + if (status->hasRxHeader && status->assembler.getComplete()) { + // is the source ID a blacklisted ID? + lookups::RadioId rid = m_network->m_ridLookup->find(status->assembler.dataHeader.getLLId()); + if (!rid.radioDefault()) { + if (!rid.radioEnabled()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(status->assembler.dataHeader.getLLId())) + .tag("dstId", std::to_string(status->assembler.dataHeader.getLLId())) + .field("message", INFLUXDB_ERRSTR_DISABLED_SRC_RID) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); } - } - - // are we processing extended address data from the first block? - if (status->header.getSAP() == PDUSAP::EXT_ADDR && status->header.getFormat() == PDUFormatType::CONFIRMED && - status->blockData[i].getSerialNo() == 0U) { - uint8_t secondHeader[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(secondHeader, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - status->blockData[i].getData(secondHeader); - - status->header.decodeExtAddr(secondHeader); - LogMessage(LOG_NET, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, srcLlId = %u", - status->blockData[i].getSerialNo(), status->blockData[i].getFormat(), status->blockData[i].getLastBlock(), - status->header.getEXSAP(), status->header.getSrcLLId()); - status->extendedAddress = true; + m_status.erase(peerId); + delete status; + status = nullptr; + return false; } - else { - LogMessage(LOG_NET, P25_PDU_STR ", peerId = %u, block %u, fmt = $%02X, lastBlock = %u", - peerId, (status->header.getFormat() == PDUFormatType::CONFIRMED) ? status->blockData[i].getSerialNo() : status->dataBlockCnt, status->blockData[i].getFormat(), - status->blockData[i].getLastBlock()); - } - - status->blockData[i].getData(status->pduUserData + dataOffset); - dataOffset += (status->header.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - status->pduUserDataLength = dataOffset; - - status->dataBlockCnt++; } - else { - if (status->blockData[i].getFormat() == PDUFormatType::CONFIRMED) - LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC), block %u", i); - else - LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); - if (m_network->m_dumpPacketData) { - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - } - } + status->callBusy = true; - offset += P25_PDU_FEC_LENGTH_BYTES; - } + // process all blocks in the data stream + status->pduUserDataLength = status->assembler.getUserDataLength(); + status->pduUserData = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memset(status->pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); - if (status->dataBlockCnt < blocksToFollow) { - LogWarning(LOG_NET, P25_PDU_STR ", incomplete PDU (%d / %d blocks), peerId = %u, llId = %u", status->dataBlockCnt, blocksToFollow, peerId, status->llId); - } + status->assembler.getUserData(status->pduUserData); - // dispatch the PDU data - if (status->dataBlockCnt > 0U && status->hasRxHeader) { + // dispatch the PDU data dispatch(peerId); - } - uint64_t duration = hrc::diff(pktTime, status->callStartTime); - uint32_t srcId = (status->extendedAddress) ? status->header.getSrcLLId() : status->header.getLLId(); - uint32_t dstId = status->header.getLLId(); - LogMessage(LOG_NET, "P25, Data Call End, peer = %u, srcId = %u, dstId = %u, blocks = %u, duration = %u, streamId = %u, external = %u", - peerId, srcId, dstId, status->header.getBlocksToFollow(), duration / 1000, streamId, external); - - // report call event to InfluxDB - if (m_network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("call_event") - .tag("peerId", std::to_string(peerId)) - .tag("mode", "P25") - .tag("streamId", std::to_string(streamId)) - .tag("srcId", std::to_string(srcId)) - .tag("dstId", std::to_string(dstId)) - .field("duration", duration) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .requestAsync(m_network->m_influxServer); - } + uint64_t duration = hrc::diff(pktTime, status->callStartTime); + uint32_t srcId = (status->assembler.getExtendedAddress()) ? status->assembler.dataHeader.getSrcLLId() : status->assembler.dataHeader.getLLId(); + uint32_t dstId = status->assembler.dataHeader.getLLId(); + LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "P25, Data Call End, peer = %u, srcId = %u, dstId = %u, blocks = %u, duration = %u, streamId = %u, fromUpstream = %u", + peerId, srcId, dstId, status->assembler.dataHeader.getBlocksToFollow(), duration / 1000, streamId, fromUpstream); + + // report call event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_event") + .tag("peerId", std::to_string(peerId)) + .tag("mode", "P25") + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(srcId)) + .tag("dstId", std::to_string(dstId)) + .field("duration", duration) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .requestAsync(m_network->m_influxServer); + } - delete status; - m_status.erase(peerId); + m_status.erase(peerId); + delete status; + status = nullptr; + } else { + status->callBusy = false; + } } return true; @@ -372,7 +308,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a std::string srcIpStr = __IP_FROM_UINT(srcProtoAddr); std::string tgtIpStr = __IP_FROM_UINT(tgtProtoAddr); - LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", + LogInfoEx(LOG_P25, "VTUN -> PDU IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", srcIpStr.c_str(), WUID_FNE, tgtIpStr.c_str(), llId, pktLen, proto); // assemble a P25 PDU frame header for transport... @@ -388,15 +324,15 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a pktHeader->calculateLength(pktLen); uint32_t pduLength = pktHeader->getPDULength(); if (pduLength < pktLen) { - LogWarning(LOG_NET, "P25, VTUN, data truncated!"); + LogWarning(LOG_P25, "VTUN, data truncated!"); pktLen = pduLength; // don't overflow the buffer } DECLARE_UINT8_ARRAY(pduUserData, pduLength); ::memcpy(pduUserData, data, pktLen); -#if DEBUG_P25_PDU_DATA +//#if DEBUG_P25_PDU_DATA Utils::dump(1U, "P25, P25PacketData::processPacketFrame(), pduUserData", pduUserData, pduLength); -#endif +//#endif // queue frame for dispatch QueuedDataFrame* qf = new QueuedDataFrame(); @@ -416,6 +352,58 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a #endif // !defined(_WIN32) } +/* Helper to write a PDU acknowledge response. */ + +void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) +{ + if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) + return; + + data::DataHeader rspHeader = data::DataHeader(); + rspHeader.setFormat(PDUFormatType::RSP); + rspHeader.setMFId(MFG_STANDARD); + rspHeader.setOutbound(true); + rspHeader.setResponseClass(ackClass); + rspHeader.setResponseType(ackType); + rspHeader.setResponseStatus(ackStatus); + rspHeader.setLLId(llId); + if (srcLlId > 0U) { + rspHeader.setSrcLLId(srcLlId); + } + + if (!extendedAddress) + rspHeader.setFullMessage(true); + else + rspHeader.setFullMessage(false); + + rspHeader.setBlocksToFollow(0U); + + dispatchUserFrameToFNE(rspHeader, srcLlId > 0U, false, nullptr); +} + +/* Helper used to return a KMM to the calling SU. */ + +void P25PacketData::write_PDU_KMM(const uint8_t* data, uint32_t len, uint32_t llId, bool encrypted) +{ + // assemble a P25 PDU frame header for transport... + data::DataHeader dataHeader = data::DataHeader(); + dataHeader.setFormat(PDUFormatType::CONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP((encrypted) ? PDUSAP::ENC_KMM : PDUSAP::UNENC_KMM); + dataHeader.setLLId(llId); + dataHeader.setBlocksToFollow(1U); + + dataHeader.calculateLength(len); + uint32_t pduLength = dataHeader.getPDULength(); + + DECLARE_UINT8_ARRAY(pduUserData, pduLength); + ::memcpy(pduUserData, data, len); + + dispatchUserFrameToFNE(dataHeader, false, false, pduUserData); +} + /* Updates the timer by the passed number of milliseconds. */ void P25PacketData::clock(uint32_t ms) @@ -435,25 +423,25 @@ void P25PacketData::clock(uint32_t ms) processed = true; if (frame->retryCnt >= MAX_PKT_RETRY_CNT && !frame->extendRetry) { - LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + LogWarning(LOG_P25, P25_PDU_STR ", max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); goto pkt_clock_abort; } if (frame->retryCnt >= (MAX_PKT_RETRY_CNT * 2U) && frame->extendRetry) { - LogWarning(LOG_NET, "P25, max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); + LogWarning(LOG_P25, P25_PDU_STR ", max packet retry count exceeded, dropping packet, dstIp = %s", __IP_FROM_UINT(frame->tgtProtoAddr).c_str()); m_readyForNextPkt[frame->llId] = true; // force ready for next packet goto pkt_clock_abort; } std::string tgtIpStr = __IP_FROM_UINT(frame->tgtProtoAddr); - LogMessage(LOG_NET, "P25, VTUN -> PDU IP Data, dstIp = %s (%u), userDataLen = %u, retries = %u", + LogInfoEx(LOG_P25, "VTUN -> PDU IP Data, dstIp = %s (%u), userDataLen = %u, retries = %u", tgtIpStr.c_str(), frame->llId, frame->userDataLen, frame->retryCnt); // do we have a valid target address? if (frame->llId == 0U) { frame->llId = getLLIdAddress(frame->tgtProtoAddr); if (frame->llId == 0U) { - LogWarning(LOG_NET, "P25, no ARP entry for, dstIp = %s", tgtIpStr.c_str()); + LogWarning(LOG_P25, P25_PDU_STR ", no ARP entry for, dstIp = %s", tgtIpStr.c_str()); write_PDU_ARP(frame->tgtProtoAddr); processed = false; @@ -470,7 +458,7 @@ void P25PacketData::clock(uint32_t ms) auto ready = std::find_if(m_readyForNextPkt.begin(), m_readyForNextPkt.end(), [=](ReadyForNextPktPair x) { return x.first == frame->llId; }); if (ready != m_readyForNextPkt.end()) { if (!ready->second) { - LogWarning(LOG_NET, "P25, subscriber not ready, dstIp = %s", tgtIpStr.c_str()); + LogWarning(LOG_P25, P25_PDU_STR ", subscriber not ready, dstIp = %s", tgtIpStr.c_str()); processed = false; frame->timestamp = now + SUBSCRIBER_READY_RETRY_MS; frame->extendRetry = true; @@ -480,7 +468,7 @@ void P25PacketData::clock(uint32_t ms) } m_readyForNextPkt[frame->llId] = false; - dispatchUserFrameToFNE(*frame->header, false, frame->userData); + dispatchUserFrameToFNE(*frame->header, false, false, frame->userData); } } @@ -498,6 +486,38 @@ void P25PacketData::clock(uint32_t ms) } } +/* Helper to cleanup any call's left in a dangling state without any further updates. */ + +void P25PacketData::cleanupStale() +{ + // check to see if any peers have been quiet (no ping) longer than allowed + std::vector peersToRemove = std::vector(); + m_status.lock(false); + for (auto peerStatus : m_status) { + uint32_t id = peerStatus.first; + RxStatus* status = peerStatus.second; + if (status != nullptr) { + uint64_t lastPktDuration = hrc::diff(hrc::now(), status->lastPacket); + if ((lastPktDuration / 1000) > 10U) { + LogWarning(LOG_P25, "P25, Data Call Timeout, lasted more then %us with no further updates", 10U); + status->callBusy = true; // force flag the call busy + peersToRemove.push_back(id); + } + } + } + m_status.unlock(); + + // remove any peers + for (uint32_t peerId : peersToRemove) { + RxStatus* status = m_status[peerId]; + if (status != nullptr) { + m_status.erase(peerId); + delete status; + status = nullptr; + } + } +} + // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- @@ -509,60 +529,40 @@ void P25PacketData::dispatch(uint32_t peerId) RxStatus* status = m_status[peerId]; if (status == nullptr) { - LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet state, status shouldn't be null"); + LogError(LOG_P25, P25_PDU_STR ", illegal PDU packet state, status shouldn't be null"); return; } - bool crcValid = false; - if (status->header.getBlocksToFollow() > 0U) { - if (status->pduUserDataLength < 4U) { - LogError(LOG_NET, P25_PDU_STR ", illegal PDU packet length, peer = %u, llId = %u, blocks %u, len %u", - peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); - return; - } - - crcValid = edac::CRC::checkCRC32(status->pduUserData, status->pduUserDataLength); - if (!crcValid) { - LogError(LOG_NET, P25_PDU_STR ", failed CRC-32 check, peer = %u, llId = %u, blocks %u, len %u", - peerId, status->header.getLLId(), status->header.getBlocksToFollow(), status->pduUserDataLength); - return; - } - } - - if (m_network->m_dumpPacketData && status->dataBlockCnt > 0U) { - Utils::dump(1U, "P25, ISP PDU Packet", status->pduUserData, status->pduUserDataLength); - } - - if (status->header.getFormat() == PDUFormatType::RSP) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, peer = %u, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - peerId, status->header.getFormat(), status->header.getResponseClass(), status->header.getResponseType(), status->header.getResponseStatus(), - status->header.getLLId(), status->header.getSrcLLId()); + if (status->assembler.dataHeader.getFormat() == PDUFormatType::RSP) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, peer = %u, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + peerId, status->assembler.dataHeader.getFormat(), status->assembler.dataHeader.getResponseClass(), status->assembler.dataHeader.getResponseType(), status->assembler.dataHeader.getResponseStatus(), + status->assembler.dataHeader.getLLId(), status->assembler.dataHeader.getSrcLLId()); // bryanb: this is naive and possibly error prone - m_readyForNextPkt[status->header.getSrcLLId()] = true; + m_readyForNextPkt[status->assembler.dataHeader.getSrcLLId()] = true; - if (status->header.getResponseClass() == PDUAckClass::ACK && status->header.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, peer = %u, llId = %u, all blocks received OK, n = %u", - peerId, status->header.getLLId(), status->header.getResponseStatus()); + if (status->assembler.dataHeader.getResponseClass() == PDUAckClass::ACK && status->assembler.dataHeader.getResponseType() == PDUAckType::ACK) { + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, OSP ACK, peer = %u, llId = %u, all blocks received OK, n = %u", + peerId, status->assembler.dataHeader.getLLId(), status->assembler.dataHeader.getResponseStatus()); } else { - if (status->header.getResponseClass() == PDUAckClass::NACK) { - switch (status->header.getResponseType()) { + if (status->assembler.dataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (status->assembler.dataHeader.getResponseType()) { case PDUAckType::NACK_ILLEGAL: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, peer = %u, llId = %u", - peerId, status->header.getLLId()); + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, peer = %u, llId = %u", + peerId, status->assembler.dataHeader.getLLId()); break; case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, peer = %u, llId = %u, n = %u", - peerId, status->header.getLLId(), status->header.getResponseStatus()); + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, peer = %u, llId = %u, n = %u", + peerId, status->assembler.dataHeader.getLLId(), status->assembler.dataHeader.getResponseStatus()); break; case PDUAckType::NACK_SEQ: case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, peer = %u, llId = %u, seqNo = %u", - peerId, status->header.getLLId(), status->header.getResponseStatus()); + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, peer = %u, llId = %u, seqNo = %u", + peerId, status->assembler.dataHeader.getLLId(), status->assembler.dataHeader.getResponseStatus()); break; case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, peer = %u, llId = %u, n = %u", - peerId, status->header.getLLId(), status->header.getResponseStatus()); + LogInfoEx(LOG_P25, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, peer = %u, llId = %u, n = %u", + peerId, status->assembler.dataHeader.getLLId(), status->assembler.dataHeader.getResponseStatus()); break; default: @@ -574,17 +574,13 @@ void P25PacketData::dispatch(uint32_t peerId) return; } - if (status->header.getFormat() == PDUFormatType::UNCONFIRMED) { - m_readyForNextPkt[status->header.getSrcLLId()] = true; + if (status->assembler.dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) { + m_readyForNextPkt[status->assembler.dataHeader.getSrcLLId()] = true; } - uint8_t sap = (status->extendedAddress) ? status->header.getEXSAP() : status->header.getSAP(); - - // don't dispatch SNDCP control, conventional data registration or ARP - if (sap != PDUSAP::SNDCP_CTRL_DATA && sap != PDUSAP::CONV_DATA_REG && - sap != PDUSAP::ARP) { - dispatchToFNE(peerId); - } + uint8_t sap = (status->assembler.getExtendedAddress()) ? status->assembler.dataHeader.getEXSAP() : status->assembler.dataHeader.getSAP(); + if (status->assembler.getAuxiliaryES()) + sap = status->assembler.dataHeader.getEXSAP(); // handle standard P25 service access points switch (sap) { @@ -599,7 +595,7 @@ void P25PacketData::dispatch(uint32_t peerId) uint8_t arpPacket[P25_PDU_ARP_PCKT_LENGTH]; ::memset(arpPacket, 0x00U, P25_PDU_ARP_PCKT_LENGTH); - ::memcpy(arpPacket, status->pduUserData + 12U, P25_PDU_ARP_PCKT_LENGTH); + ::memcpy(arpPacket, status->pduUserData, P25_PDU_ARP_PCKT_LENGTH); uint16_t opcode = GET_UINT16(arpPacket, 6U); uint32_t srcHWAddr = GET_UINT24(arpPacket, 8U); @@ -608,16 +604,16 @@ void P25PacketData::dispatch(uint32_t peerId) uint32_t tgtProtoAddr = GET_UINT32(arpPacket, 18U); if (opcode == P25_PDU_ARP_REQUEST) { - LogMessage(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); + LogInfoEx(LOG_P25, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); if (fneIPv4 == tgtProtoAddr) { write_PDU_ARP_Reply(fneIPv4, srcHWAddr, srcProtoAddr, WUID_FNE); } else { write_PDU_ARP_Reply(tgtProtoAddr, srcHWAddr, srcProtoAddr); } } else if (opcode == P25_PDU_ARP_REPLY) { - LogMessage(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); + LogInfoEx(LOG_P25, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); if (fneIPv4 == srcProtoAddr) { - LogWarning(LOG_NET, P25_PDU_STR ", ARP reply, %u is trying to masquerade as us...", srcHWAddr); + LogWarning(LOG_P25, P25_PDU_STR ", ARP reply, %u is trying to masquerade as us...", srcHWAddr); } else { m_arpTable[srcHWAddr] = srcProtoAddr; @@ -644,13 +640,14 @@ void P25PacketData::dispatch(uint32_t peerId) if (!m_network->m_host->m_vtunEnabled) break; - int dataPktOffset = 0U; - if (status->header.getFormat() == PDUFormatType::CONFIRMED && status->extendedAddress) - dataPktOffset = 4U; - if (status->header.getFormat() == PDUFormatType::UNCONFIRMED && status->extendedAddress) - dataPktOffset = 12U; + uint32_t srcLlId = status->assembler.dataHeader.getSrcLLId(); + if (!status->assembler.getExtendedAddress()) + srcLlId = status->assembler.dataHeader.getLLId(); + uint32_t dstLlId = status->assembler.dataHeader.getLLId(); + if (!status->assembler.getExtendedAddress()) + dstLlId = WUID_FNE; - struct ip* ipHeader = (struct ip*)(status->pduUserData + dataPktOffset); + struct ip* ipHeader = (struct ip*)(status->pduUserData); char srcIp[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(ipHeader->ip_src), srcIp, INET_ADDRSTRLEN); @@ -663,73 +660,81 @@ void P25PacketData::dispatch(uint32_t peerId) // reflect broadcast messages back to the CAI network bool handled = false; - if (status->header.getLLId() == WUID_ALL) { - LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, repeated to CAI, broadcast packet, dstIp = %s (%u)", - dstIp, status->header.getLLId()); + if (status->assembler.dataHeader.getLLId() == WUID_ALL) { + LogInfoEx(LOG_P25, "PDU -> VTUN, IP Data, repeated to CAI, broadcast packet, dstIp = %s (%u)", + dstIp, status->assembler.dataHeader.getLLId()); - dispatchUserFrameToFNE(status->header, status->extendedAddress, status->pduUserData); + dispatchUserFrameToFNE(status->assembler.dataHeader, status->assembler.getExtendedAddress(), + status->assembler.getAuxiliaryES(), status->pduUserData); handled = true; // is the source SU one we have proper ARP entries for? - auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getSrcLLId(); }); + auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->assembler.dataHeader.getSrcLLId(); }); if (arpEntry == m_arpTable.end()) { uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); - LogMessage(LOG_NET, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->header.getSrcLLId()); - m_arpTable[status->header.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr); + LogInfoEx(LOG_P25, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->assembler.dataHeader.getSrcLLId()); + m_arpTable[status->assembler.dataHeader.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr); } } // is the target SU one we have proper ARP entries for? - auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getLLId(); }); + auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->assembler.dataHeader.getLLId(); }); if (arpEntry != m_arpTable.end()) { - LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, repeated to CAI, destination IP has a CAI ARP table entry, dstIp = %s (%u)", - dstIp, status->header.getLLId()); + LogInfoEx(LOG_P25, "PDU -> VTUN, IP Data, repeated to CAI, destination IP has a CAI ARP table entry, dstIp = %s (%u)", + dstIp, status->assembler.dataHeader.getLLId()); - dispatchUserFrameToFNE(status->header, status->extendedAddress, status->pduUserData); + dispatchUserFrameToFNE(status->assembler.dataHeader, status->assembler.getExtendedAddress(), status->assembler.getAuxiliaryES(), + status->pduUserData); handled = true; // is the source SU one we have proper ARP entries for? - auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->header.getSrcLLId(); }); + auto arpEntry = std::find_if(m_arpTable.begin(), m_arpTable.end(), [=](ArpTablePair x) { return x.first == status->assembler.dataHeader.getSrcLLId(); }); if (arpEntry == m_arpTable.end()) { uint32_t srcProtoAddr = Utils::reverseEndian(ipHeader->ip_src.s_addr); - LogMessage(LOG_NET, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->header.getSrcLLId()); - m_arpTable[status->header.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr); + LogInfoEx(LOG_P25, P25_PDU_STR ", adding ARP entry, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), status->assembler.dataHeader.getSrcLLId()); + m_arpTable[status->assembler.dataHeader.getSrcLLId()] = Utils::reverseEndian(ipHeader->ip_src.s_addr); } } // transmit packet to IP network - LogMessage(LOG_NET, "P25, PDU -> VTUN, IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", - srcIp, status->header.getSrcLLId(), dstIp, status->header.getLLId(), pktLen, proto); + LogInfoEx(LOG_P25, "PDU -> VTUN, IP Data, srcIp = %s (%u), dstIp = %s (%u), pktLen = %u, proto = %02X", + srcIp, srcLlId, dstIp, dstLlId, pktLen, proto); DECLARE_UINT8_ARRAY(ipFrame, pktLen); - ::memcpy(ipFrame, status->pduUserData + dataPktOffset, pktLen); + ::memcpy(ipFrame, status->pduUserData, pktLen); #if DEBUG_P25_PDU_DATA Utils::dump(1U, "P25, P25PacketData::dispatch(), ipFrame", ipFrame, pktLen); #endif if (!m_network->m_host->m_tun->write(ipFrame, pktLen)) { - LogError(LOG_NET, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen); + LogError(LOG_P25, P25_PDU_STR ", failed to write IP frame to virtual tunnel, len %u", pktLen); } // if the packet is unhandled and sent off to VTUN; ack the packet so the sender knows we received it if (!handled) { - write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->header.getNs(), status->header.getSrcLLId(), - true, status->header.getLLId()); + if (status->assembler.getExtendedAddress()) { + m_readyForNextPkt[srcLlId] = true; + write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->assembler.dataHeader.getNs(), srcLlId, + true, dstLlId); + } else { + m_readyForNextPkt[srcLlId] = true; + write_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, status->assembler.dataHeader.getNs(), srcLlId, false); + } } #endif // !defined(_WIN32) } break; case PDUSAP::CONV_DATA_REG: { - LogMessage(LOG_NET, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), peer = %u, blocksToFollow = %u", - peerId, status->header.getBlocksToFollow()); + LogInfoEx(LOG_P25, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), peer = %u, blocksToFollow = %u", + peerId, status->assembler.dataHeader.getBlocksToFollow()); processConvDataReg(status); } break; case PDUSAP::SNDCP_CTRL_DATA: { - LogMessage(LOG_NET, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), peer = %u, blocksToFollow = %u", - peerId, status->header.getBlocksToFollow()); + LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), peer = %u, blocksToFollow = %u", + peerId, status->assembler.dataHeader.getBlocksToFollow()); processSNDCPControl(status); } @@ -737,12 +742,16 @@ void P25PacketData::dispatch(uint32_t peerId) case PDUSAP::UNENC_KMM: case PDUSAP::ENC_KMM: { - LogMessage(LOG_NET, P25_PDU_STR ", KMM (Key Management Message), peer = %u, blocksToFollow = %u", - peerId, status->header.getBlocksToFollow()); + LogInfoEx(LOG_P25, P25_PDU_STR ", KMM (Key Management Message), peer = %u, blocksToFollow = %u", + peerId, status->assembler.dataHeader.getBlocksToFollow()); - processKMM(status); + bool encrypted = (sap == PDUSAP::ENC_KMM); + m_network->m_p25OTARService->processDLD(status->pduUserData, status->pduUserDataLength, status->llId, + status->assembler.dataHeader.getNs(), encrypted); } + break; default: + dispatchToFNE(peerId); break; } } @@ -753,52 +762,48 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) { RxStatus* status = m_status[peerId]; - uint32_t srcId = (status->extendedAddress) ? status->header.getSrcLLId() : status->header.getLLId(); - uint32_t dstId = status->header.getLLId(); + uint32_t srcId = (status->assembler.getExtendedAddress()) ? status->assembler.dataHeader.getSrcLLId() : status->assembler.dataHeader.getLLId(); + uint32_t dstId = status->assembler.dataHeader.getLLId(); + + /* + ** MASTER TRAFFIC + */ // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { - uint32_t i = 0U; for (auto peer : m_network->m_peers) { if (peerId != peer.first) { - // every 2 peers flush the queue - if (i % 2U == 0U) { - m_network->m_frameQueue->flushQueue(); - } - - write_PDU_User(peer.first, peerId, nullptr, status->header, status->extendedAddress, status->pduUserData, true); + write_PDU_User(peer.first, peerId, nullptr, status->assembler.dataHeader, status->assembler.getExtendedAddress(), + status->assembler.getAuxiliaryES(), status->pduUserData); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", + LogDebug(LOG_P25, "srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, peer.first, DUID::PDU, srcId, dstId); } - - i++; } } - m_network->m_frameQueue->flushQueue(); } - // repeat traffic to external peers + /* + ** PEER TRAFFIC (e.g. upstream networks this FNE is peered to) + */ + + // repeat traffic to neighbor FNE peers if (m_network->m_host->m_peerNetworks.size() > 0U) { for (auto peer : m_network->m_host->m_peerNetworks) { uint32_t dstPeerId = peer.second->getPeerId(); // don't try to repeat traffic to the source peer...if this traffic - // is coming from a external peer + // is coming from a neighbor FNE peer if (dstPeerId != peerId) { - // check if the source peer is blocked from sending to this peer - if (peer.second->checkBlockedPeer(peerId)) { - continue; - } - // skip peer if it isn't enabled if (!peer.second->isEnabled()) { continue; } - write_PDU_User(dstPeerId, peerId, peer.second, status->header, status->extendedAddress, status->pduUserData); + write_PDU_User(dstPeerId, peerId, peer.second, status->assembler.dataHeader, status->assembler.getExtendedAddress(), + status->assembler.getAuxiliaryES(), status->pduUserData); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", + LogDebug(LOG_P25, "srcPeer = %u, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peerId, dstPeerId, DUID::PDU, srcId, dstId); } } @@ -806,9 +811,9 @@ void P25PacketData::dispatchToFNE(uint32_t peerId) } } -/* Helper to dispatch PDU user data back to the local FNE network. (Will not transmit to external peers.) */ +/* Helper to dispatch PDU user data back to the local FNE network. (Will not transmit to neighbor FNE peers.) */ -void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData) +void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData) { uint32_t srcId = (extendedAddress) ? dataHeader.getSrcLLId() : dataHeader.getLLId(); uint32_t dstId = dataHeader.getLLId(); @@ -823,28 +828,22 @@ void P25PacketData::dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bo dataHeader.setNs(m_suSendSeq[srcId]); + /* + ** MASTER TRAFFIC + */ + // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { - uint32_t i = 0U; for (auto peer : m_network->m_peers) { - // every 2 peers flush the queue - if (i % 2U == 0U) { - m_network->m_frameQueue->flushQueue(); - } - - write_PDU_User(peer.first, m_network->m_peerId, nullptr, dataHeader, extendedAddress, pduUserData, true); + write_PDU_User(peer.first, m_network->m_peerId, nullptr, dataHeader, extendedAddress, auxiliaryES, pduUserData); if (m_network->m_debug) { - LogDebug(LOG_NET, "P25, dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", + LogDebug(LOG_P25, "dstPeer = %u, duid = $%02X, srcId = %u, dstId = %u", peer.first, DUID::PDU, srcId, dstId); } - - i++; } - m_network->m_frameQueue->flushQueue(); } } - /* Helper used to process conventional data registration from PDU data. */ bool P25PacketData::processConvDataReg(RxStatus* status) @@ -857,11 +856,11 @@ bool P25PacketData::processConvDataReg(RxStatus* status) uint32_t ipAddr = (status->pduUserData[8U] << 24) + (status->pduUserData[9U] << 16) + (status->pduUserData[10U] << 8) + status->pduUserData[11U]; if (ipAddr == 0U) { - LogWarning(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect) with zero IP address, llId = %u", llId); + LogWarning(LOG_P25, P25_PDU_STR ", CONNECT (Registration Request Connect) with zero IP address, llId = %u", llId); ipAddr = getIPAddress(llId); } - LogMessage(LOG_NET, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + LogInfoEx(LOG_P25, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); m_arpTable[llId] = ipAddr; // update ARP table } break; @@ -869,13 +868,13 @@ bool P25PacketData::processConvDataReg(RxStatus* status) { uint32_t llId = (status->pduUserData[1U] << 16) + (status->pduUserData[2U] << 8) + status->pduUserData[3U]; - LogMessage(LOG_NET, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); + LogInfoEx(LOG_P25, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); m_arpTable.erase(llId); } break; default: - LogError(LOG_RF, "P25 unhandled PDU registration type, regType = $%02X", regType); + LogError(LOG_P25, "P25 unhandled PDU registration type, regType = $%02X", regType); break; } @@ -888,17 +887,17 @@ bool P25PacketData::processSNDCPControl(RxStatus* status) { std::unique_ptr packet = SNDCPFactory::create(status->pduUserData); if (packet == nullptr) { - LogWarning(LOG_NET, P25_PDU_STR ", undecodable SNDCP packet"); + LogWarning(LOG_P25, P25_PDU_STR ", undecodable SNDCP packet"); return false; } - uint32_t llId = status->header.getLLId(); + uint32_t llId = status->assembler.dataHeader.getLLId(); switch (packet->getPDUType()) { case SNDCP_PDUType::ACT_TDS_CTX: { SNDCPCtxActRequest* isp = static_cast(packet.get()); - LogMessage(LOG_NET, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId, + LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId, isp->getNSAPI(), __IP_FROM_UINT(isp->getIPAddress()).c_str(), isp->getNAT(), isp->getDSUT(), isp->getMDPCO()); m_arpTable[llId] = isp->getIPAddress(); @@ -908,7 +907,7 @@ bool P25PacketData::processSNDCPControl(RxStatus* status) case SNDCP_PDUType::DEACT_TDS_CTX_REQ: { SNDCPCtxDeactivation* isp = static_cast(packet.get()); - LogMessage(LOG_NET, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId, + LogInfoEx(LOG_P25, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId, isp->getDeactType()); m_arpTable.erase(llId); @@ -922,58 +921,6 @@ bool P25PacketData::processSNDCPControl(RxStatus* status) return true; } -/* Helper used to process KMM frames from PDU data. */ - -bool P25PacketData::processKMM(RxStatus* status) -{ - std::unique_ptr frame = KMMFactory::create(status->pduUserData); - if (frame == nullptr) { - LogWarning(LOG_NET, P25_PDU_STR ", undecodable KMM packet"); - return false; - } - - uint32_t llId = status->header.getLLId(); - - switch (frame->getMessageId()) { - case KMM_MessageType::HELLO: - { - KMMHello* kmm = static_cast(frame.get()); - LogMessage(LOG_NET, P25_PDU_STR ", KMM Hello, llId = %u, flag = $%02X", llId, - kmm->getFlag()); - - // respond with No-Service - // assemble a P25 PDU frame header for transport... - data::DataHeader dataHeader = data::DataHeader(); - dataHeader.setFormat(PDUFormatType::UNCONFIRMED); - dataHeader.setMFId(MFG_STANDARD); - dataHeader.setAckNeeded(false); - dataHeader.setOutbound(true); - dataHeader.setSAP(PDUSAP::UNENC_KMM); - dataHeader.setLLId(status->llId); - dataHeader.setBlocksToFollow(1U); - - dataHeader.calculateLength(KMM_NO_SERVICE_LENGTH); - uint32_t pduLength = dataHeader.getPDULength(); - - DECLARE_UINT8_ARRAY(pduUserData, pduLength); - - uint8_t buffer[KMM_NO_SERVICE_LENGTH]; - KMMNoService outKmm = KMMNoService(); - outKmm.encode(buffer); - - ::memcpy(pduUserData, buffer, KMM_NO_SERVICE_LENGTH); - - dispatchUserFrameToFNE(dataHeader, false, pduUserData); - } - break; - - default: - break; - } // switch (packet->getPDUType()) - - return true; -} - /* Helper write ARP request to the network. */ void P25PacketData::write_PDU_ARP(uint32_t addr) @@ -1000,7 +947,7 @@ void P25PacketData::write_PDU_ARP(uint32_t addr) #if DEBUG_P25_PDU_DATA Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif - LogMessage(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(addr).c_str(), fneIPv4.c_str(), WUID_FNE); + LogInfoEx(LOG_P25, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(addr).c_str(), fneIPv4.c_str(), WUID_FNE); // assemble a P25 PDU frame header for transport... data::DataHeader rspHeader = data::DataHeader(); @@ -1021,7 +968,7 @@ void P25PacketData::write_PDU_ARP(uint32_t addr) DECLARE_UINT8_ARRAY(pduUserData, pduLength); ::memcpy(pduUserData + P25_PDU_HEADER_LENGTH_BYTES, arpPacket, P25_PDU_ARP_PCKT_LENGTH); - dispatchUserFrameToFNE(rspHeader, true, pduUserData); + dispatchUserFrameToFNE(rspHeader, true, false, pduUserData); #endif // !defined(_WIN32) } @@ -1056,7 +1003,7 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL #if DEBUG_P25_PDU_DATA Utils::dump(1U, "P25, P25PacketData::write_PDU_ARP_Reply(), arpPacket", arpPacket, P25_PDU_ARP_PCKT_LENGTH); #endif - LogMessage(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(targetAddr).c_str(), tgtLlid); + LogInfoEx(LOG_P25, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(targetAddr).c_str(), tgtLlid); // assemble a P25 PDU frame header for transport... data::DataHeader rspHeader = data::DataHeader(); @@ -1077,135 +1024,37 @@ void P25PacketData::write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorL DECLARE_UINT8_ARRAY(pduUserData, pduLength); ::memcpy(pduUserData + P25_PDU_HEADER_LENGTH_BYTES, arpPacket, P25_PDU_ARP_PCKT_LENGTH); - dispatchUserFrameToFNE(rspHeader, true, pduUserData); -} - -/* Helper to write a PDU acknowledge response. */ - -void P25PacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, uint32_t srcLlId) -{ - if (ackClass == PDUAckClass::ACK && ackType != PDUAckType::ACK) - return; - - data::DataHeader rspHeader = data::DataHeader(); - rspHeader.setFormat(PDUFormatType::RSP); - rspHeader.setMFId(MFG_STANDARD); - rspHeader.setOutbound(true); - rspHeader.setResponseClass(ackClass); - rspHeader.setResponseType(ackType); - rspHeader.setResponseStatus(ackStatus); - rspHeader.setLLId(llId); - if (srcLlId > 0U) { - rspHeader.setSrcLLId(srcLlId); - } - - if (!extendedAddress) - rspHeader.setFullMessage(true); - else - rspHeader.setFullMessage(false); - - rspHeader.setBlocksToFollow(0U); - - dispatchUserFrameToFNE(rspHeader, srcLlId > 0U, nullptr); + dispatchUserFrameToFNE(rspHeader, true, false, pduUserData); } /* Helper to write user data as a P25 PDU packet. */ void P25PacketData::write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, data::DataHeader& dataHeader, - bool extendedAddress, uint8_t* pduUserData, bool queueOnly) + bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData) { uint32_t streamId = m_network->createStreamId(); uint16_t pktSeq = 0U; - uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); - - if (m_network->m_verbosePacketData) - LogMessage(LOG_NET, P25_PDU_STR ", OSP, peerId = %u, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", - peerId, dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), - dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), - dataHeader.getHeaderOffset(), dataHeader.getLLId()); - - // generate the PDU header and 1/2 rate Trellis - dataHeader.encode(buffer); - writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 0U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); - if (pduUserData == nullptr) - return; - - ++pktSeq; - uint32_t packetLength = dataHeader.getPDULength(); - - if (blocksToFollow > 0U) { - uint32_t dataOffset = 0U; - uint32_t networkBlock = 1U; - - // generate the second PDU header - if ((dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData, true); - - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataHeader.encodeExtAddr(buffer); - writeNetwork(peerId, srcPeerId, peerNet, dataHeader, 1U, buffer, P25_PDU_FEC_LENGTH_BYTES, pktSeq, streamId, queueOnly); - ++pktSeq; - - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - - blocksToFollow--; - networkBlock++; - - if (m_network->m_verbosePacketData) - LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - - // are we processing extended address data from the first block? - if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData); - - if (m_network->m_verbosePacketData) - LogMessage(LOG_NET, P25_PDU_STR ", OSP, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - - if (dataHeader.getFormat() != PDUFormatType::AMBT) { - edac::CRC::addCRC32(pduUserData, packetLength); - } - - if (m_network->m_dumpPacketData) { - Utils::dump("P25, OSP PDU User Data", pduUserData, packetLength); - } - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - data::DataBlock dataBlock = data::DataBlock(); - dataBlock.setFormat(dataHeader); - dataBlock.setSerialNo(i); - dataBlock.setData(pduUserData + dataOffset); - - if (m_network->m_verbosePacketData) - LogMessage(LOG_NET, P25_PDU_STR ", OSP, peerId = %u, block %u, fmt = $%02X, lastBlock = %u", - peerId, (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), - dataBlock.getLastBlock()); - - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataBlock.encode(buffer); - writeNetwork(peerId, srcPeerId, peerNet, dataHeader, networkBlock, buffer, P25_PDU_FEC_LENGTH_BYTES, (dataBlock.getLastBlock()) ? RTP_END_OF_CALL_SEQ : pktSeq, streamId); - ++pktSeq; - - dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - - networkBlock++; - } - } + pktSeq = RTP_END_OF_CALL_SEQ; + + UserContext* context = new UserContext(); + context->obj = this; + context->peerId = peerId; + context->srcPeerId = srcPeerId; + context->peerNet = peerNet; + context->header = new data::DataHeader(dataHeader); + context->pktSeq = pktSeq; + context->streamId = streamId; + + m_assembler->assemble(dataHeader, extendedAddress, auxiliaryES, pduUserData, nullptr, context); + delete context; } /* Write data processed to the network. */ bool P25PacketData::writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, - const uint8_t *data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly) + const uint8_t *data, uint32_t len, uint16_t pktSeq, uint32_t streamId) { assert(data != nullptr); @@ -1218,7 +1067,7 @@ bool P25PacketData::writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::P if (peerNet != nullptr) { return peerNet->writeMaster({ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId); } else { - return m_network->writePeer(peerId, srcPeerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId, false); + return m_network->writePeer(peerId, srcPeerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, pktSeq, streamId); } } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index 30392adef..05c0cf768 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -21,8 +21,9 @@ #include "common/concurrent/deque.h" #include "common/concurrent/unordered_map.h" #include "common/p25/P25Defines.h" -#include "common/p25/data/DataBlock.h" +#include "common/p25/data/Assembler.h" #include "common/p25/data/DataHeader.h" +#include "common/p25/data/DataBlock.h" #include "network/FNENetwork.h" #include "network/PeerNetwork.h" #include "network/callhandler/TagP25Data.h" @@ -64,10 +65,10 @@ namespace network * @param peerId Peer ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. - * @param external Flag indicating traffic is from an external peer. + * @param fromUpstream Flag indicating traffic is from a upstream master. * @returns bool True, if frame is processed, otherwise false. */ - bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool external = false); + bool processFrame(const uint8_t* data, uint32_t len, uint32_t peerId, uint16_t pktSeq, uint32_t streamId, bool fromUpstream = false); /** * @brief Process a data frame from the virtual IP network. @@ -77,31 +78,73 @@ namespace network */ void processPacketFrame(const uint8_t* data, uint32_t len, bool alreadyQueued = false); + /** + * @brief Helper to write a PDU acknowledge response. + * @param ackClass Acknowledgement Class. + * @param ackType Acknowledgement Type. + * @param ackStatus + * @param llId Logical Link ID. + * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param srcLlId Source Logical Link ID. + */ + void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, + uint32_t srcLlId = 0U); + + /** + * @brief Helper used to return a KMM to the calling SU. + * @param data Network data buffer. + * @param len Length of data. + * @param llId Logical Link ID. + * @param encrypted Flag indicating whether or not the KMM frame is encrypted. + */ + void write_PDU_KMM(const uint8_t* data, uint32_t len, uint32_t llId, bool encrypted); + /** * @brief Updates the timer by the passed number of milliseconds. * @param ms Number of milliseconds. */ void clock(uint32_t ms); + /** + * @brief Helper to cleanup any call's left in a dangling state without any further updates. + */ + void cleanupStale(); + private: FNENetwork* m_network; TagP25Data* m_tag; + p25::data::Assembler* m_assembler; + + /** + * @brief Represents the data required for a PDU assembler custom writer context. + * @ingroup fne_network + */ + struct UserContext { + void* obj; //!< Instance of the P25PacketData class. + uint32_t peerId; //!< Peer ID for this request. + uint32_t srcPeerId; //!< Source Peer ID for this request. + network::PeerNetwork* peerNet; //!< Instance of the peer network for an upstream peer. + p25::data::DataHeader* header; //!< PDU data header. + uint16_t pktSeq; //!< Packet sequence. + uint32_t streamId; //!< Stream ID. + }; + /** * @brief Represents a queued data frame from the VTUN. */ class QueuedDataFrame { public: - p25::data::DataHeader* header; //! Instance of a PDU data header. - uint32_t llId; //! Logical Link ID - uint32_t tgtProtoAddr; //! Target Protocol Address + p25::data::DataHeader* header; //!< Instance of a PDU data header. + uint32_t llId; //!< Logical Link ID + uint32_t tgtProtoAddr; //!< Target Protocol Address - uint8_t* userData; //! Raw data buffer - uint32_t userDataLen; //! Length of raw data buffer + uint8_t* userData; //!< Raw data buffer + uint32_t userDataLen; //!< Length of raw data buffer - uint64_t timestamp; //! Timestamp in milliseconds - uint8_t retryCnt; //! Packet Retry Counter - bool extendRetry; //! Flag indicating whether or not to extend the retry count for this packet. + uint64_t timestamp; //!< Timestamp in milliseconds + uint8_t retryCnt; //!< Packet Retry Counter + bool extendRetry; //!< Flag indicating whether or not to extend the retry count for this packet. }; concurrent::deque m_queuedFrames; @@ -111,18 +154,15 @@ namespace network class RxStatus { public: system_clock::hrc::hrc_t callStartTime; + system_clock::hrc::hrc_t lastPacket; uint32_t llId; uint32_t streamId; uint32_t peerId; - p25::data::DataBlock* blockData; - p25::data::DataHeader header; + p25::data::Assembler assembler; bool hasRxHeader; - bool extendedAddress; - uint32_t dataOffset; - uint8_t dataBlockCnt; - uint8_t* netPDU; - uint32_t netPDUCount; + + bool callBusy; uint8_t* pduUserData; uint32_t pduUserDataLength; @@ -134,22 +174,12 @@ namespace network llId(0U), streamId(0U), peerId(0U), - blockData(nullptr), - header(), + assembler(), hasRxHeader(false), - extendedAddress(false), - dataOffset(0U), - dataBlockCnt(0U), - netPDU(nullptr), - netPDUCount(0U), + callBusy(false), pduUserData(nullptr), pduUserDataLength(0U) { - blockData = new p25::data::DataBlock[P25DEF::P25_MAX_PDU_BLOCKS]; - - netPDU = new uint8_t[P25DEF::P25_PDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(netPDU, 0x00U, P25DEF::P25_PDU_FRAME_LENGTH_BYTES + 2U); - pduUserData = new uint8_t[P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); } @@ -158,10 +188,6 @@ namespace network */ ~RxStatus() { - if (blockData != nullptr) - delete[] blockData; - if (netPDU != nullptr) - delete[] netPDU; if (pduUserData != nullptr) delete[] pduUserData; } @@ -188,12 +214,13 @@ namespace network */ void dispatchToFNE(uint32_t peerId); /** - * @brief Helper to dispatch PDU user data back to the local FNE network. (Will not transmit to external peers.) + * @brief Helper to dispatch PDU user data back to the local FNE network. (Will not transmit to neighbor FNE peers.) * @param dataHeader Instance of a PDU data header. * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. */ - void dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); + void dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); /** * @brief Helper used to process conventional data registration from PDU data. @@ -208,13 +235,6 @@ namespace network */ bool processSNDCPControl(RxStatus* status); - /** - * @brief Helper used to process KMM frames from PDU data. - * @param status Instance of the RxStatus class. - * @returns bool True, if KMM data was processed, otherwise false. - */ - bool processKMM(RxStatus* status); - /** * @brief Helper write ARP request to the network. * @param addr IP Address. @@ -229,18 +249,6 @@ namespace network */ void write_PDU_ARP_Reply(uint32_t targetAddr, uint32_t requestorLlid, uint32_t requestorAddr, uint32_t targetLlid = 0U); - /** - * @brief Helper to write a PDU acknowledge response. - * @param ackClass Acknowledgement Class. - * @param ackType Acknowledgement Type. - * @param ackStatus - * @param llId Logical Link ID. - * @param extendedAddress Flag indicating whether or not to extended addressing is in use. - * @param srcLlId Source Logical Link ID. - */ - void write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t ackStatus, uint32_t llId, bool extendedAddress, - uint32_t srcLlId = 0U); - /** * @brief Helper to write user data as a P25 PDU packet. * @param peerId Peer ID. @@ -248,10 +256,11 @@ namespace network * @param peerNet Instance of PeerNetwork to use to send traffic. * @param dataHeader Instance of a PDU data header. * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. */ void write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, - bool extendedAddress, uint8_t* pduUserData, bool queueOnly = false); + bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); /** * @brief Write data processed to the network. @@ -266,7 +275,7 @@ namespace network * @param streamId Stream ID. */ bool writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, - const uint8_t* data, uint32_t len, uint16_t pktSeq, uint32_t streamId, bool queueOnly = false); + const uint8_t* data, uint32_t len, uint16_t pktSeq, uint32_t streamId); /** * @brief Helper to determine if the logical link ID has an ARP entry. diff --git a/src/fne/network/RESTAPI.cpp b/src/fne/restapi/RESTAPI.cpp similarity index 92% rename from src/fne/network/RESTAPI.cpp rename to src/fne/restapi/RESTAPI.cpp index f57c36aec..17d715e4d 100644 --- a/src/fne/network/RESTAPI.cpp +++ b/src/fne/restapi/RESTAPI.cpp @@ -10,19 +10,19 @@ */ #include "fne/Defines.h" #include "common/edac/SHA256.h" +#include "common/json/json.h" #include "common/lookups/AffiliationLookup.h" -#include "common/network/json/json.h" #include "common/Log.h" #include "common/Utils.h" #include "fne/network/callhandler/TagDMRData.h" #include "fne/network/callhandler/TagP25Data.h" -#include "fne/network/RESTAPI.h" +#include "fne/network/SpanningTree.h" +#include "fne/restapi/RESTAPI.h" #include "HostFNE.h" using namespace network; -using namespace network::rest; -using namespace network::rest::http; - +using namespace restapi; +using namespace restapi::http; using namespace lookups; #include @@ -638,6 +638,7 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(FNE_GET_PEER_QUERY).get(REST_API_BIND(RESTAPI::restAPI_GetPeerQuery, this)); m_dispatcher.match(FNE_GET_PEER_COUNT).get(REST_API_BIND(RESTAPI::restAPI_GetPeerCount, this)); m_dispatcher.match(FNE_PUT_PEER_RESET).put(REST_API_BIND(RESTAPI::restAPI_PutPeerReset, this)); + m_dispatcher.match(FNE_PUT_PEER_RESET_CONN).put(REST_API_BIND(RESTAPI::restAPI_PutPeerResetConn, this)); m_dispatcher.match(FNE_GET_RID_QUERY).get(REST_API_BIND(RESTAPI::restAPI_GetRIDQuery, this)); m_dispatcher.match(FNE_PUT_RID_ADD).put(REST_API_BIND(RESTAPI::restAPI_PutRIDAdd, this)); @@ -666,6 +667,8 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(FNE_GET_AFF_LIST).get(REST_API_BIND(RESTAPI::restAPI_GetAffList, this)); + m_dispatcher.match(FNE_GET_SPANNING_TREE).get(REST_API_BIND(RESTAPI::restAPI_GetSpanningTree, this)); + /* ** Digital Mobile Radio */ @@ -873,9 +876,9 @@ void RESTAPI::restAPI_GetPeerQuery(const HTTPPayload& request, HTTPPayload& repl LogDebug(LOG_REST, "No peers connected to this FNE"); } - // report any Peer-Link reported peers - if (m_network->m_peerLinkPeers.size() > 0) { - for (auto entry : m_network->m_peerLinkPeers) { + // report any peers from replica peers + if (m_network->m_peerReplicaPeers.size() > 0) { + for (auto entry : m_network->m_peerReplicaPeers) { json::array peerObjs = entry.second; if (entry.second.size() > 0) { for (auto linkEntry : entry.second) { @@ -940,6 +943,45 @@ void RESTAPI::restAPI_PutPeerReset(const HTTPPayload& request, HTTPPayload& repl m_network->resetPeer(peerId); } +/* REST API endpoint; implements put reset upstream peer connection request.*/ + +void RESTAPI::restAPI_PutPeerResetConn(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object req = json::object(); + if (!parseRequestBody(request, reply, req)) { + return; + } + + errorPayload(reply, "OK", HTTPPayload::OK); + + if (!req["peerId"].is()) { + errorPayload(reply, "peerId was not a valid integer"); + return; + } + + uint32_t peerId = req["peerId"].get(); + + if (m_host->m_peerNetworks.size() > 0) { + for (auto peer : m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->getPeerId() == peerId) { + LogInfoEx(LOG_NET, "PEER %u, request to reset upstream peer connection", peerId); + + peer.second->clearDuplicateConnFlag(); + + peer.second->close(); + peer.second->open(); + break; + } + } + } + } +} + /* REST API endpoint; implements get radio ID query request. */ void RESTAPI::restAPI_GetRIDQuery(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) @@ -1221,12 +1263,18 @@ void RESTAPI::restAPI_GetPeerList(const HTTPPayload& request, HTTPPayload& reply uint32_t peerId = entry.first; std::string peerAlias = entry.second.peerAlias(); - bool peerLink = entry.second.peerLink(); bool peerPassword = !entry.second.peerPassword().empty(); // True if password is not empty, otherwise false + bool peerReplica = entry.second.peerReplica(); + bool canRequestKeys = entry.second.canRequestKeys(); + bool canIssueInhibit = entry.second.canIssueInhibit(); + bool hasCallPriority = entry.second.hasCallPriority(); peerObj["peerId"].set(peerId); peerObj["peerAlias"].set(peerAlias); - peerObj["peerLink"].set(peerLink); peerObj["peerPassword"].set(peerPassword); + peerObj["peerReplica"].set(peerReplica); + peerObj["canRequestKeys"].set(canRequestKeys); + peerObj["canIssueInhibit"].set(canIssueInhibit); + peerObj["hasCallPriority"].set(hasCallPriority); peers.push_back(json::value(peerObj)); } } @@ -1271,32 +1319,73 @@ void RESTAPI::restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, peerAlias = req["peerAlias"].get(); } + // Get peer password (optional) + std::string peerPassword = ""; + if (req.find("peerPassword") != req.end()) { + // Validate + if (!req["peerPassword"].is()) { + errorPayload(reply, "peerPassword was not a valid string"); + return; + } + // Get + peerPassword = req["peerPassword"].get(); + } + // Get peer link setting (optional) - bool peerLink = false; - if (req.find("peerLink") != req.end()) { + bool peerReplica = false; + if (req.find("peerReplica") != req.end()) { // Validate - if (!req["peerLink"].is()) { - errorPayload(reply, "peerLink was not a valid boolean"); + if (!req["peerReplica"].is()) { + errorPayload(reply, "peerReplica was not a valid boolean"); return; } // Get - peerLink = req["peerLink"].get(); + peerReplica = req["peerReplica"].get(); } - // Get peer password (optional) - std::string peerPassword = ""; - if (req.find("peerPassword") != req.end()) { + // Get canRequestKeys + bool canRequestKeys = false; + if (req.find("canRequestKeys") != req.end()) { // Validate - if (!req["peerPassword"].is()) { - errorPayload(reply, "peerPassword was not a valid string"); + if (!req["canRequestKeys"].is()) { + errorPayload(reply, "canRequestKeys was not a valid boolean"); return; } // Get - peerPassword = req["peerPassword"].get(); + canRequestKeys = req["canRequestKeys"].get(); + } + + // Get canIssueInhibit + bool canIssueInhibit = false; + if (req.find("canIssueInhibit") != req.end()) { + // Validate + if (!req["canIssueInhibit"].is()) { + errorPayload(reply, "canIssueInhibit was not a valid boolean"); + return; + } + // Get + canIssueInhibit = req["canIssueInhibit"].get(); + } + + // Get hasCallPriority + bool hasCallPriority = false; + if (req.find("hasCallPriority") != req.end()) { + // Validate + if (!req["hasCallPriority"].is()) { + errorPayload(reply, "hasCallPriority was not a valid boolean"); + return; + } + // Get + hasCallPriority = req["hasCallPriority"].get(); } PeerId entry = PeerId(peerId, peerAlias, peerPassword, false); - entry.peerLink(peerLink); + + // Set additional values + entry.peerReplica(peerReplica); + entry.canRequestKeys(canRequestKeys); + entry.canIssueInhibit(canIssueInhibit); + entry.hasCallPriority(hasCallPriority); m_peerListLookup->addEntry(peerId, entry); } @@ -1576,6 +1665,26 @@ void RESTAPI::restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, reply.payload(response); } +/* REST API endpoint; implements get spanning tree list request. */ + +void RESTAPI::restAPI_GetSpanningTree(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) +{ + if (!validateAuth(request, reply)) { + return; + } + + json::object response = json::object(); + setResponseDefaultStatus(response); + + json::array tree = json::array(); + if (m_network != nullptr) { + SpanningTree::serializeTree(m_network->m_treeRoot, tree); + } + + response["masterTree"].set(tree); + reply.payload(response); +} + /* ** Digital Mobile Radio */ diff --git a/src/fne/network/RESTAPI.h b/src/fne/restapi/RESTAPI.h similarity index 82% rename from src/fne/network/RESTAPI.h rename to src/fne/restapi/RESTAPI.h index 03a923de2..66684a711 100644 --- a/src/fne/network/RESTAPI.h +++ b/src/fne/restapi/RESTAPI.h @@ -17,14 +17,14 @@ #define __REST_API_H__ #include "fne/Defines.h" -#include "common/network/rest/RequestDispatcher.h" -#include "common/network/rest/http/HTTPServer.h" -#include "common/network/rest/http/SecureHTTPServer.h" +#include "common/restapi/RequestDispatcher.h" +#include "common/restapi/http/HTTPServer.h" +#include "common/restapi/http/SecureHTTPServer.h" #include "common/lookups/AdjSiteMapLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/Thread.h" -#include "fne/network/RESTDefines.h" +#include "fne/restapi/RESTDefines.h" #include #include @@ -92,12 +92,12 @@ class HOST_SW_API RESTAPI : private Thread { void close(); private: - typedef network::rest::RequestDispatcher RESTDispatcherType; - typedef network::rest::http::HTTPPayload HTTPPayload; + typedef restapi::RequestDispatcher RESTDispatcherType; + typedef restapi::http::HTTPPayload HTTPPayload; RESTDispatcherType m_dispatcher; - network::rest::http::HTTPServer m_restServer; + restapi::http::HTTPServer m_restServer; #if defined(ENABLE_SSL) - network::rest::http::SecureHTTPServer m_restSecureServer; + restapi::http::SecureHTTPServer m_restSecureServer; bool m_enableSSL; #endif // ENABLE_SSL @@ -148,7 +148,7 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get version request. @@ -156,14 +156,14 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get status request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get peer query request. @@ -171,21 +171,28 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetPeerQuery(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetPeerQuery(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get peer count request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetPeerCount(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetPeerCount(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get peer reset request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutPeerReset(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutPeerReset(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + /** + * @brief REST API endpoint; implements put reset upstream peer connection request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_PutPeerResetConn(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get radio ID query request. @@ -193,28 +200,28 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetRIDQuery(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetRIDQuery(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put radio ID add request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutRIDAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutRIDAdd(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put radio ID delete request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutRIDDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutRIDDelete(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put radio ID commit request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetRIDCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetRIDCommit(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get talkgroup ID query request. @@ -222,28 +229,28 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetTGQuery(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetTGQuery(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put talkgroup ID add request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutTGAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutTGAdd(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put talkgroup ID delete request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutTGDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutTGDelete(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put talkgroup ID commit request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetTGCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetTGCommit(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get peer list query request. @@ -251,28 +258,28 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetPeerList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetPeerList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put peer add request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutPeerAdd(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put peer delete request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutPeerDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutPeerDelete(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put peer list commit request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetPeerCommit(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get adjacent site map list query request. @@ -280,28 +287,28 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetAdjMapList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put adjacent site map add request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutAdjMapAdd(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put adjacent site map delete request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutAdjMapDelete(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put adjacent site map commit request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetAdjMapCommit(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief @@ -309,7 +316,7 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetForceUpdate(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetForceUpdate(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get reload talkgroup ID list request. @@ -317,14 +324,14 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetReloadTGs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetReloadTGs(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get reload radio ID list request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetReloadRIDs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetReloadRIDs(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get affiliation list request. @@ -332,7 +339,15 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetAffList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); + + /** + * @brief REST API endpoint; implements get spanning tree list request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param match HTTP request matcher. + */ + void restAPI_GetSpanningTree(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /* ** Digital Mobile Radio @@ -344,7 +359,7 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /* ** Project 25 @@ -356,7 +371,7 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); }; #endif // __REST_API_H__ diff --git a/src/fne/network/RESTDefines.h b/src/fne/restapi/RESTDefines.h similarity index 92% rename from src/fne/network/RESTDefines.h rename to src/fne/restapi/RESTDefines.h index 66fe955ba..52a8d1490 100644 --- a/src/fne/network/RESTDefines.h +++ b/src/fne/restapi/RESTDefines.h @@ -19,7 +19,7 @@ #define __FNE_REST_DEFINES_H__ #include "fne/Defines.h" -#include "host/network/RESTDefines.h" +#include "host/restapi/RESTDefines.h" // --------------------------------------------------------------------------- // Constants @@ -28,6 +28,7 @@ #define FNE_GET_PEER_QUERY "/peer/query" #define FNE_GET_PEER_COUNT "/peer/count" #define FNE_PUT_PEER_RESET "/peer/reset" +#define FNE_PUT_PEER_RESET_CONN "/peer/connreset" #define FNE_GET_RID_QUERY "/rid/query" #define FNE_PUT_RID_ADD "/rid/add" @@ -56,4 +57,6 @@ #define FNE_GET_AFF_LIST "/report-affiliations" +#define FNE_GET_SPANNING_TREE "/spanning-tree" + #endif // __FNE_REST_DEFINES_H__ diff --git a/src/fne/win32/resource.rc b/src/fne/win32/resource.rc index 330fc13ab..14b9004eb 100644 Binary files a/src/fne/win32/resource.rc and b/src/fne/win32/resource.rc differ diff --git a/src/fw/hotspot b/src/fw/hotspot index 718093aea..e50f1ec90 160000 --- a/src/fw/hotspot +++ b/src/fw/hotspot @@ -1 +1 @@ -Subproject commit 718093aea3a5a6c80f7fceb1bbbc940168b8d98c +Subproject commit e50f1ec9027d3c6d54f95452aadb8dd4b0f113ec diff --git a/src/fw/modem b/src/fw/modem index b124551ad..a65c44a8d 160000 --- a/src/fw/modem +++ b/src/fw/modem @@ -1 +1 @@ -Subproject commit b124551ad8bf56692b82da80325a7508a30a3a0b +Subproject commit a65c44a8df1867517f9a30346577d22ba2797b19 diff --git a/src/host/ActivityLog.cpp b/src/host/ActivityLog.cpp index 6764a4c89..c44261b03 100644 --- a/src/host/ActivityLog.cpp +++ b/src/host/ActivityLog.cpp @@ -1,25 +1,16 @@ // 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 / Modem Host Software -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* -* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL -* -*/ +/* + * 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. + * + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * + */ #include "ActivityLog.h" #include "common/network/BaseNetwork.h" #include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork() -#if defined(_WIN32) -#include "common/Clock.h" -#else -#include -#endif // defined(_WIN32) - #if defined(CATCH2_TEST_COMPILATION) #include #endif @@ -29,7 +20,6 @@ #include #include #include -#include // --------------------------------------------------------------------------- // Constants @@ -43,12 +33,12 @@ const uint32_t ACT_LOG_BUFFER_LEN = 501U; // Global Variables // --------------------------------------------------------------------------- -static std::string m_actFilePath; -static std::string m_actFileRoot; +static std::string g_actFilePath; +static std::string g_actFileRoot; -static FILE* m_actFpLog = nullptr; +static FILE* g_actFpLog = nullptr; -static struct tm m_actTm; +static struct tm g_actTm; // --------------------------------------------------------------------------- // Global Functions @@ -66,22 +56,22 @@ static bool ActivityLogOpen() struct tm* tm = ::localtime(&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) + if (tm->tm_mday == g_actTm.tm_mday && tm->tm_mon == g_actTm.tm_mon && tm->tm_year == g_actTm.tm_year) { + if (g_actFpLog != nullptr) return true; } else { - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } char filename[200U]; - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_actFilePath.c_str(), m_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", g_actFilePath.c_str(), g_actFileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - m_actFpLog = ::fopen(filename, "a+t"); - m_actTm = *tm; + g_actFpLog = ::fopen(filename, "a+t"); + g_actTm = *tm; - return m_actFpLog != nullptr; + return g_actFpLog != nullptr; } /* Initializes the activity log. */ @@ -91,8 +81,8 @@ bool ActivityLogInitialise(const std::string& filePath, const std::string& fileR #if defined(CATCH2_TEST_COMPILATION) return true; #endif - m_actFilePath = filePath; - m_actFileRoot = fileRoot; + g_actFilePath = filePath; + g_actFileRoot = fileRoot; return ::ActivityLogOpen(); } @@ -104,62 +94,34 @@ void ActivityLogFinalise() #if defined(CATCH2_TEST_COMPILATION) return; #endif - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } /* Writes a new entry to the activity log. */ -void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) +void log_internal::ActivityLogInternal(const std::string& log) { #if defined(CATCH2_TEST_COMPILATION) return; #endif - assert(mode != nullptr); - assert(msg != nullptr); - - char buffer[ACT_LOG_BUFFER_LEN]; - time_t now; - ::time(&now); - struct tm* tm = ::localtime(&now); - - struct timeval nowMillis; - ::gettimeofday(&nowMillis, NULL); - - if (strcmp(mode, "") == 0) { - ::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, nowMillis.tv_usec / 1000U); - } - else { - ::sprintf(buffer, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu %s %s ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, mode, (sourceRf) ? "RF" : "Net"); - } - - 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 (LogGetNetwork() != nullptr) { + network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork(); + network->writeActLog(log.c_str()); + } + + ::fprintf(g_actFpLog, "%s\n", log.c_str()); + ::fflush(g_actFpLog); if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { - ::fprintf(stdout, "%s" EOL, buffer); + ::fprintf(stdout, "%s" EOL, log.c_str()); ::fflush(stdout); } } diff --git a/src/host/ActivityLog.h b/src/host/ActivityLog.h index 0a4c4a0c9..d3a12652c 100644 --- a/src/host/ActivityLog.h +++ b/src/host/ActivityLog.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -18,12 +18,29 @@ #include "Defines.h" +#if defined(_WIN32) +#include "common/Clock.h" +#else +#include +#endif // defined(_WIN32) + #include +#include // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- +namespace log_internal +{ + /** + * @brief Writes a new entry to the diagnostics log. + * @param level Log level for entry. + * @param log Fully formatted log message. + */ + extern HOST_SW_API void ActivityLogInternal(const std::string& log); +} // namespace log_internal + /** * @brief Initializes the activity log. * @param filePath File path for the log file. @@ -34,14 +51,53 @@ extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const * @brief Finalizes the activity log. */ extern HOST_SW_API void ActivityLogFinalise(); + /** - * @brief Writes a new entry to the activity log. + * @brief Writes a new entry to the diagnostics log. * @param mode Activity mode. * @param sourceRf Flag indicating whether or not the activity entry came from RF. - * @param msg String format. + * @param fmt String format. * - * This is a variable argument function. + * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. */ -extern HOST_SW_API void ActivityLog(const char* mode, const bool sourceRf, const char* msg, ...); +template +HOST_SW_API void ActivityLog(const char* mode, const bool sourceRf, const std::string& fmt, Args... args) +{ + using namespace log_internal; + + int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + + int prefixLen = 0; + char prefixBuf[256]; + + time_t now; + ::time(&now); + struct tm* tm = ::localtime(&now); + + struct timeval nowMillis; + ::gettimeofday(&nowMillis, NULL); + + if (strcmp(mode, "") == 0) { + prefixLen = ::sprintf(prefixBuf, "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, nowMillis.tv_usec / 1000U); + } + else { + prefixLen = ::sprintf(prefixBuf, "A: %04d-%02d-%02d %02d:%02d:%02d.%03lu %s %s ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nowMillis.tv_usec / 1000U, mode, (sourceRf) ? "RF" : "Net"); + } + + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + + std::snprintf(buf.get(), size, fmt.c_str(), args ...); + + std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen); + std::string msg = std::string(buf.get(), buf.get() + size - 1); + + ActivityLogInternal(std::string(prefix + msg)); +} #endif // __ACTIVITY_LOG_H__ diff --git a/src/host/CMakeLists.txt b/src/host/CMakeLists.txt index aba8f13eb..7b94bba7e 100644 --- a/src/host/CMakeLists.txt +++ b/src/host/CMakeLists.txt @@ -50,6 +50,8 @@ file(GLOB dvmhost_SRC "src/host/modem/port/specialized/*.cpp" "src/host/network/*.h" "src/host/network/*.cpp" + "src/host/restapi/*.h" + "src/host/restapi/*.cpp" "src/host/win32/*.h" ) diff --git a/src/host/Defines.h b/src/host/Defines.h index 33ecb9ddb..6190291ec 100644 --- a/src/host/Defines.h +++ b/src/host/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 3c4bb617b..d1bec4514 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ @@ -332,7 +329,7 @@ bool Host::readParams() m_p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("F7E").c_str(), NULL, 16); m_p25NAC = p25::P25Utils::nac(m_p25NAC); - uint32_t p25TxNAC = (uint32_t)::strtoul(rfssConfig["txNAC"].as("293").c_str(), NULL, 16); + uint32_t p25TxNAC = (uint32_t)::strtoul(rfssConfig["txNAC"].as("F7E").c_str(), NULL, 16); if (p25TxNAC == m_p25NAC) { LogWarning(LOG_HOST, "Only use txNAC when split NAC operations are needed. nac and txNAC should not be the same!"); } @@ -511,7 +508,6 @@ bool Host::createModem() yaml::Node dfsiParams = modemConf["dfsi"]; bool rtrt = dfsiParams["rtrt"].as(true); - bool diu = dfsiParams["diu"].as(true); uint16_t jitter = dfsiParams["jitter"].as(200U); uint16_t dfsiCallTimeout = dfsiParams["callTimeout"].as(200U); bool useFSCForUDP = dfsiParams["fsc"].as(false); @@ -632,7 +628,6 @@ bool Host::createModem() if (modemMode == MODEM_MODE_DFSI) { m_isModemDFSI = true; LogInfo(" DFSI RT/RT: %s", rtrt ? "yes" : "no"); - LogInfo(" DFSI DIU Flag: %s", diu ? "yes" : "no"); LogInfo(" DFSI Jitter Size: %u ms", jitter); if (g_remoteModemMode) { LogInfo(" DFSI Use FSC: %s", useFSCForUDP ? "yes" : "no"); @@ -719,7 +714,7 @@ bool Host::createModem() } if (m_isModemDFSI) { - m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, m_p25QueueSizeBytes, rtrt, diu, jitter, + m_modem = new ModemV24(modemPort, m_duplex, m_p25QueueSizeBytes, m_p25QueueSizeBytes, rtrt, jitter, dumpModemStatus, trace, debug); ((ModemV24*)m_modem)->setCallTimeout(dfsiCallTimeout); ((ModemV24*)m_modem)->setTIAFormat(dfsiTIAMode); diff --git a/src/host/Host.DMR.cpp b/src/host/Host.DMR.cpp index 945affe25..da87c7844 100644 --- a/src/host/Host.DMR.cpp +++ b/src/host/Host.DMR.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL * */ @@ -57,7 +54,7 @@ void* Host::threadDMRReader1(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -165,7 +162,7 @@ void* Host::threadDMRReader1(void* arg) if (host->m_dmr->getRFState(1U) == RS_RF_REJECTED) { host->m_dmr1RejectTimer.clock(ms); if (host->m_dmr1RejectTimer.hasExpired()) { - LogMessage(LOG_HOST, "DMR, slot 1 reset from previous call reject, frames = %u", host->m_dmr1RejCnt); + LogInfoEx(LOG_HOST, "DMR, slot 1 reset from previous call reject, frames = %u", host->m_dmr1RejCnt); host->m_dmr1RejectTimer.stop(); host->m_dmr->clearRFReject(1U); host->m_dmr1RejCnt = 0U; @@ -182,7 +179,7 @@ void* Host::threadDMRReader1(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -213,7 +210,7 @@ void* Host::threadDMRWriter1(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -311,7 +308,7 @@ void* Host::threadDMRWriter1(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -342,7 +339,7 @@ void* Host::threadDMRReader2(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -449,7 +446,7 @@ void* Host::threadDMRReader2(void* arg) if (host->m_dmr->getRFState(2U) == RS_RF_REJECTED) { host->m_dmr2RejectTimer.clock(ms); if (host->m_dmr2RejectTimer.hasExpired()) { - LogMessage(LOG_HOST, "DMR, slot 2 reset from previous in-call reject, frames = %u", host->m_dmr2RejCnt); + LogInfoEx(LOG_HOST, "DMR, slot 2 reset from previous in-call reject, frames = %u", host->m_dmr2RejCnt); host->m_dmr2RejectTimer.stop(); host->m_dmr->clearRFReject(2U); host->m_dmr2RejCnt = 0U; @@ -466,7 +463,7 @@ void* Host::threadDMRReader2(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -497,7 +494,7 @@ void* Host::threadDMRWriter2(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -595,7 +592,7 @@ void* Host::threadDMRWriter2(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/host/Host.NXDN.cpp b/src/host/Host.NXDN.cpp index 29681fe20..d478bd5fb 100644 --- a/src/host/Host.NXDN.cpp +++ b/src/host/Host.NXDN.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL * */ @@ -44,7 +41,7 @@ void* Host::threadNXDNReader(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -114,7 +111,7 @@ void* Host::threadNXDNReader(void* arg) if (host->m_nxdn->getRFState() == RS_RF_REJECTED) { host->m_nxdnRejectTimer.clock(ms); if (host->m_nxdnRejectTimer.hasExpired()) { - LogMessage(LOG_HOST, "NXDN, reset from previous call reject, frames = %u", host->m_nxdnRejCnt); + LogInfoEx(LOG_HOST, "NXDN, reset from previous call reject, frames = %u", host->m_nxdnRejCnt); host->m_nxdnRejectTimer.stop(); host->m_nxdn->clearRFReject(); host->m_nxdnRejCnt = 0U; @@ -131,7 +128,7 @@ void* Host::threadNXDNReader(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -162,7 +159,7 @@ void* Host::threadNXDNWriter(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -247,7 +244,7 @@ void* Host::threadNXDNWriter(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/host/Host.P25.cpp b/src/host/Host.P25.cpp index 45f9980ec..eb0c7ff94 100644 --- a/src/host/Host.P25.cpp +++ b/src/host/Host.P25.cpp @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL * */ @@ -45,7 +42,7 @@ void* Host::threadP25Reader(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -156,7 +153,7 @@ void* Host::threadP25Reader(void* arg) if (host->m_p25->getRFState() == RS_RF_REJECTED) { host->m_p25RejectTimer.clock(ms); if (host->m_p25RejectTimer.hasExpired()) { - LogMessage(LOG_HOST, "P25, reset from previous call reject, frames = %u", host->m_p25RejCnt); + LogInfoEx(LOG_HOST, "P25, reset from previous call reject, frames = %u", host->m_p25RejCnt); host->m_p25RejectTimer.stop(); host->m_p25->clearRFReject(); host->m_p25RejCnt = 0U; @@ -173,7 +170,7 @@ void* Host::threadP25Reader(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -204,7 +201,7 @@ void* Host::threadP25Writer(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -332,7 +329,7 @@ void* Host::threadP25Writer(void* arg) } } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 761493f11..1837b6cec 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -997,7 +997,7 @@ int Host::run() m_nxdnBcastDurationTimer.stop(); } - LogMessage(LOG_HOST, "CW, start transmitting"); + LogInfoEx(LOG_HOST, "CW, start transmitting"); m_isTxCW = true; std::lock_guard lock(m_clockingMutex); @@ -1021,7 +1021,7 @@ int Host::run() m_modem->clock(ms); if (!first && !m_modem->hasTX()) { - LogMessage(LOG_HOST, "CW, finished transmitting"); + LogInfoEx(LOG_HOST, "CW, finished transmitting"); break; } @@ -1073,10 +1073,10 @@ int Host::run() g_fireDMRBeacon = false; if (m_dmrTSCCData) { - LogMessage(LOG_HOST, "DMR, start CC broadcast"); + LogInfoEx(LOG_HOST, "DMR, start CC broadcast"); } else { - LogMessage(LOG_HOST, "DMR, roaming beacon burst"); + LogInfoEx(LOG_HOST, "DMR, roaming beacon burst"); } dmrBeaconIntervalTimer.start(); m_dmrBeaconDurationTimer.start(); @@ -1134,7 +1134,7 @@ int Host::run() // hide this message for continuous CC -- otherwise display every time we process if (!m_p25CtrlChannel) { - LogMessage(LOG_HOST, "P25, start CC broadcast"); + LogInfoEx(LOG_HOST, "P25, start CC broadcast"); } g_fireP25Control = false; @@ -1188,7 +1188,7 @@ int Host::run() // hide this message for continuous CC -- otherwise display every time we process if (!m_nxdnCtrlChannel) { - LogMessage(LOG_HOST, "NXDN, start CC broadcast"); + LogInfoEx(LOG_HOST, "NXDN, start CC broadcast"); } g_fireNXDNControl = false; @@ -1519,7 +1519,7 @@ bool Host::rmtPortModemOpen(Modem* modem) if (!ret) return false; - LogMessage(LOG_MODEM, "Modem Ready [Remote Mode]"); + LogInfoEx(LOG_MODEM, "Modem Ready [Remote Mode]"); // handled modem open return true; @@ -1756,7 +1756,7 @@ void* Host::threadRPC(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -1783,7 +1783,7 @@ void* Host::threadRPC(void* arg) Thread::sleep(m_idleTickDelay); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -1814,7 +1814,7 @@ void* Host::threadModem(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -1843,7 +1843,7 @@ void* Host::threadModem(void* arg) Thread::sleep(m_idleTickDelay); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -1874,7 +1874,7 @@ void* Host::threadWatchdog(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -2058,7 +2058,7 @@ void* Host::threadWatchdog(void* arg) Thread::sleep(m_idleTickDelay); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -2089,7 +2089,7 @@ void* Host::threadSiteData(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -2127,7 +2127,7 @@ void* Host::threadSiteData(void* arg) Thread::sleep(m_idleTickDelay); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -2158,7 +2158,7 @@ void* Host::threadPresence(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -2219,12 +2219,12 @@ void* Host::threadPresence(void* arg) host->rfCh()->setRFChData(channelNo, voiceCh); host->m_voiceChPeerId[channelNo] = peerId; - LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo); + LogInfoEx(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo); LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port()); g_fireCCVCNotification = true; // announce this registration immediately to the FNE } else { - LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo); + LogInfoEx(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo); g_RPC->defaultResponse(reply, "registration rejected", network::NetRPC::BAD_REQUEST); } }); @@ -2248,7 +2248,7 @@ void* Host::threadPresence(void* arg) if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr && !host->m_dmrCtrlChannel && !host->m_p25CtrlChannel && !host->m_nxdnCtrlChannel) { if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) { - LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); + LogInfoEx(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); hasInitialRegistered = true; std::string localAddress = network::udp::Socket::getLocalAddress(); @@ -2279,7 +2279,7 @@ void* Host::threadPresence(void* arg) } } else - ::LogMessage(LOG_HOST, "CC %s:%u, VC registered, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); + ::LogInfoEx(LOG_HOST, "CC %s:%u, VC registered, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); }, host->m_controlChData.address(), host->m_controlChData.port()); presenceNotifyTimer.start(); @@ -2291,7 +2291,7 @@ void* Host::threadPresence(void* arg) if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || g_fireCCVCNotification) { g_fireCCVCNotification = false; if (host->m_network != nullptr && host->m_voiceChPeerId.size() > 0) { - LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", host->m_network->getPeerId()); + LogInfoEx(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", host->m_network->getPeerId()); std::vector peers; for (auto it : host->m_voiceChPeerId) { @@ -2312,7 +2312,7 @@ void* Host::threadPresence(void* arg) Thread::sleep(m_idleTickDelay); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/host/Host.h b/src/host/Host.h index 5a65c426f..774847ce0 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -32,15 +32,15 @@ #include "common/lookups/IdenTableLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" -#include "common/network/json/json.h" +#include "common/json/json.h" #include "common/network/Network.h" #include "common/network/NetRPC.h" #include "common/yaml/Yaml.h" #include "dmr/Control.h" #include "p25/Control.h" #include "nxdn/Control.h" -#include "network/RESTAPI.h" #include "network/RPCDefines.h" +#include "restapi/RESTAPI.h" #include "modem/Modem.h" #include "modem/ModemV24.h" diff --git a/src/host/HostMain.cpp b/src/host/HostMain.cpp index 6047517e7..5cc5714ce 100644 --- a/src/host/HostMain.cpp +++ b/src/host/HostMain.cpp @@ -290,6 +290,8 @@ int main(int argc, char** argv) } } + log_stacktrace::SignalHandling sh(g_foreground); + ::signal(SIGINT, sigHandler); ::signal(SIGTERM, sigHandler); #if !defined(_WIN32) diff --git a/src/host/HostMain.h b/src/host/HostMain.h index 00b1bbe2e..83f12dc15 100644 --- a/src/host/HostMain.h +++ b/src/host/HostMain.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2020,2022 Bryan Biedenkapp, N2PLL * diff --git a/src/host/calibrate/HostCal.cpp b/src/host/calibrate/HostCal.cpp index 0aa658ca5..1bbb3f90c 100644 --- a/src/host/calibrate/HostCal.cpp +++ b/src/host/calibrate/HostCal.cpp @@ -175,7 +175,7 @@ int HostCal::run(int argc, char **argv) { if (!m_isHotspot) { m_modem->m_txInvert = !m_modem->m_txInvert; - LogMessage(LOG_CAL, " - TX Invert: %s", m_modem->m_txInvert ? "On" : "Off"); + LogInfoEx(LOG_CAL, " - TX Invert: %s", m_modem->m_txInvert ? "On" : "Off"); writeConfig(); } } @@ -184,7 +184,7 @@ int HostCal::run(int argc, char **argv) { if (!m_isHotspot) { m_modem->m_rxInvert = !m_modem->m_rxInvert; - LogMessage(LOG_CAL, " - RX Invert: %s", m_modem->m_rxInvert ? "On" : "Off"); + LogInfoEx(LOG_CAL, " - RX Invert: %s", m_modem->m_rxInvert ? "On" : "Off"); writeConfig(); } } @@ -193,7 +193,7 @@ int HostCal::run(int argc, char **argv) { if (!m_isHotspot) { m_modem->m_pttInvert = !m_modem->m_pttInvert; - LogMessage(LOG_CAL, " - PTT Invert: %s", m_modem->m_pttInvert ? "On" : "Off"); + LogInfoEx(LOG_CAL, " - PTT Invert: %s", m_modem->m_pttInvert ? "On" : "Off"); writeConfig(); } } @@ -202,7 +202,7 @@ int HostCal::run(int argc, char **argv) { if (!m_isHotspot) { m_modem->m_dcBlocker = !m_modem->m_dcBlocker; - LogMessage(LOG_CAL, " - DC Blocker: %s", m_modem->m_dcBlocker ? "On" : "Off"); + LogInfoEx(LOG_CAL, " - DC Blocker: %s", m_modem->m_dcBlocker ? "On" : "Off"); writeConfig(); } } @@ -210,7 +210,7 @@ int HostCal::run(int argc, char **argv) case 'D': { m_debug = !m_debug; - LogMessage(LOG_CAL, " - Modem Debug: %s", m_debug ? "On" : "Off"); + LogInfoEx(LOG_CAL, " - Modem Debug: %s", m_debug ? "On" : "Off"); writeConfig(); } break; @@ -486,7 +486,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -502,7 +502,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -518,7 +518,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -534,7 +534,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -550,7 +550,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -566,7 +566,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -584,7 +584,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } else { @@ -610,7 +610,7 @@ int HostCal::run(int argc, char **argv) m_p25Enabled = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -633,7 +633,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -651,7 +651,7 @@ int HostCal::run(int argc, char **argv) m_p25TduTest = false; m_nxdnEnabled = true; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } else { @@ -671,7 +671,7 @@ int HostCal::run(int argc, char **argv) m_p25Rx1K = false; m_p25TduTest = false; - LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_modeStr.c_str()); writeConfig(); } break; @@ -757,65 +757,65 @@ int HostCal::run(int argc, char **argv) void HostCal::displayHelp() { - LogMessage(LOG_CAL, "General Commands:"); - LogMessage(LOG_CAL, " Toggle transmit"); - LogMessage(LOG_CAL, " ` Display current settings and operation mode"); - LogMessage(LOG_CAL, " ! Restart into Bootloader Mode (wipes configuration area!)"); - LogMessage(LOG_CAL, " V Display version of host"); - LogMessage(LOG_CAL, " v Display version of firmware"); - LogMessage(LOG_CAL, " H/h Display help"); - LogMessage(LOG_CAL, " S/s Save calibration settings to configuration file"); + LogInfoEx(LOG_CAL, "General Commands:"); + LogInfoEx(LOG_CAL, " Toggle transmit"); + LogInfoEx(LOG_CAL, " ` Display current settings and operation mode"); + LogInfoEx(LOG_CAL, " ! Restart into Bootloader Mode (wipes configuration area!)"); + LogInfoEx(LOG_CAL, " V Display version of host"); + LogInfoEx(LOG_CAL, " v Display version of firmware"); + LogInfoEx(LOG_CAL, " H/h Display help"); + LogInfoEx(LOG_CAL, " S/s Save calibration settings to configuration file"); if (!m_modem->m_flashDisabled) { - LogMessage(LOG_CAL, " U Read modem configuration area and reset local configuration"); + LogInfoEx(LOG_CAL, " U Read modem configuration area and reset local configuration"); } - LogMessage(LOG_CAL, " ) Swap Duplex Flag (depending on mode this will enable/disable duplex)"); - LogMessage(LOG_CAL, " Q/q Quit"); - LogMessage(LOG_CAL, "Level Adjustment Commands:"); + LogInfoEx(LOG_CAL, " ) Swap Duplex Flag (depending on mode this will enable/disable duplex)"); + LogInfoEx(LOG_CAL, " Q/q Quit"); + LogInfoEx(LOG_CAL, "Level Adjustment Commands:"); if (!m_isHotspot) { - LogMessage(LOG_CAL, " I Toggle transmit inversion"); - LogMessage(LOG_CAL, " i Toggle receive inversion"); - LogMessage(LOG_CAL, " p Toggle PTT inversion"); - LogMessage(LOG_CAL, " d Toggle DC blocker"); + LogInfoEx(LOG_CAL, " I Toggle transmit inversion"); + LogInfoEx(LOG_CAL, " i Toggle receive inversion"); + LogInfoEx(LOG_CAL, " p Toggle PTT inversion"); + LogInfoEx(LOG_CAL, " d Toggle DC blocker"); } if (!m_isHotspot) { - LogMessage(LOG_CAL, " R/r Increase/Decrease receive level"); - LogMessage(LOG_CAL, " T/t Increase/Decrease transmit level"); + LogInfoEx(LOG_CAL, " R/r Increase/Decrease receive level"); + LogInfoEx(LOG_CAL, " T/t Increase/Decrease transmit level"); } else { - LogMessage(LOG_CAL, " T/t Increase/Decrease deviation level"); + LogInfoEx(LOG_CAL, " T/t Increase/Decrease deviation level"); } if (!m_isHotspot) { - LogMessage(LOG_CAL, " C/c Increase/Decrease RX DC offset level"); - LogMessage(LOG_CAL, " O/o Increase/Decrease TX DC offset level"); + LogInfoEx(LOG_CAL, " C/c Increase/Decrease RX DC offset level"); + LogInfoEx(LOG_CAL, " O/o Increase/Decrease TX DC offset level"); } - LogMessage(LOG_CAL, " X Set FDMA Preambles"); - LogMessage(LOG_CAL, " W Set DMR Rx Delay"); + LogInfoEx(LOG_CAL, " X Set FDMA Preambles"); + LogInfoEx(LOG_CAL, " W Set DMR Rx Delay"); if (!m_isHotspot) { - LogMessage(LOG_CAL, " w Set P25 Correlation Count"); + LogInfoEx(LOG_CAL, " w Set P25 Correlation Count"); } if (m_isHotspot) { - LogMessage(LOG_CAL, " F Set Rx Frequency Adjustment"); - LogMessage(LOG_CAL, " f Set Tx Frequency Adjustment"); + LogInfoEx(LOG_CAL, " F Set Rx Frequency Adjustment"); + LogInfoEx(LOG_CAL, " f Set Tx Frequency Adjustment"); } if (!m_isHotspot) { - LogMessage(LOG_CAL, " 1/2 Increase/Decrease receive coarse level"); - LogMessage(LOG_CAL, " 3/4 Increase/Decrease receive fine level"); - LogMessage(LOG_CAL, " 5/6 Increase/Decrease transmit coarse level"); - LogMessage(LOG_CAL, " 9/0 Increase/Decrease RSSI coarse level"); + LogInfoEx(LOG_CAL, " 1/2 Increase/Decrease receive coarse level"); + LogInfoEx(LOG_CAL, " 3/4 Increase/Decrease receive fine level"); + LogInfoEx(LOG_CAL, " 5/6 Increase/Decrease transmit coarse level"); + LogInfoEx(LOG_CAL, " 9/0 Increase/Decrease RSSI coarse level"); } - LogMessage(LOG_CAL, "Mode Commands:"); - LogMessage(LOG_CAL, " Z %s", DMR_CAL_STR); - LogMessage(LOG_CAL, " z %s", P25_CAL_STR); - LogMessage(LOG_CAL, " L %s", DMR_LF_CAL_STR); - LogMessage(LOG_CAL, " M %s", DMR_CAL_1K_STR); - LogMessage(LOG_CAL, " m %s", DMR_DMO_CAL_1K_STR); - LogMessage(LOG_CAL, " P %s", P25_CAL_1K_STR); - LogMessage(LOG_CAL, " N %s", NXDN_CAL_1K_STR); - LogMessage(LOG_CAL, " B %s", DMR_FEC_STR); - LogMessage(LOG_CAL, " J %s", DMR_FEC_1K_STR); - LogMessage(LOG_CAL, " b %s", P25_FEC_STR); - LogMessage(LOG_CAL, " j %s", P25_FEC_1K_STR); - LogMessage(LOG_CAL, " n %s", NXDN_FEC_STR); - LogMessage(LOG_CAL, " x %s", RSSI_CAL_STR); + LogInfoEx(LOG_CAL, "Mode Commands:"); + LogInfoEx(LOG_CAL, " Z %s", DMR_CAL_STR); + LogInfoEx(LOG_CAL, " z %s", P25_CAL_STR); + LogInfoEx(LOG_CAL, " L %s", DMR_LF_CAL_STR); + LogInfoEx(LOG_CAL, " M %s", DMR_CAL_1K_STR); + LogInfoEx(LOG_CAL, " m %s", DMR_DMO_CAL_1K_STR); + LogInfoEx(LOG_CAL, " P %s", P25_CAL_1K_STR); + LogInfoEx(LOG_CAL, " N %s", NXDN_CAL_1K_STR); + LogInfoEx(LOG_CAL, " B %s", DMR_FEC_STR); + LogInfoEx(LOG_CAL, " J %s", DMR_FEC_1K_STR); + LogInfoEx(LOG_CAL, " b %s", P25_FEC_STR); + LogInfoEx(LOG_CAL, " j %s", P25_FEC_1K_STR); + LogInfoEx(LOG_CAL, " n %s", NXDN_FEC_STR); + LogInfoEx(LOG_CAL, " x %s", RSSI_CAL_STR); } /* Helper to change the Rx level. */ @@ -829,7 +829,7 @@ bool HostCal::setTXLevel(int incr) if (m_modem->m_cwIdTXLevel > 100.0F) m_modem->m_cwIdTXLevel = 100.0F; - LogMessage(LOG_CAL, " - TX Level: %.1f%%", m_modem->m_cwIdTXLevel); + LogInfoEx(LOG_CAL, " - TX Level: %.1f%%", m_modem->m_cwIdTXLevel); return writeConfig(); } @@ -840,7 +840,7 @@ bool HostCal::setTXLevel(int incr) if (m_modem->m_cwIdTXLevel < 0.0F) m_modem->m_cwIdTXLevel = 0.0F; - LogMessage(LOG_CAL, " - TX Level: %.1f%%", m_modem->m_cwIdTXLevel); + LogInfoEx(LOG_CAL, " - TX Level: %.1f%%", m_modem->m_cwIdTXLevel); return writeConfig(); } @@ -858,7 +858,7 @@ bool HostCal::setRXLevel(int incr) if (m_modem->m_rxLevel > 100.0F) m_modem->m_rxLevel = 100.0F; - LogMessage(LOG_CAL, " - RX Level: %.1f%%", m_modem->m_rxLevel); + LogInfoEx(LOG_CAL, " - RX Level: %.1f%%", m_modem->m_rxLevel); return writeConfig(); } @@ -869,7 +869,7 @@ bool HostCal::setRXLevel(int incr) if (m_modem->m_rxLevel < 0.0F) m_modem->m_rxLevel = 0.0F; - LogMessage(LOG_CAL, " - RX Level: %.1f%%", m_modem->m_rxLevel); + LogInfoEx(LOG_CAL, " - RX Level: %.1f%%", m_modem->m_rxLevel); return writeConfig(); } @@ -882,13 +882,13 @@ bool HostCal::setTXDCOffset(int incr) { if (incr > 0 && m_modem->m_txDCOffset < 127) { m_modem->m_txDCOffset++; - LogMessage(LOG_CAL, " - TX DC Offset: %d", m_modem->m_txDCOffset); + LogInfoEx(LOG_CAL, " - TX DC Offset: %d", m_modem->m_txDCOffset); return writeConfig(); } if (incr < 0 && m_modem->m_txDCOffset > -127) { m_modem->m_txDCOffset--; - LogMessage(LOG_CAL, " - TX DC Offset: %d", m_modem->m_txDCOffset); + LogInfoEx(LOG_CAL, " - TX DC Offset: %d", m_modem->m_txDCOffset); return writeConfig(); } @@ -901,13 +901,13 @@ bool HostCal::setRXDCOffset(int incr) { if (incr > 0 && m_modem->m_rxDCOffset < 127) { m_modem->m_rxDCOffset++; - LogMessage(LOG_CAL, " - RX DC Offset: %d", m_modem->m_rxDCOffset); + LogInfoEx(LOG_CAL, " - RX DC Offset: %d", m_modem->m_rxDCOffset); return writeConfig(); } if (incr < 0 && m_modem->m_rxDCOffset > -127) { m_modem->m_rxDCOffset--; - LogMessage(LOG_CAL, " - RX DC Offset: %d", m_modem->m_rxDCOffset); + LogInfoEx(LOG_CAL, " - RX DC Offset: %d", m_modem->m_rxDCOffset); return writeConfig(); } @@ -923,7 +923,7 @@ bool HostCal::setRXCoarseLevel(int incr) m_modem->m_rxCoarsePot += 1U; } - LogMessage(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); + LogInfoEx(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); return writeConfig(); } @@ -932,7 +932,7 @@ bool HostCal::setRXCoarseLevel(int incr) m_modem->m_rxCoarsePot -= 1U; } - LogMessage(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); + LogInfoEx(LOG_CAL, " - RX Coarse Level: %u", m_modem->m_rxCoarsePot); return writeConfig(); } @@ -948,7 +948,7 @@ bool HostCal::setRXFineLevel(int incr) m_modem->m_rxFinePot += 1U; } - LogMessage(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); + LogInfoEx(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); return writeConfig(); } @@ -957,7 +957,7 @@ bool HostCal::setRXFineLevel(int incr) m_modem->m_rxFinePot -= 1U; } - LogMessage(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); + LogInfoEx(LOG_CAL, " - RX Fine Level: %u", m_modem->m_rxFinePot); return writeConfig(); } @@ -973,7 +973,7 @@ bool HostCal::setTXCoarseLevel(int incr) m_modem->m_txCoarsePot += 1U; } - LogMessage(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); + LogInfoEx(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); return writeConfig(); } @@ -982,7 +982,7 @@ bool HostCal::setTXCoarseLevel(int incr) m_modem->m_txCoarsePot -= 1U; } - LogMessage(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); + LogInfoEx(LOG_CAL, " - TX Coarse Level: %u", m_modem->m_txCoarsePot); return writeConfig(); } @@ -998,7 +998,7 @@ bool HostCal::setRSSICoarseLevel(int incr) m_modem->m_rssiCoarsePot += 1U; } - LogMessage(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); + LogInfoEx(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); return writeConfig(); } @@ -1007,7 +1007,7 @@ bool HostCal::setRSSICoarseLevel(int incr) m_modem->m_rssiCoarsePot -= 1U; } - LogMessage(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); + LogInfoEx(LOG_CAL, " - RSSI Coarse Level: %u", m_modem->m_rssiCoarsePot); return writeConfig(); } @@ -1027,61 +1027,61 @@ void HostCal::printStatus() std::string modemPort = uartConfig["port"].as(); uint32_t portSpeed = uartConfig["speed"].as(115200U); - LogMessage(LOG_CAL, " - Operating Mode: %s, Port Type: %s, Modem Port: %s, Port Speed: %u, Proto Ver: %u", m_modeStr.c_str(), type.c_str(), modemPort.c_str(), portSpeed, m_modem->getVersion()); + LogInfoEx(LOG_CAL, " - Operating Mode: %s, Port Type: %s, Modem Port: %s, Port Speed: %u, Proto Ver: %u", m_modeStr.c_str(), type.c_str(), modemPort.c_str(), portSpeed, m_modem->getVersion()); } { if (!m_isHotspot) { - LogMessage(LOG_CAL, " - PTT Invert: %s, RX Invert: %s, TX Invert: %s, DC Blocker: %s", + LogInfoEx(LOG_CAL, " - PTT Invert: %s, RX Invert: %s, TX Invert: %s, DC Blocker: %s", m_modem->m_pttInvert ? "yes" : "no", m_modem->m_rxInvert ? "yes" : "no", m_modem->m_txInvert ? "yes" : "no", m_modem->m_dcBlocker ? "yes" : "no"); } - LogMessage(LOG_CAL, " - RX Level: %.1f%%, TX Level: %.1f%%, TX DC Offset: %d, RX DC Offset: %d", + LogInfoEx(LOG_CAL, " - RX Level: %.1f%%, TX Level: %.1f%%, TX DC Offset: %d, RX DC Offset: %d", m_modem->m_rxLevel, m_modem->m_cwIdTXLevel, m_modem->m_txDCOffset, m_modem->m_rxDCOffset); if (!m_isHotspot) { - LogMessage(LOG_CAL, " - RX Coarse Level: %u, RX Fine Level: %u, TX Coarse Level: %u, RSSI Coarse Level: %u", + LogInfoEx(LOG_CAL, " - RX Coarse Level: %u, RX Fine Level: %u, TX Coarse Level: %u, RSSI Coarse Level: %u", m_modem->m_rxCoarsePot, m_modem->m_rxFinePot, m_modem->m_txCoarsePot, m_modem->m_rssiCoarsePot); - LogMessage(LOG_CAL, " - DMR Symbol +/- 3 Level Adj.: %d, DMR Symbol +/- 1 Level Adj.: %d, P25 Symbol +/- 3 Level Adj.: %d, P25 Symbol +/- 1 Level Adj.: %d", + LogInfoEx(LOG_CAL, " - DMR Symbol +/- 3 Level Adj.: %d, DMR Symbol +/- 1 Level Adj.: %d, P25 Symbol +/- 3 Level Adj.: %d, P25 Symbol +/- 1 Level Adj.: %d", m_modem->m_dmrSymLevel3Adj, m_modem->m_dmrSymLevel1Adj, m_modem->m_p25SymLevel3Adj, m_modem->m_p25SymLevel1Adj); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { - LogMessage(LOG_CAL, " - NXDN Symbol +/- 3 Level Adj.: %d, NXDN Symbol +/- 1 Level Adj.: %d", + LogInfoEx(LOG_CAL, " - NXDN Symbol +/- 3 Level Adj.: %d, NXDN Symbol +/- 1 Level Adj.: %d", m_modem->m_nxdnSymLevel3Adj, m_modem->m_nxdnSymLevel1Adj); } } if (m_isHotspot) { - LogMessage(LOG_CAL, " - DMR Disc. BW: %d, P25 Disc. BW: %d, DMR Post Demod BW: %d, P25 Post Demod BW: %d", + LogInfoEx(LOG_CAL, " - DMR Disc. BW: %d, P25 Disc. BW: %d, DMR Post Demod BW: %d, P25 Post Demod BW: %d", m_modem->m_dmrDiscBWAdj, m_modem->m_p25DiscBWAdj, m_modem->m_dmrPostBWAdj, m_modem->m_p25PostBWAdj); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { - LogMessage(LOG_CAL, " - NXDN Disc. BW: %d, NXDN Post Demod BW: %d", + LogInfoEx(LOG_CAL, " - NXDN Disc. BW: %d, NXDN Post Demod BW: %d", m_modem->m_nxdnDiscBWAdj, m_modem->m_nxdnPostBWAdj); - LogMessage(LOG_CAL, " - AFC Enabled: %u, AFC KI: %u, AFC KP: %u, AFC Range: %u", + LogInfoEx(LOG_CAL, " - AFC Enabled: %u, AFC KI: %u, AFC KP: %u, AFC Range: %u", m_modem->m_afcEnable, m_modem->m_afcKI, m_modem->m_afcKP, m_modem->m_afcRange); } switch (m_modem->m_adfGainMode) { case ADF_GAIN_AUTO_LIN: - LogMessage(LOG_CAL, " - ADF7021 Gain Mode: Auto High Linearity"); + LogInfoEx(LOG_CAL, " - ADF7021 Gain Mode: Auto High Linearity"); break; case ADF_GAIN_LOW: - LogMessage(LOG_CAL, " - ADF7021 Gain Mode: Low"); + LogInfoEx(LOG_CAL, " - ADF7021 Gain Mode: Low"); break; case ADF_GAIN_HIGH: - LogMessage(LOG_CAL, " - ADF7021 Gain Mode: High"); + LogInfoEx(LOG_CAL, " - ADF7021 Gain Mode: High"); break; case ADF_GAIN_AUTO: default: - LogMessage(LOG_CAL, " - ADF7021 Gain Mode: Auto"); + LogInfoEx(LOG_CAL, " - ADF7021 Gain Mode: Auto"); break; } } - LogMessage(LOG_CAL, " - FDMA Preambles: %u (%.1fms), DMR Rx Delay: %u (%.1fms), P25 Corr. Count: %u (%.1fms)", m_modem->m_fdmaPreamble, float(m_modem->m_fdmaPreamble) * 0.2222F, m_modem->m_dmrRxDelay, float(m_modem->m_dmrRxDelay) * 0.0416666F, + LogInfoEx(LOG_CAL, " - FDMA Preambles: %u (%.1fms), DMR Rx Delay: %u (%.1fms), P25 Corr. Count: %u (%.1fms)", m_modem->m_fdmaPreamble, float(m_modem->m_fdmaPreamble) * 0.2222F, m_modem->m_dmrRxDelay, float(m_modem->m_dmrRxDelay) * 0.0416666F, m_modem->m_p25CorrCount, float(m_modem->m_p25CorrCount) * 0.667F); - LogMessage(LOG_CAL, " - Rx Freq: %uHz, Tx Freq: %uHz, Rx Offset: %dHz, Tx Offset: %dHz", m_modem->m_rxFrequency, m_modem->m_txFrequency, m_modem->m_rxTuning, m_modem->m_txTuning); - LogMessage(LOG_CAL, " - Rx Effective Freq: %uHz, Tx Effective Freq: %uHz", m_rxAdjustedFreq, m_txAdjustedFreq); + LogInfoEx(LOG_CAL, " - Rx Freq: %uHz, Tx Freq: %uHz, Rx Offset: %dHz, Tx Offset: %dHz", m_modem->m_rxFrequency, m_modem->m_txFrequency, m_modem->m_rxTuning, m_modem->m_txTuning); + LogInfoEx(LOG_CAL, " - Rx Effective Freq: %uHz, Tx Effective Freq: %uHz", m_rxAdjustedFreq, m_txAdjustedFreq); } getStatus(); diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index 81c609226..ce07af578 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -94,7 +94,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_supervisor = supervisor; - Slot::m_verifyReg = dmrProtocol["verifyReg"].as(false); + Slot::s_verifyReg = dmrProtocol["verifyReg"].as(false); uint8_t nRandWait = (uint8_t)dmrProtocol["nRandWait"].as(DEFAULT_NRAND_WAIT); if (nRandWait > 15U) @@ -143,7 +143,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_enableTSCC = enableTSCC; yaml::Node rfssConfig = systemConf["config"]; - uint32_t defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + uint32_t defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as(0U); m_slot1->setDefaultNetIdleTG(defaultNetIdleTalkgroup); m_slot2->setDefaultNetIdleTG(defaultNetIdleTalkgroup); @@ -153,8 +153,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_slot2->setNotifyCC(notifyCC); bool disableUnitRegTimeout = dmrProtocol["disableUnitRegTimeout"].as(false); - m_slot1->m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); - m_slot2->m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); + m_slot1->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); + m_slot2->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); /* ** Voice Silence and Frame Loss Thresholds @@ -196,7 +196,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa // set the In-Call Control function callback if (m_network != nullptr) { - m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) { processInCallCtrl(command, dstId, slotNo); }); + m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId, slotNo); }); } /* @@ -220,14 +221,14 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa LogInfo(" TSCC Disable Grant Source ID Check: yes"); } if (m_supervisor) - LogMessage(LOG_DMR, "Host is configured to operate as a DMR TSCC, site controller mode."); + LogInfoEx(LOG_DMR, "Host is configured to operate as a DMR TSCC, site controller mode."); } if (disableNetworkGrant) { LogInfo(" Disable Network Grants: yes"); } if (defaultNetIdleTalkgroup != 0U) { - LogInfo(" Default Network Idle Talkgroup: $%04X", defaultNetIdleTalkgroup); + LogInfo(" Default Network Idle Talkgroup: %u", defaultNetIdleTalkgroup); } LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); @@ -236,7 +237,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa LogInfo(" Silence Threshold: %u (%.1f%%)", silenceThreshold, float(silenceThreshold) / 1.41F); LogInfo(" Frame Loss Threshold: %u", frameLossThreshold); - LogInfo(" Verify Registration: %s", Slot::m_verifyReg ? "yes" : "no"); + LogInfo(" Verify Registration: %s", Slot::s_verifyReg ? "yes" : "no"); LogInfo(" Conventional Network Grant Demand: %s", convNetGrantDemand ? "yes" : "no"); } } @@ -316,7 +317,7 @@ bool Control::processWakeup(const uint8_t* data) } if (m_verbose) { - LogMessage(LOG_RF, "DMR, CSBKO, BSDWNACT, srcId = %u", srcId); + LogInfoEx(LOG_RF, "DMR, CSBKO, BSDWNACT, srcId = %u", srcId); } return true; @@ -467,9 +468,9 @@ dmr::lookups::DMRAffiliationLookup* Control::affiliations() { switch (m_tsccSlotNo) { case 1U: - return m_slot1->m_affiliations; + return m_slot1->s_affiliations; case 2U: - return m_slot2->m_affiliations; + return m_slot2->s_affiliations; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", m_tsccSlotNo); break; @@ -531,7 +532,7 @@ Slot* Control::getTSCCSlot() const void Control::tsccActivateSlot(uint32_t slotNo, uint32_t dstId, uint32_t srcId, bool group, bool voice) { if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, payload activation, srcId = %u, group = %u, dstId = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, payload activation, srcId = %u, group = %u, dstId = %u", slotNo, srcId, group, dstId); } @@ -561,7 +562,7 @@ void Control::tsccActivateSlot(uint32_t slotNo, uint32_t dstId, uint32_t srcId, void Control::tsccClearActivatedSlot(uint32_t slotNo) { if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, payload activation clear", slotNo); + LogInfoEx(LOG_DMR, "DMR Slot %u, payload activation clear", slotNo); } switch (slotNo) { @@ -769,7 +770,7 @@ void Control::processNetwork() if (m_enableTSCC) { Slot* tscc = getTSCCSlot(); if (tscc != nullptr) { - if (!tscc->m_affiliations->isGranted(dstId)) { + if (!tscc->s_affiliations->isGranted(dstId)) { tscc->m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, (flco == FLCO::GROUP) ? true : false, true); } } diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 1a8dedbdc..874bf8ef4 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -35,51 +35,51 @@ using namespace dmr::packet; // Static Class Members // --------------------------------------------------------------------------- -Control* Slot::m_dmr = nullptr; +Control* Slot::s_dmr = nullptr; -bool Slot::m_authoritative = true; +bool Slot::s_authoritative = true; -uint32_t Slot::m_colorCode = 0U; +uint32_t Slot::s_colorCode = 0U; -SiteData Slot::m_siteData = SiteData(); -uint32_t Slot::m_channelNo = 0U; +SiteData Slot::s_siteData = SiteData(); +uint32_t Slot::s_channelNo = 0U; -bool Slot::m_embeddedLCOnly = false; -bool Slot::m_dumpTAData = true; +bool Slot::s_embeddedLCOnly = false; +bool Slot::s_dumpTAData = true; -modem::Modem* Slot::m_modem = nullptr; -network::Network* Slot::m_network = nullptr; +modem::Modem* Slot::s_modem = nullptr; +network::Network* Slot::s_network = nullptr; -bool Slot::m_duplex = true; +bool Slot::s_duplex = true; -::lookups::IdenTableLookup* Slot::m_idenTable = nullptr; -::lookups::RadioIdLookup* Slot::m_ridLookup = nullptr; -::lookups::TalkgroupRulesLookup* Slot::m_tidLookup = nullptr; -dmr::lookups::DMRAffiliationLookup *Slot::m_affiliations = nullptr; -::lookups::VoiceChData Slot::m_controlChData = ::lookups::VoiceChData(); +::lookups::IdenTableLookup* Slot::s_idenTable = nullptr; +::lookups::RadioIdLookup* Slot::s_ridLookup = nullptr; +::lookups::TalkgroupRulesLookup* Slot::s_tidLookup = nullptr; +dmr::lookups::DMRAffiliationLookup *Slot::s_affiliations = nullptr; +::lookups::VoiceChData Slot::s_controlChData = ::lookups::VoiceChData(); -::lookups::IdenTable Slot::m_idenEntry = ::lookups::IdenTable(); +::lookups::IdenTable Slot::s_idenEntry = ::lookups::IdenTable(); -uint32_t Slot::m_hangCount = 3U * 17U; +uint32_t Slot::s_hangCount = 3U * 17U; -::lookups::RSSIInterpolator* Slot::m_rssiMapper = nullptr; +::lookups::RSSIInterpolator* Slot::s_rssiMapper = nullptr; -uint32_t Slot::m_jitterTime = 360U; -uint32_t Slot::m_jitterSlots = 6U; +uint32_t Slot::s_jitterTime = 360U; +uint32_t Slot::s_jitterSlots = 6U; -uint8_t* Slot::m_idle = nullptr; +uint8_t* Slot::s_idle = nullptr; -FLCO::E Slot::m_flco1; -uint8_t Slot::m_id1 = 0U; -Slot::SLCO_ACT_TYPE Slot::m_actType1 = Slot::SLCO_ACT_TYPE::VOICE; -FLCO::E Slot::m_flco2; -uint8_t Slot::m_id2 = 0U; -Slot::SLCO_ACT_TYPE Slot::m_actType2 = Slot::SLCO_ACT_TYPE::VOICE; +FLCO::E Slot::s_flco1; +uint8_t Slot::s_id1 = 0U; +Slot::SLCO_ACT_TYPE Slot::s_actType1 = Slot::SLCO_ACT_TYPE::VOICE; +FLCO::E Slot::s_flco2; +uint8_t Slot::s_id2 = 0U; +Slot::SLCO_ACT_TYPE Slot::s_actType2 = Slot::SLCO_ACT_TYPE::VOICE; -bool Slot::m_verifyReg = false; +bool Slot::s_verifyReg = false; -uint8_t Slot::m_alohaNRandWait = DEFAULT_NRAND_WAIT; -uint8_t Slot::m_alohaBackOff = 1U; +uint8_t Slot::s_alohaNRandWait = DEFAULT_NRAND_WAIT; +uint8_t Slot::s_alohaBackOff = 1U; // --------------------------------------------------------------------------- // Constants @@ -179,9 +179,9 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); m_adjSiteUpdateTimer.start(); - m_voice = new Voice(this, m_network, m_embeddedLCOnly, m_dumpTAData, debug, verbose); - m_data = new Data(this, m_network, dumpDataPacket, repeatDataPacket, debug, verbose); - m_control = new ControlSignaling(this, m_network, dumpCSBKData, debug, verbose); + m_voice = new Voice(this, s_network, s_embeddedLCOnly, s_dumpTAData, debug, verbose); + m_data = new Data(this, s_network, dumpDataPacket, repeatDataPacket, debug, verbose); + m_control = new ControlSignaling(this, s_network, dumpCSBKData, debug, verbose); } /* Finalizes a instance of the Slot class. */ @@ -241,9 +241,9 @@ bool Slot::processFrame(uint8_t *data, uint32_t len) raw |= (data[36U] << 0) & 0x00FFU; // Convert the raw RSSI to dBm - int rssi = m_rssiMapper->interpolate(raw); + int rssi = s_rssiMapper->interpolate(raw); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, raw RSSI = %u, reported RSSI = %d dBm", m_slotNo, raw, rssi); + LogInfoEx(LOG_RF, "DMR Slot %u, raw RSSI = %u, reported RSSI = %d dBm", m_slotNo, raw, rssi); } // RSSI is always reported as positive @@ -390,8 +390,7 @@ uint32_t Slot::getFrame(uint8_t* data) void Slot::processNetwork(const data::NetData& dmrData) { // don't process network frames if the RF modem isn't in a listening state - if (m_rfState != RS_RF_LISTENING) { - m_network->resetDMR(m_slotNo); + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { return; } @@ -406,7 +405,7 @@ void Slot::processNetwork(const data::NetData& dmrData) } } - if (m_authoritative) { + if (s_authoritative) { // don't process network frames if the destination ID's don't match and the network TG hang timer is running if (m_netLastDstId != 0U && dmrData.getDstId() != 0U && m_netState != RS_NET_IDLE) { if (m_netLastDstId != dmrData.getDstId() && (m_netTGHang.isRunning() && !m_netTGHang.hasExpired())) { @@ -420,7 +419,7 @@ void Slot::processNetwork(const data::NetData& dmrData) } // don't process network frames if this modem isn't authoritative - if (!m_authoritative && m_permittedDstId != dmrData.getDstId()) { + if (!s_authoritative && m_permittedDstId != dmrData.getDstId()) { if (!g_disableNonAuthoritativeLogging) LogWarning(LOG_NET, "DMR Slot %u, [NON-AUTHORITATIVE] Ignoring network traffic, destination not permitted!", m_slotNo); return; @@ -430,7 +429,7 @@ void Slot::processNetwork(const data::NetData& dmrData) DataType::E dataType = dmrData.getDataType(); - Slot* tscc = m_dmr->getTSCCSlot(); + Slot* tscc = s_dmr->getTSCCSlot(); bool enableTSCC = false; if (tscc != nullptr) @@ -471,7 +470,7 @@ void Slot::processNetwork(const data::NetData& dmrData) } if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, remote grant demand, srcId = %u, dstId = %u, unitToUnit = %u", + LogInfoEx(LOG_NET, "DMR Slot %u, remote grant demand, srcId = %u, dstId = %u, unitToUnit = %u", m_slotNo, dmrData.getSrcId(), dmrData.getDstId(), unitToUnit); } @@ -493,7 +492,7 @@ void Slot::processNetwork(const data::NetData& dmrData) if (dataType != DataType::CSBK) return; else { - if (m_slotNo != m_dmr->m_tsccSlotNo) + if (m_slotNo != s_dmr->m_tsccSlotNo) return; } } @@ -530,8 +529,8 @@ void Slot::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId) { if (m_rfState == RS_RF_AUDIO && m_rfLC->getDstId() == dstId) { LogWarning(LOG_DMR, "Slot %u, network requested in-call traffic reject, dstId = %u", m_slotNo, dstId); - if (m_affiliations->isGranted(dstId)) { - m_affiliations->releaseGrant(dstId, false); + if (s_affiliations->isGranted(dstId)) { + s_affiliations->releaseGrant(dstId, false); if (!m_enableTSCC) { notifyCC_ReleaseGrant(dstId); } @@ -559,30 +558,30 @@ void Slot::clock() uint32_t ms = m_interval.elapsed(); m_interval.start(); - if (m_network != nullptr) { - if (m_network->getStatus() == network::NET_STAT_RUNNING) { - m_siteData.setNetActive(true); + if (s_network != nullptr) { + if (s_network->getStatus() == network::NET_STAT_RUNNING) { + s_siteData.setNetActive(true); } else { - m_siteData.setNetActive(false); + s_siteData.setNetActive(false); } - lc::CSBK::setSiteData(m_siteData); + lc::CSBK::setSiteData(s_siteData); } // if we have control enabled; do clocking to generate a CC data stream if (m_enableTSCC) { - m_dmr->m_tsccCntInterval.clock(ms); - if (m_dmr->m_tsccCntInterval.isRunning() && m_dmr->m_tsccCntInterval.hasExpired()) { - m_dmr->m_tsccCnt++; - if (m_dmr->m_tsccCnt == TSCC_MAX_CSC_CNT) { - m_dmr->m_tsccCnt = 0U; + s_dmr->m_tsccCntInterval.clock(ms); + if (s_dmr->m_tsccCntInterval.isRunning() && s_dmr->m_tsccCntInterval.hasExpired()) { + s_dmr->m_tsccCnt++; + if (s_dmr->m_tsccCnt == TSCC_MAX_CSC_CNT) { + s_dmr->m_tsccCnt = 0U; } - m_dmr->m_tsccCntInterval.start(); + s_dmr->m_tsccCntInterval.start(); } - m_modem->setDMRIgnoreCACH_AT(m_slotNo); + s_modem->setDMRIgnoreCACH_AT(m_slotNo); if (m_ccRunning && !m_ccPacketInterval.isRunning()) { m_ccPacketInterval.start(); @@ -607,16 +606,16 @@ void Slot::clock() m_ccSeq = 0U; } - if (m_dmr->m_tsccPayloadActive) { - if ((m_dmr->m_tsccCnt % 2) == 0) { - setShortLC_Payload(m_siteData, m_dmr->m_tsccCnt); + if (s_dmr->m_tsccPayloadActive) { + if ((s_dmr->m_tsccCnt % 2) == 0) { + setShortLC_Payload(s_siteData, s_dmr->m_tsccCnt); } } else { - setShortLC_TSCC(m_siteData, m_dmr->m_tsccCnt); + setShortLC_TSCC(s_siteData, s_dmr->m_tsccCnt); } - writeRF_ControlData(m_dmr->m_tsccCnt, m_ccSeq); + writeRF_ControlData(s_dmr->m_tsccCnt, m_ccSeq); m_ccSeq++; } @@ -632,7 +631,7 @@ void Slot::clock() } // activate payload channel if requested from the TSCC - if (m_dmr->m_tsccPayloadActive) { + if (s_dmr->m_tsccPayloadActive) { if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { if (m_tsccPayloadDstId > 0U) { if (m_tsccPayloadActRetry.isRunning()) { @@ -644,7 +643,7 @@ void Slot::clock() } } - if ((m_dmr->m_tsccCnt % 2) > 0) { + if ((s_dmr->m_tsccCnt % 2) > 0) { if (m_tsccPayloadVoice) setShortLC(m_slotNo, m_tsccPayloadDstId, m_tsccPayloadGroup ? FLCO::GROUP : FLCO::PRIVATE, SLCO_ACT_TYPE::VOICE); else @@ -660,7 +659,7 @@ void Slot::clock() if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) { if (!m_rfTimeout) { - LogMessage(LOG_RF, "DMR Slot %u, user has timed out", m_slotNo); + LogInfoEx(LOG_RF, "DMR Slot %u, user has timed out", m_slotNo); m_rfTimeout = true; } } @@ -683,13 +682,13 @@ void Slot::clock() if (m_rfTGHang.hasExpired()) { m_rfTGHang.stop(); if (m_verbose) { - LogMessage(LOG_RF, "Slot %u, talkgroup hang has expired, lastDstId = %u", m_slotNo, m_rfLastDstId); + LogInfoEx(LOG_RF, "Slot %u, talkgroup hang has expired, lastDstId = %u", m_slotNo, m_rfLastDstId); } m_rfLastDstId = 0U; m_rfLastSrcId = 0U; // reset permitted ID and clear permission state - if (!m_authoritative && m_permittedDstId != 0U) { + if (!s_authoritative && m_permittedDstId != 0U) { m_permittedDstId = 0U; } } @@ -697,19 +696,19 @@ void Slot::clock() if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) { if (!m_netTimeout) { - LogMessage(LOG_NET, "DMR Slot %u, user has timed out", m_slotNo); + LogInfoEx(LOG_NET, "DMR Slot %u, user has timed out", m_slotNo); m_netTimeout = true; } } - if (m_authoritative) { + if (s_authoritative) { if (m_netTGHang.isRunning()) { m_netTGHang.clock(ms); if (m_netTGHang.hasExpired()) { m_netTGHang.stop(); if (m_verbose) { - LogMessage(LOG_NET, "Slot %u, talkgroup hang has expired, lastDstId = %u", m_slotNo, m_netLastDstId); + LogInfoEx(LOG_NET, "Slot %u, talkgroup hang has expired, lastDstId = %u", m_slotNo, m_netLastDstId); } m_netLastDstId = 0U; m_netLastSrcId = 0U; @@ -743,9 +742,9 @@ void Slot::clock() if (m_packetTimer.isRunning() && m_packetTimer.hasExpired()) { uint32_t elapsed = m_elapsed.elapsed(); - if (elapsed >= m_jitterTime) { + if (elapsed >= s_jitterTime) { LogWarning(LOG_NET, "DMR Slot %u, lost audio for %ums filling in", m_slotNo, elapsed); - m_voice->insertSilence(m_jitterSlots); + m_voice->insertSilence(s_jitterSlots); m_elapsed.start(); } @@ -771,7 +770,7 @@ void Slot::clockSiteData(uint32_t ms) { if (m_enableTSCC) { // clock all the grant timers - m_affiliations->clock(ms); + s_affiliations->clock(ms); // do we need to network announce ourselves? if (!m_adjSiteUpdateTimer.isRunning()) { @@ -783,10 +782,10 @@ void Slot::clockSiteData(uint32_t ms) if (m_adjSiteUpdateTimer.isRunning() && m_adjSiteUpdateTimer.hasExpired()) { if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { m_control->writeAdjSSNetwork(); - if (m_network != nullptr) { - if (m_affiliations->grpAffSize() > 0) { - auto affs = m_affiliations->grpAffTable(); - m_network->announceAffiliationUpdate(affs); + if (s_network != nullptr) { + if (s_affiliations->grpAffSize() > 0) { + auto affs = s_affiliations->grpAffTable(); + s_network->announceAffiliationUpdate(affs); } } m_adjSiteUpdateTimer.start(); @@ -823,15 +822,15 @@ void Slot::clockSiteData(uint32_t ms) void Slot::permittedTG(uint32_t dstId) { - if (m_authoritative) { + if (s_authoritative) { return; } if (m_verbose) { if (dstId == 0U) - LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG unpermit", m_slotNo); + LogInfoEx(LOG_DMR, "DMR Slot %u, non-authoritative TG unpermit", m_slotNo); else - LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); + LogInfoEx(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); } m_permittedDstId = dstId; @@ -846,7 +845,7 @@ void Slot::grantTG(uint32_t srcId, uint32_t dstId, bool grp) } if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, network TG grant demand, srcId = %u, dstId = %u", m_slotNo, srcId, dstId); + LogInfoEx(LOG_DMR, "DMR Slot %u, network TG grant demand, srcId = %u, dstId = %u", m_slotNo, srcId, dstId); } m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, grp); @@ -861,19 +860,19 @@ void Slot::releaseGrantTG(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, VC request, release TG grant, dstId = %u", m_slotNo, dstId); + LogInfoEx(LOG_DMR, "DMR Slot %u, VC request, release TG grant, dstId = %u", m_slotNo, dstId); } - if (m_affiliations->isGranted(dstId)) { - uint32_t chNo = m_affiliations->getGrantedCh(dstId); - uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); + if (s_affiliations->isGranted(dstId)) { + uint32_t chNo = s_affiliations->getGrantedCh(dstId); + uint32_t srcId = s_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = s_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_DMR, "DMR Slot %u, VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations->releaseGrant(dstId, false); + s_affiliations->releaseGrant(dstId, false); } } @@ -885,16 +884,16 @@ void Slot::touchGrantTG(uint32_t dstId) return; } - if (m_affiliations->isGranted(dstId)) { - uint32_t chNo = m_affiliations->getGrantedCh(dstId); - uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); + if (s_affiliations->isGranted(dstId)) { + uint32_t chNo = s_affiliations->getGrantedCh(dstId); + uint32_t srcId = s_affiliations->getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = s_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_DMR, "DMR Slot %u, VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } - m_affiliations->touchGrant(dstId); + s_affiliations->touchGrant(dstId); } } @@ -914,8 +913,8 @@ void Slot::clearRFReject() m_netFrames = 0U; m_netLost = 0U; - if (m_network != nullptr) - m_network->resetDMR(m_slotNo); + if (s_network != nullptr) + s_network->resetDMR(m_slotNo); m_rfState = RS_RF_LISTENING; } @@ -936,8 +935,8 @@ void Slot::setTSCC(bool enable, bool dedicated) m_enableTSCC = enable; m_dedicatedTSCC = dedicated; if (m_enableTSCC) { - m_modem->setDMRIgnoreCACH_AT(m_slotNo); - m_affiliations->setSlotForChannelTSCC(m_channelNo, m_slotNo); + s_modem->setDMRIgnoreCACH_AT(m_slotNo); + s_affiliations->setSlotForChannelTSCC(s_channelNo, m_slotNo); } } @@ -951,8 +950,8 @@ void Slot::setTSCCActivated(uint32_t dstId, uint32_t srcId, bool group, bool voi m_tsccPayloadVoice = voice; // start payload channel transmit - if (!m_modem->hasTX()) { - m_modem->writeDMRStart(true); + if (!s_modem->hasTX()) { + s_modem->writeDMRStart(true); } if (m_tsccPayloadDstId != 0U && !m_tsccPayloadActRetry.isRunning()) { @@ -1004,37 +1003,37 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s assert(idenTable != nullptr); assert(rssiMapper != nullptr); - m_dmr = dmr; + s_dmr = dmr; - m_authoritative = authoritative; + s_authoritative = authoritative; - m_colorCode = colorCode; + s_colorCode = colorCode; - m_siteData = siteData; + s_siteData = siteData; - m_embeddedLCOnly = embeddedLCOnly; - m_dumpTAData = dumpTAData; + s_embeddedLCOnly = embeddedLCOnly; + s_dumpTAData = dumpTAData; - m_modem = modem; - m_network = network; + s_modem = modem; + s_network = network; - m_duplex = duplex; + s_duplex = duplex; - m_idenTable = idenTable; - m_ridLookup = ridLookup; - m_tidLookup = tidLookup; - m_affiliations = new dmr::lookups::DMRAffiliationLookup(chLookup, verbose); + s_idenTable = idenTable; + s_ridLookup = ridLookup; + s_tidLookup = tidLookup; + s_affiliations = new dmr::lookups::DMRAffiliationLookup(chLookup, verbose); // set the grant release callback - m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { - Slot* tscc = m_dmr->getTSCCSlot(); + s_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t srcId, uint32_t dstId, uint8_t slot) { + Slot* tscc = s_dmr->getTSCCSlot(); if (tscc != nullptr) { - if (chNo == tscc->m_channelNo) { - m_dmr->tsccClearActivatedSlot(slot); + if (chNo == tscc->s_channelNo) { + s_dmr->tsccClearActivatedSlot(slot); return; } - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["slot"].set(slot); @@ -1048,7 +1047,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s } // callback REST API to clear TG permit for the granted TG on the specified voice channel - if (m_authoritative && m_dmr->m_supervisor) { + if (s_authoritative && s_dmr->m_supervisor) { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); dstId = 0U; // clear TG value @@ -1065,56 +1064,56 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s }); // set the unit deregistration callback - m_affiliations->setUnitDeregCallback([=](uint32_t srcId, bool automatic) { - if (m_network != nullptr) - m_network->announceUnitDeregistration(srcId); + s_affiliations->setUnitDeregCallback([=](uint32_t srcId, bool automatic) { + if (s_network != nullptr) + s_network->announceUnitDeregistration(srcId); }); - m_hangCount = callHang * 17U; + s_hangCount = callHang * 17U; - m_rssiMapper = rssiMapper; + s_rssiMapper = rssiMapper; - m_jitterTime = jitter; + s_jitterTime = jitter; float jitter_tmp = float(jitter) / 360.0F; - m_jitterSlots = (uint32_t)(std::ceil(jitter_tmp) * 6.0F); + s_jitterSlots = (uint32_t)(std::ceil(jitter_tmp) * 6.0F); - m_idle = new uint8_t[DMR_FRAME_LENGTH_BYTES + 2U]; - ::memcpy(m_idle, IDLE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); + s_idle = new uint8_t[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memcpy(s_idle, IDLE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); // Generate the Slot Type for the Idle frame SlotType slotType; slotType.setColorCode(colorCode); slotType.setDataType(DataType::IDLE); - slotType.encode(m_idle + 2U); + slotType.encode(s_idle + 2U); } /* Sets local configured site data. */ void Slot::setSiteData(::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg) { - m_siteData = SiteData(SiteModel::SM_SMALL, netId, siteId, 3U, requireReg); - m_channelNo = channelNo; + s_siteData = SiteData(SiteModel::SM_SMALL, netId, siteId, 3U, requireReg); + s_channelNo = channelNo; - std::vector<::lookups::IdenTable> entries = m_idenTable->list(); + std::vector<::lookups::IdenTable> entries = s_idenTable->list(); for (auto entry : entries) { if (entry.channelId() == channelId) { - m_idenEntry = entry; + s_idenEntry = entry; break; } } - m_controlChData = controlChData; + s_controlChData = controlChData; - lc::CSBK::setSiteData(m_siteData); + lc::CSBK::setSiteData(s_siteData); } /* Sets TSCC Aloha configuration. */ void Slot::setAlohaConfig(uint8_t nRandWait, uint8_t backOff) { - m_alohaNRandWait = nRandWait; - m_alohaBackOff = backOff; + s_alohaNRandWait = nRandWait; + s_alohaBackOff = backOff; } // --------------------------------------------------------------------------- @@ -1141,9 +1140,9 @@ void Slot::addFrame(const uint8_t *data, bool net, bool imm) uint32_t fifoSpace = 0U; if (m_slotNo == 1U) { - fifoSpace = m_modem->getDMRSpace1(); + fifoSpace = s_modem->getDMRSpace1(); } else { - fifoSpace = m_modem->getDMRSpace2(); + fifoSpace = s_modem->getDMRSpace2(); } //LogDebugEx(LOG_DMR, "Slot::addFrame()", "Slot %u, fifoSpace = %u", m_slotNo, fifoSpace); @@ -1202,14 +1201,14 @@ void Slot::processFrameLoss() m_slotNo, float(m_rfFrames) / 16.667F, float(m_rfErrs * 100U) / float(m_rfBits), m_frameLossCnt); } - LogMessage(LOG_RF, "DMR Slot %u, total frames: %d, total bits: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, "DMR Slot %u, total frames: %d, total bits: %d, errors: %d, BER: %.4f%%", m_slotNo, m_rfFrames, m_rfBits, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); // release trunked grant (if necessary) - Slot* tscc = m_dmr->getTSCCSlot(); + Slot* tscc = s_dmr->getTSCCSlot(); if (tscc != nullptr) { if (tscc->m_enableTSCC && m_rfLC != nullptr) { - tscc->m_affiliations->releaseGrant(m_rfLC->getDstId(), false); + tscc->s_affiliations->releaseGrant(m_rfLC->getDstId(), false); } clearTSCCActivated(); @@ -1242,11 +1241,11 @@ void Slot::processFrameLoss() void Slot::notifyCC_ReleaseGrant(uint32_t dstId) { - if (m_controlChData.address().empty()) { + if (s_controlChData.address().empty()) { return; } - if (m_controlChData.port() == 0) { + if (s_controlChData.port() == 0) { return; } @@ -1255,7 +1254,7 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, notifying CC of call termination, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + LogInfoEx(LOG_DMR, "DMR Slot %u, CC %s:%u, notifying CC of call termination, dstId = %u", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); } // callback REST API to release the granted TG on the specified control channel @@ -1266,21 +1265,21 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId) g_RPC->req(RPC_RELEASE_DMR_TG, req, [=](json::object& req, json::object& reply) { if (!req["status"].is()) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); return; } int status = req["status"].get(); if (status != network::NetRPC::OK) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); if (req["message"].is()) { std::string retMsg = req["message"].get(); ::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str()); } } else - ::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, released grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); - }, m_controlChData.address(), m_controlChData.port()); + ::LogInfoEx(LOG_DMR, "DMR Slot %u, CC %s:%u, released grant, dstId = %u", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); + }, s_controlChData.address(), s_controlChData.port()); m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -1292,11 +1291,11 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId) void Slot::notifyCC_TouchGrant(uint32_t dstId) { - if (m_controlChData.address().empty()) { + if (s_controlChData.address().empty()) { return; } - if (m_controlChData.port() == 0) { + if (s_controlChData.port() == 0) { return; } @@ -1313,21 +1312,21 @@ void Slot::notifyCC_TouchGrant(uint32_t dstId) g_RPC->req(RPC_TOUCH_DMR_TG, req, [=](json::object& req, json::object& reply) { // validate channelNo is a string within the JSON blob if (!req["status"].is()) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); return; } int status = req["status"].get(); if (status != network::NetRPC::OK) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); if (req["message"].is()) { std::string retMsg = req["message"].get(); ::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str()); } } else - ::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, touched grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); - }, m_controlChData.address(), m_controlChData.port()); + ::LogInfoEx(LOG_DMR, "DMR Slot %u, CC %s:%u, touched grant, dstId = %u", m_slotNo, s_controlChData.address().c_str(), s_controlChData.port(), dstId); + }, s_controlChData.address(), s_controlChData.port()); } /* Write data frame to the network. */ @@ -1350,7 +1349,7 @@ void Slot::writeNetwork(const uint8_t* data, DataType::E dataType, FLCO::E flco, if (m_netState != RS_NET_IDLE) return; - if (m_network == nullptr) + if (s_network == nullptr) return; data::NetData dmrData; @@ -1369,7 +1368,7 @@ void Slot::writeNetwork(const uint8_t* data, DataType::E dataType, FLCO::E flco, dmrData.setData(data + 2U); - m_network->writeDMR(dmrData, noSequence); + s_network->writeDMR(dmrData, noSequence); } /* Helper to write RF end of frame data. */ @@ -1380,38 +1379,38 @@ void Slot::writeEndRF(bool writeEnd) if (m_netState == RS_NET_IDLE) { if (m_enableTSCC) - setShortLC_Payload(m_siteData, m_dmr->m_tsccCnt); + setShortLC_Payload(s_siteData, s_dmr->m_tsccCnt); else setShortLC(m_slotNo, 0U); } if (writeEnd) { - if (m_netState == RS_NET_IDLE && m_duplex && !m_rfTimeout) { + if (m_netState == RS_NET_IDLE && s_duplex && !m_rfTimeout) { // Create a dummy start end frame uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; - Sync::addDMRDataSync(data + 2U, m_duplex); + Sync::addDMRDataSync(data + 2U, s_duplex); lc::FullLC fullLC; fullLC.encode(*m_rfLC, data + 2U, DataType::TERMINATOR_WITH_LC); SlotType slotType; - slotType.setColorCode(m_colorCode); + slotType.setColorCode(s_colorCode); slotType.setDataType(DataType::TERMINATOR_WITH_LC); slotType.encode(data + 2U); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; - for (uint32_t i = 0U; i < m_hangCount; i++) + for (uint32_t i = 0U; i < s_hangCount; i++) addFrame(data); } } m_data->m_pduDataOffset = 0U; - if (m_network != nullptr) - m_network->resetDMR(m_slotNo); + if (s_network != nullptr) + s_network->resetDMR(m_slotNo); m_rfTimeoutTimer.stop(); m_rfTimeout = false; @@ -1438,21 +1437,21 @@ void Slot::writeEndNet(bool writeEnd) // Create a dummy start end frame uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; - Sync::addDMRDataSync(data + 2U, m_duplex); + Sync::addDMRDataSync(data + 2U, s_duplex); lc::FullLC fullLC; fullLC.encode(*m_netLC, data + 2U, DataType::TERMINATOR_WITH_LC); SlotType slotType; - slotType.setColorCode(m_colorCode); + slotType.setColorCode(s_colorCode); slotType.setDataType(DataType::TERMINATOR_WITH_LC); slotType.encode(data + 2U); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; - if (m_duplex) { - for (uint32_t i = 0U; i < m_hangCount; i++) + if (s_duplex) { + for (uint32_t i = 0U; i < s_hangCount; i++) addFrame(data, true); } else { @@ -1462,10 +1461,10 @@ void Slot::writeEndNet(bool writeEnd) } // release trunked grant (if necessary) - Slot* tscc = m_dmr->getTSCCSlot(); + Slot* tscc = s_dmr->getTSCCSlot(); if (tscc != nullptr) { if (tscc->m_enableTSCC && m_netLC != nullptr) { - tscc->m_affiliations->releaseGrant(m_netLC->getDstId(), false); + tscc->s_affiliations->releaseGrant(m_netLC->getDstId(), false); } clearTSCCActivated(); @@ -1477,8 +1476,8 @@ void Slot::writeEndNet(bool writeEnd) m_data->m_pduDataOffset = 0U; - if (m_network != nullptr) - m_network->resetDMR(m_slotNo); + if (s_network != nullptr) + s_network->resetDMR(m_slotNo); m_networkWatchdog.stop(); m_netTimeoutTimer.stop(); @@ -1557,11 +1556,11 @@ void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) m_control->writeRF_TSCC_Aloha(); break; case 2: - m_control->writeRF_TSCC_Bcast_Ann_Wd(m_channelNo, true, m_siteData.systemIdentity(), m_siteData.requireReg()); + m_control->writeRF_TSCC_Bcast_Ann_Wd(s_channelNo, true, s_siteData.systemIdentity(), s_siteData.requireReg()); break; case 3: { - std::unordered_map grants = m_affiliations->grantTable(); + std::unordered_map grants = s_affiliations->grantTable(); if (grants.size() > 0) { uint32_t j = 0U; if (m_lastLateEntry > grants.size()) { @@ -1571,8 +1570,8 @@ void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) for (auto entry : grants) { if (j == m_lastLateEntry) { uint32_t dstId = entry.first; - uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - bool grp = m_affiliations->isGroup(dstId); + uint32_t srcId = s_affiliations->getGrantedSrcId(dstId); + bool grp = s_affiliations->isGroup(dstId); if (m_debug) { LogDebugEx(LOG_DMR, "Slot::writeRF_ControlData()", "frameCnt = %u, seq = %u, late entry, dstId = %u, srcId = %u", frameCnt, n, dstId, srcId); @@ -1646,31 +1645,31 @@ void Slot::clearTSCCActivated() void Slot::setShortLC(uint32_t slotNo, uint32_t id, FLCO::E flco, SLCO_ACT_TYPE actType) { - assert(m_modem != nullptr); + assert(s_modem != nullptr); switch (slotNo) { case 1U: - m_id1 = 0U; - m_flco1 = flco; - m_actType1 = actType; + s_id1 = 0U; + s_flco1 = flco; + s_actType1 = actType; if (id != 0U) { uint8_t buffer[3U]; buffer[0U] = (id << 16) & 0xFFU; buffer[1U] = (id << 8) & 0xFFU; buffer[2U] = (id << 0) & 0xFFU; - m_id1 = edac::CRC::crc8(buffer, 3U); + s_id1 = edac::CRC::crc8(buffer, 3U); } break; case 2U: - m_id2 = 0U; - m_flco2 = flco; - m_actType2 = actType; + s_id2 = 0U; + s_flco2 = flco; + s_actType2 = actType; if (id != 0U) { uint8_t buffer[3U]; buffer[0U] = (id << 16) & 0xFFU; buffer[1U] = (id << 8) & 0xFFU; buffer[2U] = (id << 0) & 0xFFU; - m_id2 = edac::CRC::crc8(buffer, 3U); + s_id2 = edac::CRC::crc8(buffer, 3U); } break; default: @@ -1679,7 +1678,7 @@ void Slot::setShortLC(uint32_t slotNo, uint32_t id, FLCO::E flco, SLCO_ACT_TYPE } // If we have no activity to report, let the modem send the null Short LC when it's ready - if (m_id1 == 0U && m_id2 == 0U) + if (s_id1 == 0U && s_id2 == 0U) return; uint8_t lc[5U]; @@ -1688,35 +1687,35 @@ void Slot::setShortLC(uint32_t slotNo, uint32_t id, FLCO::E flco, SLCO_ACT_TYPE lc[2U] = 0x00U; lc[3U] = 0x00U; - if (m_id1 != 0U) { - lc[2U] = m_id1; - if (m_actType1 == SLCO_ACT_TYPE::VOICE && m_flco1 == FLCO::GROUP) + if (s_id1 != 0U) { + lc[2U] = s_id1; + if (s_actType1 == SLCO_ACT_TYPE::VOICE && s_flco1 == FLCO::GROUP) lc[1U] |= 0x08U; - else if (m_actType1 == SLCO_ACT_TYPE::VOICE && m_flco1 == FLCO::PRIVATE) + else if (s_actType1 == SLCO_ACT_TYPE::VOICE && s_flco1 == FLCO::PRIVATE) lc[1U] |= 0x09U; - else if (m_actType1 == SLCO_ACT_TYPE::DATA && m_flco1 == FLCO::GROUP) + else if (s_actType1 == SLCO_ACT_TYPE::DATA && s_flco1 == FLCO::GROUP) lc[1U] |= 0x0BU; - else if (m_actType1 == SLCO_ACT_TYPE::DATA && m_flco1 == FLCO::PRIVATE) + else if (s_actType1 == SLCO_ACT_TYPE::DATA && s_flco1 == FLCO::PRIVATE) lc[1U] |= 0x0AU; - else if (m_actType1 == SLCO_ACT_TYPE::CSBK && m_flco1 == FLCO::GROUP) + else if (s_actType1 == SLCO_ACT_TYPE::CSBK && s_flco1 == FLCO::GROUP) lc[1U] |= 0x02U; - else if (m_actType1 == SLCO_ACT_TYPE::CSBK && m_flco1 == FLCO::PRIVATE) + else if (s_actType1 == SLCO_ACT_TYPE::CSBK && s_flco1 == FLCO::PRIVATE) lc[1U] |= 0x03U; } - if (m_id2 != 0U) { - lc[3U] = m_id2; - if (m_actType2 == SLCO_ACT_TYPE::VOICE && m_flco2 == FLCO::GROUP) + if (s_id2 != 0U) { + lc[3U] = s_id2; + if (s_actType2 == SLCO_ACT_TYPE::VOICE && s_flco2 == FLCO::GROUP) lc[1U] |= 0x08U; - else if (m_actType2 == SLCO_ACT_TYPE::VOICE && m_flco2 == FLCO::PRIVATE) + else if (s_actType2 == SLCO_ACT_TYPE::VOICE && s_flco2 == FLCO::PRIVATE) lc[1U] |= 0x09U; - else if (m_actType2 == SLCO_ACT_TYPE::DATA && m_flco2 == FLCO::GROUP) + else if (s_actType2 == SLCO_ACT_TYPE::DATA && s_flco2 == FLCO::GROUP) lc[1U] |= 0x0BU; - else if (m_actType2 == SLCO_ACT_TYPE::DATA && m_flco2 == FLCO::PRIVATE) + else if (s_actType2 == SLCO_ACT_TYPE::DATA && s_flco2 == FLCO::PRIVATE) lc[1U] |= 0x0AU; - else if (m_actType2 == SLCO_ACT_TYPE::CSBK && m_flco2 == FLCO::GROUP) + else if (s_actType2 == SLCO_ACT_TYPE::CSBK && s_flco2 == FLCO::GROUP) lc[1U] |= 0x02U; - else if (m_actType2 == SLCO_ACT_TYPE::CSBK && m_flco2 == FLCO::PRIVATE) + else if (s_actType2 == SLCO_ACT_TYPE::CSBK && s_flco2 == FLCO::PRIVATE) lc[1U] |= 0x03U; } @@ -1727,14 +1726,14 @@ void Slot::setShortLC(uint32_t slotNo, uint32_t id, FLCO::E flco, SLCO_ACT_TYPE lc::ShortLC shortLC; shortLC.encode(lc, sLC); - m_modem->writeDMRShortLC(sLC); + s_modem->writeDMRShortLC(sLC); } /* Helper to set the DMR short LC for TSCC. */ void Slot::setShortLC_TSCC(SiteData siteData, uint16_t counter) { - assert(m_modem != nullptr); + assert(s_modem != nullptr); uint8_t lc[5U]; uint32_t lcValue = 0U; @@ -1787,14 +1786,14 @@ void Slot::setShortLC_TSCC(SiteData siteData, uint16_t counter) lc::ShortLC shortLC; shortLC.encode(lc, sLC); - m_modem->writeDMRShortLC(sLC); + s_modem->writeDMRShortLC(sLC); } /* Helper to set the DMR short LC for payload. */ void Slot::setShortLC_Payload(SiteData siteData, uint16_t counter) { - assert(m_modem != nullptr); + assert(s_modem != nullptr); uint8_t lc[5U]; uint32_t lcValue = 0U; @@ -1847,5 +1846,5 @@ void Slot::setShortLC_Payload(SiteData siteData, uint16_t counter) lc::ShortLC shortLC; shortLC.encode(lc, sLC); - m_modem->writeDMRShortLC(sLC); + s_modem->writeDMRShortLC(sLC); } diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index 04e71a92a..3f1395360 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -444,62 +444,62 @@ namespace dmr bool m_verbose; bool m_debug; - static Control* m_dmr; + static Control* s_dmr; - static bool m_authoritative; + static bool s_authoritative; - static uint32_t m_colorCode; + static uint32_t s_colorCode; - static SiteData m_siteData; - static uint32_t m_channelNo; + static SiteData s_siteData; + static uint32_t s_channelNo; - static bool m_embeddedLCOnly; - static bool m_dumpTAData; + static bool s_embeddedLCOnly; + static bool s_dumpTAData; - static modem::Modem* m_modem; - static network::Network* m_network; + static modem::Modem* s_modem; + static network::Network* s_network; - static bool m_duplex; + static bool s_duplex; - static ::lookups::IdenTableLookup* m_idenTable; - static ::lookups::RadioIdLookup* m_ridLookup; - static ::lookups::TalkgroupRulesLookup* m_tidLookup; - static lookups::DMRAffiliationLookup* m_affiliations; - static ::lookups::VoiceChData m_controlChData; + static ::lookups::IdenTableLookup* s_idenTable; + static ::lookups::RadioIdLookup* s_ridLookup; + static ::lookups::TalkgroupRulesLookup* s_tidLookup; + static lookups::DMRAffiliationLookup* s_affiliations; + static ::lookups::VoiceChData s_controlChData; - static ::lookups::IdenTable m_idenEntry; + static ::lookups::IdenTable s_idenEntry; - static uint32_t m_hangCount; + static uint32_t s_hangCount; - static ::lookups::RSSIInterpolator* m_rssiMapper; + static ::lookups::RSSIInterpolator* s_rssiMapper; - static uint32_t m_jitterTime; - static uint32_t m_jitterSlots; + static uint32_t s_jitterTime; + static uint32_t s_jitterSlots; - static uint8_t* m_idle; + static uint8_t* s_idle; /** * @brief Short LC Activity Type */ enum SLCO_ACT_TYPE { - NONE, //! None - VOICE, //! Voice - DATA, //! Data - CSBK //! CSBK + NONE, //!< Slot Activity Type - None + VOICE, //!< Slot Activity Type - Voice + DATA, //!< Slot Activity Type - Data + CSBK //!< Slot Activity Type - CSBK }; - static defines::FLCO::E m_flco1; - static uint8_t m_id1; - static SLCO_ACT_TYPE m_actType1; + static defines::FLCO::E s_flco1; + static uint8_t s_id1; + static SLCO_ACT_TYPE s_actType1; - static defines::FLCO::E m_flco2; - static uint8_t m_id2; - static SLCO_ACT_TYPE m_actType2; + static defines::FLCO::E s_flco2; + static uint8_t s_id2; + static SLCO_ACT_TYPE s_actType2; - static bool m_verifyReg; + static bool s_verifyReg; - static uint8_t m_alohaNRandWait; - static uint8_t m_alohaBackOff; + static uint8_t s_alohaNRandWait; + static uint8_t s_alohaBackOff; /** * @brief Add data frame to the data ring buffer. diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index 6a03b862a..f0010b203 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -61,6 +61,8 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s return false; } + __lock(); + if (getAvailableSlotForChannel(chNo) == 0U || chNo == m_tsccChNo) { m_chLookup->removeRFCh(chNo); } @@ -77,10 +79,12 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s m_grantTimers[dstId].start(); if (m_verbose) { - LogMessage(LOG_HOST, "%s, granting channel, chNo = %u, slot = %u, dstId = %u, group = %u", + LogInfoEx(LOG_HOST, "%s, granting channel, chNo = %u, slot = %u, dstId = %u, group = %u", m_name.c_str(), chNo, slot, dstId, grp); } + __unlock(); + return true; } @@ -112,18 +116,21 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) if (isGranted(dstId)) { uint32_t chNo = m_grantChTable.at(dstId); + uint32_t srcId = getGrantedSrcId(dstId); std::tuple slotData = m_grantChSlotTable.at(dstId); uint8_t slot = std::get<1>(slotData); if (m_verbose) { - LogMessage(LOG_HOST, "%s, releasing channel grant, chNo = %u, slot = %u, dstId = %u", + LogInfoEx(LOG_HOST, "%s, releasing channel grant, chNo = %u, slot = %u, dstId = %u", m_name.c_str(), chNo, slot, dstId); } if (m_releaseGrant != nullptr) { - m_releaseGrant(chNo, dstId, slot); + m_releaseGrant(chNo, srcId, dstId, slot); } + __lock(); + m_grantChTable.erase(dstId); m_grantSrcIdTable.erase(dstId); m_grantChSlotTable.erase(dstId); @@ -140,6 +147,8 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_grantTimers[dstId].stop(); + __unlock(); + return true; } @@ -154,6 +163,8 @@ bool DMRAffiliationLookup::isChBusy(uint32_t chNo) const return false; } + __spinlock(); + // lookup dynamic channel grant table entry for (auto grantEntry : m_grantChTable) { if (grantEntry.second == chNo) { @@ -187,6 +198,8 @@ uint8_t DMRAffiliationLookup::getGrantedSlot(uint32_t dstId) const return 0U; } + __spinlock(); + // lookup dynamic channel grant table entry for (auto entry : m_grantChSlotTable) { if (entry.first == dstId) { @@ -219,6 +232,8 @@ uint32_t DMRAffiliationLookup::getAvailableChannelForSlot(uint8_t slot) const return 0U; } + __spinlock(); + uint32_t chNo = 0U; for (auto entry : m_chLookup->rfChDataTable()) { if (entry.second.chNo() == m_tsccChNo && slot == m_tsccSlot) { @@ -260,6 +275,8 @@ uint8_t DMRAffiliationLookup::getAvailableSlotForChannel(uint32_t chNo) const return 0U; } + __spinlock(); + uint8_t slot = 1U; // lookup dynamic channel slot grant table entry diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index e458c1ed5..572f9ca34 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -35,7 +35,7 @@ using namespace dmr::packet; // Make sure control data is supported. #define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ - if (!m_slot->m_dmr->getTSCCSlot()->m_enableTSCC) { \ + if (!m_slot->s_dmr->getTSCCSlot()->m_enableTSCC) { \ LogWarning(LOG_RF, "DMR Slot %u, %s denial, unsupported service, srcId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID); \ writeRF_CSBK_ACK_RSP(_SRCID, ReasonCode::TS_DENY_RSN_SYS_UNSUPPORTED_SVC, 0U); \ return false; \ @@ -67,7 +67,7 @@ using namespace dmr::packet; // Verify the source RID is registered. #define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID) \ - if (!m_slot->m_affiliations->isUnitReg(_SRCID) && m_slot->m_verifyReg) { \ + if (!m_slot->s_affiliations->isUnitReg(_SRCID) && m_slot->s_verifyReg) { \ LogWarning(LOG_RF, "DMR Slot %u, %s denial, RID not registered, srcId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID); \ writeRF_CSBK_ACK_RSP(_SRCID, ReasonCode::TS_DENY_RSN_PERM_USER_REFUSED, 0U); \ return false; \ @@ -76,25 +76,25 @@ using namespace dmr::packet; // Macro helper to verbose log a generic CSBK. #define VERBOSE_LOG_CSBK(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic CSBK. #define VERBOSE_LOG_CSBK_DST(_PCKT_STR, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _DSTID); \ + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _DSTID); \ } // Macro helper to verbose log a generic network CSBK. #define VERBOSE_LOG_CSBK_NET(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic network CSBK. #define DEBUG_LOG_CSBK(_PCKT_STR) \ if (m_debug) { \ - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s", m_slot->m_slotNo, _PCKT_STR.c_str()); \ + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s", m_slot->m_slotNo, _PCKT_STR.c_str()); \ } // --------------------------------------------------------------------------- @@ -118,7 +118,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) DataType::E dataType = (DataType::E)(data[1U] & 0x0FU); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(dataType); if (dataType == DataType::CSBK) { @@ -135,7 +135,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) uint32_t srcId = csbk->getSrcId(); uint32_t dstId = csbk->getDstId(); - m_slot->m_affiliations->touchUnitReg(srcId); + m_slot->s_affiliations->touchUnitReg(srcId); if (srcId != 0U || dstId != 0U) { // don't process RF frames if the network isn't in a idle state and the RF destination is the network destination @@ -165,7 +165,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) if (m_slot->m_netState == RS_NET_IDLE && csbk->getDataContent()) { m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::PRIVATE, Slot::SLCO_ACT_TYPE::DATA); } else { - m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::PRIVATE, Slot::SLCO_ACT_TYPE::CSBK); + m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::PRIVATE, Slot::SLCO_ACT_TYPE::CSBK); } bool handled = false; @@ -180,7 +180,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) { if (csbk->getFID() == FID_MOT) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); } @@ -190,7 +190,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) CSBK_RAND* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), serviceKind = $%02X, serviceOptions = $%02X, serviceExtra = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), serviceKind = $%02X, serviceOptions = $%02X, serviceExtra = $%02X, srcId = %u, dstId = %u", m_slot->m_slotNo, isp->getServiceKind(), isp->getServiceOptions(), isp->getServiceExtra(), isp->getSrcId(), isp->getDstId()); } @@ -210,11 +210,11 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 1U); - if (m_slot->m_authoritative) { + if (m_slot->s_authoritative) { writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), false); } else { - if (m_slot->m_network != nullptr) - m_slot->m_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, true); + if (m_slot->s_network != nullptr) + m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, true); } break; case ServiceKind::GRP_VOICE_CALL: @@ -229,11 +229,11 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 1U); - if (m_slot->m_authoritative) { + if (m_slot->s_authoritative) { writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true); } else { - if (m_slot->m_network != nullptr) - m_slot->m_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, false); + if (m_slot->s_network != nullptr) + m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, false); } break; case ServiceKind::IND_DATA_CALL: @@ -293,7 +293,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) { CSBK_EXT_FNCT* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction(), dstId, srcId); } @@ -332,7 +332,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) { CSBK_MAINT* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, kind = $%02X, srcId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, kind = $%02X, srcId = %u", m_slot->m_slotNo, csbk->toString().c_str(), isp->getMaintKind(), srcId); } } @@ -340,7 +340,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) case CSBKO::PRECCSBK: { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, dstId); } } @@ -359,14 +359,14 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); m_slot->m_rfSeqNo = 0U; data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); m_slot->writeNetwork(data, DataType::CSBK, gi ? FLCO::GROUP : FLCO::PRIVATE, srcId, dstId, 0U, 0U, true); @@ -406,7 +406,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) return; } - if (osp->getSystemId() != m_slot->m_siteData.systemIdentity()) { + if (osp->getSystemId() != m_slot->s_siteData.systemIdentity()) { // update site table data AdjSiteData site; try { @@ -416,7 +416,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) } if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, %s, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), osp->getSystemId(), osp->getLogicalCh1()); } @@ -465,7 +465,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) { if (csbk->getFID() == FID_MOT) { if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, %s, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), srcId, dstId); } @@ -473,7 +473,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) } else { CSBK_RAND* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, RAND (Random Access), serviceKind = $%02X, serviceOptions = $%02X, serviceExtra = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, RAND (Random Access), serviceKind = $%02X, serviceOptions = $%02X, serviceExtra = $%02X, srcId = %u, dstId = %u", m_slot->m_slotNo, isp->getServiceKind(), isp->getServiceOptions(), isp->getServiceExtra(), isp->getSrcId(), isp->getDstId()); } @@ -481,14 +481,14 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) case ServiceKind::IND_VOICE_CALL: writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 1U); - if (!m_slot->m_affiliations->isGranted(dstId)) { + if (!m_slot->s_affiliations->isGranted(dstId)) { writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), false, true); } break; case ServiceKind::GRP_VOICE_CALL: writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 1U); - if (!m_slot->m_affiliations->isGranted(dstId)) { + if (!m_slot->s_affiliations->isGranted(dstId)) { writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true, true); } break; @@ -526,7 +526,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) { CSBK_EXT_FNCT* isp = static_cast(csbk.get()); if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", m_slot->m_slotNo, csbk->toString().c_str(), isp->getExtendedFunction(), dstId, srcId); } @@ -564,7 +564,7 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) case CSBKO::PRECCSBK: { if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, PRECCSBK (%s Preamble CSBK), toFollow = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->getDataContent() ? "Data" : "CSBK", csbk->getCBF(), srcId, dstId); } } @@ -582,11 +582,11 @@ void ControlSignaling::processNetwork(const data::NetData& dmrData) // regenerate the Slot Type SlotType slotType; slotType.decode(data + 2U); - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.encode(data + 2U); // convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -608,20 +608,20 @@ void ControlSignaling::writeAdjSSNetwork() return; } - if (m_slot->m_network != nullptr) { + if (m_slot->s_network != nullptr) { // transmit adjacent site broadcast std::unique_ptr csbk = std::make_unique(); - csbk->siteIdenEntry(m_slot->m_idenEntry); + csbk->siteIdenEntry(m_slot->s_idenEntry); csbk->setCdef(false); csbk->setAnncType(BroadcastAnncType::ANN_WD_TSCC); - csbk->setLogicalCh1(m_slot->m_channelNo); + csbk->setLogicalCh1(m_slot->s_channelNo); csbk->setAnnWdCh1(true); - csbk->setSystemId(m_slot->m_siteData.systemIdentity()); - csbk->setRequireReg(m_slot->m_siteData.requireReg()); + csbk->setSystemId(m_slot->s_siteData.systemIdentity()); + csbk->setRequireReg(m_slot->s_siteData.requireReg()); if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, CSBK, %s, network announce, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), - m_slot->m_siteData.systemIdentity(), m_slot->m_channelNo); + LogInfoEx(LOG_NET, "DMR Slot %u, CSBK, %s, network announce, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), + m_slot->s_siteData.systemIdentity(), m_slot->s_channelNo); } writeNet_CSBK(csbk.get()); @@ -639,7 +639,7 @@ void ControlSignaling::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t ds csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", m_slot->m_slotNo, csbk->toString().c_str(), func, arg, dstId); } @@ -710,7 +710,7 @@ void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool imm) ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::CSBK); // Regenerate the CSBK data @@ -720,14 +720,14 @@ void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool imm) slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); m_slot->m_rfSeqNo = 0U; data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data, false, imm); } @@ -739,7 +739,7 @@ void ControlSignaling::writeNet_CSBK(lc::CSBK* csbk) ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::CSBK); // Regenerate the CSBK data @@ -756,7 +756,7 @@ void ControlSignaling::writeNet_CSBK(lc::CSBK* csbk) data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); m_slot->writeNetwork(data, DataType::CSBK, csbk->getGI() ? FLCO::GROUP : FLCO::PRIVATE, csbk->getSrcId(), csbk->getDstId(), 0U, 0U, true); @@ -777,7 +777,7 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), csbk->getSrcId(), csbk->getDstId()); } @@ -796,7 +796,7 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_DMR, "DMR Slot %u, CSBK, %s, reason = $%02X (%s), srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), reason, DMRUtils::rsnToString(reason).c_str(), csbk->getSrcId(), csbk->getDstId()); } @@ -808,7 +808,7 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net, bool skip, uint32_t chNo) { - Slot* tscc = m_slot->m_dmr->getTSCCSlot(); + Slot* tscc = m_slot->s_dmr->getTSCCSlot(); uint8_t slot = 0U; @@ -859,15 +859,15 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } - if (!tscc->m_affiliations->isGranted(dstId)) { - ::lookups::TalkgroupRuleGroupVoice groupVoice = tscc->m_tidLookup->find(dstId); + if (!tscc->s_affiliations->isGranted(dstId)) { + ::lookups::TalkgroupRuleGroupVoice groupVoice = tscc->s_tidLookup->find(dstId); slot = groupVoice.source().tgSlot(); if (grp && !tscc->m_ignoreAffiliationCheck) { // is this an affiliation required group? - ::lookups::TalkgroupRuleGroupVoice tid = tscc->m_tidLookup->find(dstId, slot); + ::lookups::TalkgroupRuleGroupVoice tid = tscc->s_tidLookup->find(dstId, slot); if (tid.config().affiliated()) { - if (!tscc->m_affiliations->hasGroupAff(dstId)) { + if (!tscc->s_affiliations->hasGroupAff(dstId)) { LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, GRP_VOICE_CALL (Group Voice Call) ignored, no group affiliations, dstId = %u", tscc->m_slotNo, dstId); return false; } @@ -876,14 +876,14 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!grp && !tscc->m_ignoreAffiliationCheck) { // is this the target registered? - if (!tscc->m_affiliations->isUnitReg(dstId)) { + if (!tscc->s_affiliations->isUnitReg(dstId)) { LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, IND_VOICE_CALL (Individual Voice Call) ignored, no unit registration, dstId = %u", tscc->m_slotNo, dstId); return false; } } - uint32_t availChNo = tscc->m_affiliations->getAvailableChannelForSlot(slot); - if (!tscc->m_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) { + uint32_t availChNo = tscc->s_affiliations->getAvailableChannelForSlot(slot); + if (!tscc->s_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) { if (grp) { if (!net) { LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, GRP_VOICE_CALL (Group Voice Call) queued, no channels available, dstId = %u", tscc->m_slotNo, dstId); @@ -908,10 +908,10 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { - if (tscc->m_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net)) { - chNo = tscc->m_affiliations->getGrantedCh(dstId); - slot = tscc->m_affiliations->getGrantedSlot(dstId); - //tscc->m_siteData.setChCnt(tscc->m_affiliations->getRFChCnt() + tscc->m_affiliations->getGrantedRFChCnt()); + if (tscc->s_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net)) { + chNo = tscc->s_affiliations->getGrantedCh(dstId); + slot = tscc->s_affiliations->getGrantedSlot(dstId); + //tscc->s_siteData.setChCnt(tscc->s_affiliations->getRFChCnt() + tscc->s_affiliations->getGrantedRFChCnt()); } } } @@ -919,7 +919,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!tscc->m_disableGrantSrcIdCheck && !net) { // do collision check between grants to see if a SU is attempting a "grant retry" or if this is a // different source from the original grant - uint32_t grantedSrcId = tscc->m_affiliations->getGrantedSrcId(dstId); + uint32_t grantedSrcId = tscc->s_affiliations->getGrantedSrcId(dstId); if (srcId != grantedSrcId) { if (!net) { LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, VOICE_CALL (Voice Call) denied, traffic in progress, dstId = %u", tscc->m_slotNo, dstId); @@ -933,18 +933,18 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } - chNo = tscc->m_affiliations->getGrantedCh(dstId); - slot = tscc->m_affiliations->getGrantedSlot(dstId); + chNo = tscc->s_affiliations->getGrantedCh(dstId); + slot = tscc->s_affiliations->getGrantedSlot(dstId); - tscc->m_affiliations->touchGrant(dstId); + tscc->s_affiliations->touchGrant(dstId); } } else { - if (tscc->m_affiliations->isGranted(dstId)) { - chNo = tscc->m_affiliations->getGrantedCh(dstId); - slot = tscc->m_affiliations->getGrantedSlot(dstId); + if (tscc->s_affiliations->isGranted(dstId)) { + chNo = tscc->s_affiliations->getGrantedCh(dstId); + slot = tscc->s_affiliations->getGrantedSlot(dstId); - tscc->m_affiliations->touchGrant(dstId); + tscc->s_affiliations->touchGrant(dstId); } else { return false; @@ -957,9 +957,9 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } // callback RPC to permit the granted TG on the specified voice channel - if (tscc->m_authoritative && tscc->m_supervisor && - tscc->m_channelNo != chNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (tscc->s_authoritative && tscc->m_supervisor && + tscc->s_channelNo != chNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -987,7 +987,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (requestFailed) { ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); - tscc->m_affiliations->releaseGrant(dstId, false); + tscc->s_affiliations->releaseGrant(dstId, false); if (!net) { writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); m_slot->m_rfState = RS_RF_REJECTED; @@ -1010,7 +1010,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } @@ -1023,8 +1023,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel - if (chNo != tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (chNo != tscc->s_channelNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1043,7 +1043,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { - m_slot->m_dmr->tsccActivateSlot(slot, dstId, srcId, grp, true); + m_slot->s_dmr->tsccActivateSlot(slot, dstId, srcId, grp, true); } } else { @@ -1052,9 +1052,9 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } // callback RPC to permit the granted TG on the specified voice channel - if (tscc->m_authoritative && tscc->m_supervisor && - tscc->m_channelNo != chNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (tscc->s_authoritative && tscc->m_supervisor && + tscc->s_channelNo != chNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1082,7 +1082,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (requestFailed) { ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); - tscc->m_affiliations->releaseGrant(dstId, false); + tscc->s_affiliations->releaseGrant(dstId, false); if (!net) { writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); m_slot->m_rfState = RS_RF_REJECTED; @@ -1103,7 +1103,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } @@ -1116,8 +1116,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel - if (chNo != tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (chNo != tscc->s_channelNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1136,7 +1136,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { - m_slot->m_dmr->tsccActivateSlot(slot, dstId, srcId, grp, true); + m_slot->s_dmr->tsccActivateSlot(slot, dstId, srcId, grp, true); } } @@ -1147,7 +1147,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net, bool skip, uint32_t chNo) { - Slot* tscc = m_slot->m_dmr->getTSCCSlot(); + Slot* tscc = m_slot->s_dmr->getTSCCSlot(); uint8_t slot = 0U; @@ -1198,12 +1198,12 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } } - if (!tscc->m_affiliations->isGranted(dstId)) { - ::lookups::TalkgroupRuleGroupVoice groupVoice = tscc->m_tidLookup->find(dstId); + if (!tscc->s_affiliations->isGranted(dstId)) { + ::lookups::TalkgroupRuleGroupVoice groupVoice = tscc->s_tidLookup->find(dstId); slot = groupVoice.source().tgSlot(); - uint32_t availChNo = tscc->m_affiliations->getAvailableChannelForSlot(slot); - if (!tscc->m_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) { + uint32_t availChNo = tscc->s_affiliations->getAvailableChannelForSlot(slot); + if (!tscc->s_affiliations->rfCh()->isRFChAvailable() || availChNo == 0U) { if (grp) { if (!net) { LogWarning(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access, GRP_DATA_CALL (Group Data Call) queued, no channels available, dstId = %u", tscc->m_slotNo, dstId); @@ -1228,19 +1228,19 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } } else { - if (tscc->m_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net)) { - chNo = tscc->m_affiliations->getGrantedCh(dstId); - slot = tscc->m_affiliations->getGrantedSlot(dstId); + if (tscc->s_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net)) { + chNo = tscc->s_affiliations->getGrantedCh(dstId); + slot = tscc->s_affiliations->getGrantedSlot(dstId); //tscc->m_siteData.setChCnt(tscc->m_affiliations->getRFChCnt() + tscc->m_affiliations->getGrantedRFChCnt()); } } } else { - chNo = tscc->m_affiliations->getGrantedCh(dstId); - slot = tscc->m_affiliations->getGrantedSlot(dstId); + chNo = tscc->s_affiliations->getGrantedCh(dstId); + slot = tscc->s_affiliations->getGrantedSlot(dstId); - tscc->m_affiliations->touchGrant(dstId); + tscc->s_affiliations->touchGrant(dstId); } } @@ -1257,7 +1257,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } @@ -1270,8 +1270,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel - if (chNo != tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (chNo != tscc->s_channelNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1290,7 +1290,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } } else { - m_slot->m_dmr->tsccActivateSlot(slot, dstId, srcId, grp, false); + m_slot->s_dmr->tsccActivateSlot(slot, dstId, srcId, grp, false); } } else { @@ -1305,7 +1305,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u csbk->setSlotNo(slot); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, %s, emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", tscc->m_slotNo, csbk->toString().c_str(), emergency, privacy, broadcast, priority, csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } @@ -1318,8 +1318,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u writeRF_CSBK_Imm(csbk.get()); // if the channel granted isn't the same as the TSCC; remote activate the payload channel - if (chNo != tscc->m_channelNo) { - ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); + if (chNo != tscc->s_channelNo) { + ::lookups::VoiceChData voiceChData = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); req["dstId"].set(dstId); @@ -1338,7 +1338,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u } } else { - m_slot->m_dmr->tsccActivateSlot(slot, dstId, srcId, grp, false); + m_slot->s_dmr->tsccActivateSlot(slot, dstId, srcId, grp, false); } } @@ -1349,7 +1349,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOptions) { - Slot* tscc = m_slot->m_dmr->getTSCCSlot(); + Slot* tscc = m_slot->s_dmr->getTSCCSlot(); bool dereg = (serviceOptions & 0x01U) == 0x01U; uint8_t powerSave = (serviceOptions >> 1) & 0x07U; @@ -1360,7 +1360,7 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt csbk->setReason(ReasonCode::TS_DENY_RSN_REG_DENIED); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, SU power saving unsupported, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, SU power saving unsupported, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); } csbk->setSrcId(WUID_REGI); @@ -1376,14 +1376,14 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt if (!dereg) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); } // remove dynamic unit registration table entry - m_slot->m_affiliations->unitDereg(srcId); + m_slot->s_affiliations->unitDereg(srcId); -// if (m_slot->m_network != nullptr) -// m_slot->m_network->announceUnitDeregistration(srcId); +// if (m_slot->s_network != nullptr) +// m_slot->s_network->announceUnitDeregistration(srcId); csbk->setReason(ReasonCode::TS_ACK_RSN_REG); } @@ -1400,18 +1400,18 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt if (csbk->getReason() == ReasonCode::TS_ACK_RSN_REG) { if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, srcId = %u, serviceOptions = $%02X", tscc->m_slotNo, csbk->toString().c_str(), srcId, serviceOptions); } ::ActivityLog("DMR", true, "unit registration request from %u", srcId); // update dynamic unit registration table - if (!m_slot->m_affiliations->isUnitReg(srcId)) { - m_slot->m_affiliations->unitReg(srcId); + if (!m_slot->s_affiliations->isUnitReg(srcId)) { + m_slot->s_affiliations->unitReg(srcId); } - if (m_slot->m_network != nullptr) - m_slot->m_network->announceUnitRegistration(srcId); + if (m_slot->s_network != nullptr) + m_slot->s_network->announceUnitRegistration(srcId); } } @@ -1425,10 +1425,10 @@ void ControlSignaling::writeRF_CSBK_U_Reg_Rsp(uint32_t srcId, uint8_t serviceOpt void ControlSignaling::writeRF_CSBK_Grant_LateEntry(uint32_t dstId, uint32_t srcId, bool grp) { - Slot* tscc = m_slot->m_dmr->getTSCCSlot(); + Slot* tscc = m_slot->s_dmr->getTSCCSlot(); - uint32_t chNo = tscc->m_affiliations->getGrantedCh(dstId); - uint8_t slot = tscc->m_affiliations->getGrantedSlot(dstId); + uint32_t chNo = tscc->s_affiliations->getGrantedCh(dstId); + uint8_t slot = tscc->s_affiliations->getGrantedSlot(dstId); if (grp) { std::unique_ptr csbk = std::make_unique(); @@ -1480,18 +1480,18 @@ void ControlSignaling::writeRF_CSBK_Payload_Activate(uint32_t dstId, uint32_t sr csbk->setLastBlock(true); - csbk->setLogicalCh1(m_slot->m_channelNo); + csbk->setLogicalCh1(m_slot->s_channelNo); csbk->setSlotNo(m_slot->m_slotNo); csbk->setSrcId(srcId); csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, csbko = $%02X, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, csbko = $%02X, chNo = %u, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), csbk->getCSBKO(), csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } - m_slot->setShortLC_Payload(m_slot->m_siteData, 1U); + m_slot->setShortLC_Payload(m_slot->s_siteData, 1U); for (uint8_t i = 0; i < 2U; i++) writeRF_CSBK(csbk.get(), imm); } @@ -1506,14 +1506,14 @@ void ControlSignaling::writeRF_CSBK_Payload_Clear(uint32_t dstId, uint32_t srcId csbk->setLastBlock(true); - csbk->setLogicalCh1(m_slot->m_channelNo); + csbk->setLogicalCh1(m_slot->s_channelNo); csbk->setSlotNo(m_slot->m_slotNo); csbk->setSrcId(srcId); csbk->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, group = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, group = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, csbk->toString().c_str(), csbk->getGI(), csbk->getLogicalCh1(), csbk->getSlotNo(), srcId, dstId); } @@ -1527,8 +1527,8 @@ void ControlSignaling::writeRF_TSCC_Aloha() { std::unique_ptr csbk = std::make_unique(); DEBUG_LOG_CSBK(csbk->toString()); - csbk->setNRandWait(m_slot->m_alohaNRandWait); - csbk->setBackoffNo(m_slot->m_alohaBackOff); + csbk->setNRandWait(m_slot->s_alohaNRandWait); + csbk->setBackoffNo(m_slot->s_alohaBackOff); writeRF_CSBK(csbk.get()); } @@ -1540,7 +1540,7 @@ void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd, m_slot->m_rfSeqNo = 0U; std::unique_ptr csbk = std::make_unique(); - csbk->siteIdenEntry(m_slot->m_idenEntry); + csbk->siteIdenEntry(m_slot->s_idenEntry); csbk->setCdef(false); csbk->setAnncType(BroadcastAnncType::ANN_WD_TSCC); csbk->setLogicalCh1(channelNo); @@ -1549,7 +1549,7 @@ void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd, csbk->setRequireReg(requireReg); if (m_debug) { - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, channelNo = %u, annWd = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, channelNo = %u, annWd = %u", m_slot->m_slotNo, csbk->toString().c_str(), channelNo, annWd); } diff --git a/src/host/dmr/packet/Data.cpp b/src/host/dmr/packet/Data.cpp index 22bc1d49e..6d602d558 100644 --- a/src/host/dmr/packet/Data.cpp +++ b/src/host/dmr/packet/Data.cpp @@ -45,7 +45,7 @@ bool Data::process(uint8_t* data, uint32_t len) DataType::E dataType = (DataType::E)(data[1U] & 0x0FU); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(dataType); if (dataType == DataType::TERMINATOR_WITH_LC) { @@ -60,7 +60,7 @@ bool Data::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); if (!m_slot->m_rfTimeout) { data[0U] = modem::TAG_EOT; @@ -68,21 +68,21 @@ bool Data::process(uint8_t* data, uint32_t len) m_slot->writeNetwork(data, DataType::TERMINATOR_WITH_LC, 0U); - if (m_slot->m_duplex) { - for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + if (m_slot->s_duplex) { + for (uint32_t i = 0U; i < m_slot->s_hangCount; i++) m_slot->addFrame(data); } } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_rfLC->getDstId()); + LogInfoEx(LOG_RF, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_rfLC->getDstId()); } // release trunked grant (if necessary) - Slot *m_tscc = m_slot->m_dmr->getTSCCSlot(); + Slot *m_tscc = m_slot->s_dmr->getTSCCSlot(); if (m_tscc != nullptr) { if (m_tscc->m_enableTSCC) { - m_tscc->m_affiliations->releaseGrant(m_slot->m_rfLC->getDstId(), false); + m_tscc->s_affiliations->releaseGrant(m_slot->m_rfLC->getDstId(), false); m_slot->clearTSCCActivated(); } } @@ -97,10 +97,10 @@ bool Data::process(uint8_t* data, uint32_t len) m_slot->m_slotNo, float(m_slot->m_rfFrames) / 16.667F, float(m_slot->m_rfErrs * 100U) / float(m_slot->m_rfBits)); } - LogMessage(LOG_RF, "DMR Slot %u, total frames: %d, total bits: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, "DMR Slot %u, total frames: %d, total bits: %d, errors: %d, BER: %.4f%%", m_slot->m_slotNo, m_slot->m_rfFrames, m_slot->m_rfBits, m_slot->m_rfErrs, float(m_slot->m_rfErrs * 100U) / float(m_slot->m_rfBits)); - m_slot->m_dmr->tsccClearActivatedSlot(m_slot->m_slotNo); + m_slot->s_dmr->tsccClearActivatedSlot(m_slot->m_slotNo); if (m_slot->m_rfTimeout) { m_slot->writeEndRF(); @@ -124,7 +124,7 @@ bool Data::process(uint8_t* data, uint32_t len) uint32_t srcId = m_rfDataHeader.getSrcId(); uint32_t dstId = m_rfDataHeader.getDstId(); - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + if (!m_slot->s_authoritative && m_slot->m_permittedDstId != dstId) { if (!g_disableNonAuthoritativeLogging) LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted!"); m_slot->m_rfState = RS_RF_LISTENING; @@ -139,7 +139,7 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { - if (m_slot->m_affiliations->isNetGranted(dstId)) { + if (m_slot->s_affiliations->isNetGranted(dstId)) { LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); m_slot->m_rfState = RS_RF_LISTENING; return false; @@ -173,34 +173,34 @@ bool Data::process(uint8_t* data, uint32_t len) m_slot->m_rfLC = std::make_unique(gi ? FLCO::GROUP : FLCO::PRIVATE, srcId, dstId); if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_DATA_HEADER ", slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", - m_slot->m_slotNo, m_rfDataHeader.getDPF(), m_rfDataHeader.getA(), m_rfDataHeader.getSAP(), m_rfDataHeader.getFullMesage(), m_rfDataHeader.getBlocksToFollow(), m_rfDataHeader.getPadLength(), m_rfDataHeader.getPacketLength(), + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER ", slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", + m_slot->m_slotNo, m_rfDataHeader.getDPF(), m_rfDataHeader.getA(), m_rfDataHeader.getSAP(), m_rfDataHeader.getFullMesage(), m_rfDataHeader.getBlocksToFollow(), m_rfDataHeader.getPadLength(), m_rfDataHeader.getPacketLength(dataType), m_rfDataHeader.getFSN(), dstId, srcId, gi); } // did we receive a response header? if (m_rfDataHeader.getDPF() == DPF::RESPONSE) { if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, slot = %u, sap = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, slot = %u, sap = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, m_rfDataHeader.getSAP(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), dstId, srcId, gi); if (m_rfDataHeader.getResponseClass() == PDUResponseClass::ACK && m_rfDataHeader.getResponseType() == PDUResponseType::ACK) { - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP ACK, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP ACK, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); } else { if (m_rfDataHeader.getResponseClass() == PDUResponseClass::NACK) { switch (m_rfDataHeader.getResponseType()) { case PDUResponseType::NACK_ILLEGAL: - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, illegal format, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, illegal format, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; case PDUResponseType::NACK_PACKET_CRC: - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet CRC error, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet CRC error, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; case PDUResponseType::NACK_UNDELIVERABLE: - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet undeliverable, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet undeliverable, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; @@ -208,7 +208,7 @@ bool Data::process(uint8_t* data, uint32_t len) break; } } else if (m_rfDataHeader.getResponseClass() == PDUResponseClass::ACK_RETRY) { - LogMessage(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP ACK RETRY, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_RF, DMR_DT_DATA_HEADER " ISP, response, OSP ACK RETRY, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); } } @@ -222,12 +222,12 @@ bool Data::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = m_slot->m_rfFrames == 0U ? modem::TAG_EOT : modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex && m_repeatDataPacket) + if (m_slot->s_duplex && m_repeatDataPacket) m_slot->addFrame(data); uint8_t controlByte = 0U; @@ -245,6 +245,7 @@ bool Data::process(uint8_t* data, uint32_t len) } ::ActivityLog("DMR", true, "Slot %u RF data header from %u to %s%u, %u blocks", m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId, m_slot->m_rfFrames); + LogInfoEx(LOG_RF, "DMR Data Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); ::memset(m_pduUserData, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U); m_pduDataOffset = 0U; @@ -274,12 +275,12 @@ bool Data::process(uint8_t* data, uint32_t len) if (m_verbose) { if (dataType == DataType::RATE_34_DATA) { - LogMessage(LOG_RF, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_RF, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } else if (dataType == DataType::RATE_12_DATA) { - LogMessage(LOG_RF, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_RF, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } else { - LogMessage(LOG_RF, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_RF, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_rfDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } } @@ -288,7 +289,20 @@ bool Data::process(uint8_t* data, uint32_t len) } if (m_rfDataHeader.getBlocksToFollow() > 0U && m_slot->m_rfFrames == 0U) { - bool crcRet = edac::CRC::checkCRC32(m_pduUserData, m_pduDataOffset); + // ooookay -- lets do the insane, and ridiculously stupid, ETSI Big-Endian reversed byte ordering bullshit for the CRC-32 + uint8_t crcBytes[MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U]; + ::memset(crcBytes, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U); + for (uint8_t i = 0U; i < m_pduDataOffset - 4U; i += 2U) { + crcBytes[i + 1U] = m_pduUserData[i]; + crcBytes[i] = m_pduUserData[i + 1U]; + } + + crcBytes[m_pduDataOffset - 1U] = m_pduUserData[m_pduDataOffset - 4U]; + crcBytes[m_pduDataOffset - 2U] = m_pduUserData[m_pduDataOffset - 3U]; + crcBytes[m_pduDataOffset - 3U] = m_pduUserData[m_pduDataOffset - 2U]; + crcBytes[m_pduDataOffset - 4U] = m_pduUserData[m_pduDataOffset - 1U]; + + bool crcRet = edac::CRC::checkInvertedCRC32(crcBytes, m_pduDataOffset); if (!crcRet) { LogWarning(LOG_RF, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_rfDataHeader.getBlocksToFollow(), m_pduDataOffset); } @@ -305,16 +319,16 @@ bool Data::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); m_slot->writeNetwork(data, dataType, 0U); - if (m_slot->m_duplex && m_repeatDataPacket) { + if (m_slot->s_duplex && m_repeatDataPacket) { m_slot->addFrame(data); } if (m_slot->m_rfFrames == 0U) { - LogMessage(LOG_RF, "DMR Slot %u, RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); + LogInfoEx(LOG_RF, "DMR Slot %u, RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); m_slot->writeEndRF(); } @@ -343,19 +357,19 @@ void Data::processNetwork(const data::NetData& dmrData) // Regenerate the Slot Type SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::TERMINATOR_WITH_LC); slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); if (!m_slot->m_netTimeout) { data[0U] = modem::TAG_EOT; data[1U] = 0x00U; - if (m_slot->m_duplex) { - for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + if (m_slot->s_duplex) { + for (uint32_t i = 0U; i < m_slot->s_hangCount; i++) m_slot->addFrame(data, true); } else { @@ -365,14 +379,14 @@ void Data::processNetwork(const data::NetData& dmrData) } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_netLC->getDstId()); + LogInfoEx(LOG_RF, DMR_DT_TERMINATOR_WITH_LC ", slot = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_netLC->getDstId()); } // release trunked grant (if necessary) - Slot *m_tscc = m_slot->m_dmr->getTSCCSlot(); + Slot *m_tscc = m_slot->s_dmr->getTSCCSlot(); if (m_tscc != nullptr) { if (m_tscc->m_enableTSCC) { - m_tscc->m_affiliations->releaseGrant(m_slot->m_netLC->getDstId(), false); + m_tscc->s_affiliations->releaseGrant(m_slot->m_netLC->getDstId(), false); m_slot->clearTSCCActivated(); } } @@ -382,7 +396,7 @@ void Data::processNetwork(const data::NetData& dmrData) ::ActivityLog("DMR", false, "Slot %u network end of voice transmission, %.1f seconds, %u%% packet loss, BER: %.1f%%", m_slot->m_slotNo, float(m_slot->m_netFrames) / 16.667F, (m_slot->m_netLost * 100U) / m_slot->m_netFrames, float(m_slot->m_netErrs * 100U) / float(m_slot->m_netBits)); - m_slot->m_dmr->tsccClearActivatedSlot(m_slot->m_slotNo); + m_slot->s_dmr->tsccClearActivatedSlot(m_slot->m_slotNo); m_slot->writeEndNet(); } @@ -401,7 +415,7 @@ void Data::processNetwork(const data::NetData& dmrData) uint32_t srcId = m_netDataHeader.getSrcId(); uint32_t dstId = m_netDataHeader.getDstId(); - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != dstId) { + if (!m_slot->s_authoritative && m_slot->m_permittedDstId != dstId) { return; } @@ -423,26 +437,26 @@ void Data::processNetwork(const data::NetData& dmrData) // did we receive a response header? if (m_netDataHeader.getDPF() == DPF::RESPONSE) { if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, slot = %u, sap = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, slot = %u, sap = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, m_netDataHeader.getSAP(), m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(), dstId, srcId, gi); if (m_netDataHeader.getResponseClass() == PDUResponseClass::ACK && m_netDataHeader.getResponseType() == PDUResponseType::ACK) { - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP ACK, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP ACK, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); } else { if (m_netDataHeader.getResponseClass() == PDUResponseClass::NACK) { switch (m_netDataHeader.getResponseType()) { case PDUResponseType::NACK_ILLEGAL: - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, illegal format, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, illegal format, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; case PDUResponseType::NACK_PACKET_CRC: - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet CRC error, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet CRC error, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; case PDUResponseType::NACK_UNDELIVERABLE: - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet undeliverable, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP NACK, packet undeliverable, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); break; @@ -450,7 +464,7 @@ void Data::processNetwork(const data::NetData& dmrData) break; } } else if (m_netDataHeader.getResponseClass() == PDUResponseClass::ACK_RETRY) { - LogMessage(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP ACK RETRY, slot = %u, dstId = %u, srcId = %u, group = %u", + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER " ISP, response, OSP ACK RETRY, slot = %u, dstId = %u, srcId = %u, group = %u", m_slot->m_slotNo, dstId, srcId, gi); } } @@ -462,19 +476,19 @@ void Data::processNetwork(const data::NetData& dmrData) // Regenerate the Slot Type SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::DATA_HEADER); slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = m_slot->m_netFrames == 0U ? modem::TAG_EOT : modem::TAG_DATA; data[1U] = 0x00U; // Put a small delay into starting transmission - m_slot->addFrame(m_slot->m_idle, true); - m_slot->addFrame(m_slot->m_idle, true); + m_slot->addFrame(m_slot->s_idle, true); + m_slot->addFrame(m_slot->s_idle, true); m_slot->addFrame(data, true); @@ -485,13 +499,14 @@ void Data::processNetwork(const data::NetData& dmrData) m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::PRIVATE, Slot::SLCO_ACT_TYPE::DATA); if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_DATA_HEADER ", slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", - m_slot->m_slotNo, m_netDataHeader.getDPF(), m_netDataHeader.getA(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMesage(), m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getPacketLength(), + LogInfoEx(LOG_NET, DMR_DT_DATA_HEADER ", slot = %u, dpf = $%02X, ack = %u, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, seqNo = %u, dstId = %u, srcId = %u, group = %u", + m_slot->m_slotNo, m_netDataHeader.getDPF(), m_netDataHeader.getA(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMesage(), m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getPacketLength(dataType), m_netDataHeader.getFSN(), dstId, srcId, gi); } ::ActivityLog("DMR", false, "Slot %u network data header from %u to %s%u, %u blocks", m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId, m_slot->m_netFrames); + LogInfoEx(LOG_NET, "DMR Data Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); ::memset(m_pduUserData, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U); m_pduDataOffset = 0U; @@ -521,12 +536,12 @@ void Data::processNetwork(const data::NetData& dmrData) if (m_verbose) { if (dataType == DataType::RATE_34_DATA) { - LogMessage(LOG_NET, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_NET, DMR_DT_RATE_34_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } else if (dataType == DataType::RATE_12_DATA) { - LogMessage(LOG_NET, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_NET, DMR_DT_RATE_12_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } else { - LogMessage(LOG_NET, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); + LogInfoEx(LOG_NET, DMR_DT_RATE_1_DATA ", ISP, block %u, dataType = $%02X, dpf = $%02X", m_netDataBlockCnt, dataBlock.getDataType(), dataBlock.getFormat()); } } @@ -535,7 +550,20 @@ void Data::processNetwork(const data::NetData& dmrData) } if (m_netDataHeader.getBlocksToFollow() > 0U && m_slot->m_netFrames == 0U) { - bool crcRet = edac::CRC::checkCRC32(m_pduUserData, m_pduDataOffset); + // ooookay -- lets do the insane, and ridiculously stupid, ETSI Big-Endian reversed byte ordering bullshit for the CRC-32 + uint8_t crcBytes[MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U]; + ::memset(crcBytes, 0x00U, MAX_PDU_COUNT * DMR_PDU_UNCODED_LENGTH_BYTES + 2U); + for (uint8_t i = 0U; i < m_pduDataOffset - 4U; i += 2U) { + crcBytes[i + 1U] = m_pduUserData[i]; + crcBytes[i] = m_pduUserData[i + 1U]; + } + + crcBytes[m_pduDataOffset - 1U] = m_pduUserData[m_pduDataOffset - 4U]; + crcBytes[m_pduDataOffset - 2U] = m_pduUserData[m_pduDataOffset - 3U]; + crcBytes[m_pduDataOffset - 3U] = m_pduUserData[m_pduDataOffset - 2U]; + crcBytes[m_pduDataOffset - 4U] = m_pduUserData[m_pduDataOffset - 1U]; + + bool crcRet = edac::CRC::checkInvertedCRC32(crcBytes, m_pduDataOffset); if (!crcRet) { LogWarning(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_netDataHeader.getBlocksToFollow(), m_pduDataOffset); } @@ -549,11 +577,11 @@ void Data::processNetwork(const data::NetData& dmrData) // regenerate the Slot Type SlotType slotType; slotType.decode(data + 2U); - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.encode(data + 2U); // convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = m_slot->m_netFrames == 0U ? modem::TAG_EOT : modem::TAG_DATA; data[1U] = 0x00U; @@ -562,7 +590,7 @@ void Data::processNetwork(const data::NetData& dmrData) } if (m_slot->m_netFrames == 0U) { - LogMessage(LOG_NET, "DMR Slot %u, RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); + LogInfoEx(LOG_NET, "DMR Slot %u, RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); m_slot->writeEndNet(); } } @@ -627,21 +655,21 @@ void Data::writeRF_PDU(DataType::E dataType, const uint8_t* pdu) ::memcpy(data, pdu, DMR_FRAME_LENGTH_BYTES); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(dataType); // Regenerate the Slot Type slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); m_slot->m_rfSeqNo = 0U; data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); } @@ -674,8 +702,8 @@ void Data::writeRF_PDU_Ack_Response(uint8_t rspClass, uint8_t rspType, uint8_t r ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); // decode the BPTC (196,96) FEC - uint8_t payload[DMR_PDU_UNCONFIRMED_LENGTH_BYTES]; - ::memset(payload, 0x00U, DMR_PDU_UNCONFIRMED_LENGTH_BYTES); + uint8_t payload[DMR_PDU_UNCODED_LENGTH_BYTES]; + ::memset(payload, 0x00U, DMR_PDU_UNCODED_LENGTH_BYTES); // encode the BPTC (196,96) FEC bptc.encode(payload, data + 2U); diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index ca574e96d..e5b55e095 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -35,7 +35,7 @@ using namespace dmr::packet; // Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ + if (!m_slot->s_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ if (!g_disableNonAuthoritativeLogging) \ LogWarning(LOG_RF, "[NON-AUTHORITATIVE] Ignoring RF traffic, destination not permitted, dstId = %u", _DST_ID); \ m_slot->m_rfState = RS_RF_LISTENING; \ @@ -44,7 +44,7 @@ using namespace dmr::packet; // Helper macro to check if the host is authoritative and the destination ID is permitted. #define CHECK_NET_AUTHORITATIVE(_DST_ID) \ - if (!m_slot->m_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ + if (!m_slot->s_authoritative && m_slot->m_permittedDstId != _DST_ID) { \ return; \ } @@ -65,7 +65,7 @@ bool Voice::process(uint8_t* data, uint32_t len) DataType::E dataType = (DataType::E)(data[1U] & 0x0FU); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(dataType); if (dataType == DataType::VOICE_LC_HEADER) { @@ -123,20 +123,20 @@ bool Voice::process(uint8_t* data, uint32_t len) // are we auto-registering legacy radios to groups? if (m_slot->m_legacyGroupReg) { - if (!m_slot->m_affiliations->isGroupAff(srcId, dstId)) { + if (!m_slot->s_affiliations->isGroupAff(srcId, dstId)) { // update dynamic unit registration table - if (!m_slot->m_affiliations->isUnitReg(srcId)) { - m_slot->m_affiliations->unitReg(srcId); + if (!m_slot->s_affiliations->isUnitReg(srcId)) { + m_slot->s_affiliations->unitReg(srcId); } - if (m_slot->m_network != nullptr) - m_slot->m_network->announceUnitRegistration(srcId); + if (m_slot->s_network != nullptr) + m_slot->s_network->announceUnitRegistration(srcId); // update dynamic affiliation table - m_slot->m_affiliations->groupAff(srcId, dstId); + m_slot->s_affiliations->groupAff(srcId, dstId); - if (m_slot->m_network != nullptr) - m_slot->m_network->announceGroupAffiliation(srcId, dstId); + if (m_slot->s_network != nullptr) + m_slot->s_network->announceGroupAffiliation(srcId, dstId); } } } @@ -163,7 +163,7 @@ bool Voice::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -185,9 +185,9 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_aveRSSI = m_slot->m_rssi; m_slot->m_rssiCount = 1U; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { m_slot->m_txQueue.clear(); - m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + m_slot->s_modem->writeDMRAbort(m_slot->m_slotNo); for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) m_slot->addFrame(data); @@ -210,11 +210,12 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, + LogInfoEx(LOG_RF, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), m_slot->m_rfLC->getFLCO(), m_slot->m_rfLC->getFID(), m_slot->m_rfLC->getPF()); } ::ActivityLog("DMR", true, "Slot %u RF %svoice header from %u to %s%u", m_slot->m_slotNo, encrypted ? "encrypted " : "", srcId, flco == FLCO::GROUP ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "DMR Voice Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); return true; } else if (dataType == DataType::VOICE_PI_HEADER) { @@ -238,24 +239,24 @@ bool Voice::process(uint8_t* data, uint32_t len) slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); m_slot->writeNetwork(data, DataType::VOICE_PI_HEADER, 0U); if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_slot->m_slotNo, + LogInfoEx(LOG_RF, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_rfPrivacyLC->getAlgId(), m_slot->m_rfPrivacyLC->getKId(), m_slot->m_rfPrivacyLC->getDstId()); uint8_t mi[MI_LENGTH_BYTES]; m_slot->m_rfPrivacyLC->getMI(mi); - LogMessage(LOG_RF, DMR_DT_VOICE_PI_HEADER ", slot = %u, Enc Sync, MI = %02X %02X %02X %02X", + LogInfoEx(LOG_RF, DMR_DT_VOICE_PI_HEADER ", slot = %u, Enc Sync, MI = %02X %02X %02X %02X", m_slot->m_slotNo, mi[0U], mi[1U], mi[2U], mi[3U]); } @@ -270,7 +271,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_lastRfN = 0U; // convert the Audio Sync to be from the BS or MS as needed - Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + Sync::addDMRAudioSync(data + 2U, m_slot->s_duplex); uint32_t errors = 0U; uint8_t fid = m_slot->m_rfLC->getFID(); @@ -293,7 +294,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + LogInfoEx(LOG_RF, DMR_DT_VOICE_SYNC ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = 0, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), fid, pf, errors, float(errors) / 1.41F); } @@ -313,7 +314,7 @@ bool Voice::process(uint8_t* data, uint32_t len) data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); m_slot->writeNetwork(data, DataType::VOICE_SYNC, 0U, errors); @@ -368,7 +369,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), + LogInfoEx(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_rfLC->getSrcId(), m_slot->m_rfLC->getDstId(), m_rfN, fid, pf, errors, float(errors) / 1.41F); } @@ -468,7 +469,7 @@ bool Voice::process(uint8_t* data, uint32_t len) lcss = m_rfEmbeddedLC.getData(data + 2U, m_rfN); // Regenerate the EMB - emb.setColorCode(m_slot->m_colorCode); + emb.setColorCode(m_slot->s_colorCode); emb.setLCSS(lcss); emb.encode(data + 2U); @@ -483,12 +484,12 @@ bool Voice::process(uint8_t* data, uint32_t len) lcss = m_rfEmbeddedLC.getData(data + 2U, m_rfN); // Regenerate the EMB - emb.setColorCode(m_slot->m_colorCode); + emb.setColorCode(m_slot->s_colorCode); emb.setLCSS(lcss); emb.encode(data + 2U); } - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); return true; @@ -502,7 +503,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // If we haven't received an LC yet, then be strict on the color code uint8_t colorCode = emb.getColorCode(); - if (colorCode != m_slot->m_colorCode) + if (colorCode != m_slot->s_colorCode) return false; m_rfEmbeddedLC.addData(data + 2U, emb.getLCSS()); @@ -543,13 +544,13 @@ bool Voice::process(uint8_t* data, uint32_t len) // Create a dummy start frame to replace the received frame uint8_t start[DMR_FRAME_LENGTH_BYTES + 2U]; - Sync::addDMRDataSync(start + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(start + 2U, m_slot->s_duplex); lc::FullLC fullLC; fullLC.encode(*m_slot->m_rfLC, start + 2U, DataType::VOICE_LC_HEADER); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::VOICE_LC_HEADER); slotType.encode(start + 2U); @@ -573,9 +574,9 @@ bool Voice::process(uint8_t* data, uint32_t len) m_slot->m_aveRSSI = m_slot->m_rssi; m_slot->m_rssiCount = 1U; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { m_slot->m_txQueue.clear(); - m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + m_slot->s_modem->writeDMRAbort(m_slot->m_slotNo); for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) m_slot->addFrame(start); @@ -629,7 +630,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + LogInfoEx(LOG_RF, DMR_DT_VOICE ", audio, slot = %u, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_rfN, fid, pf, errors, float(errors) / 1.41F); } @@ -639,7 +640,7 @@ bool Voice::process(uint8_t* data, uint32_t len) data[0U] = modem::TAG_DATA; data[1U] = 0x00U; - if (m_slot->m_duplex) + if (m_slot->s_duplex) m_slot->addFrame(data); m_slot->writeNetwork(data, DataType::VOICE, 0U, errors); @@ -656,6 +657,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } ::ActivityLog("DMR", true, "Slot %u RF late entry from %u to %s%u", m_slot->m_slotNo, srcId, flco == FLCO::GROUP ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "DMR Voice Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); return true; } } @@ -703,7 +705,7 @@ void Voice::processNetwork(const data::NetData& dmrData) srcId, flco == FLCO::GROUP ? "TG" : "", dstId); if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, VOICE_LC_HEADER, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, lc->getSrcId(), lc->getDstId(), lc->getFLCO(), lc->getFID(), lc->getPF()); + LogInfoEx(LOG_NET, "DMR Slot %u, VOICE_LC_HEADER, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, lc->getSrcId(), lc->getDstId(), lc->getFLCO(), lc->getFID(), lc->getPF()); } m_slot->m_netLC = std::move(lc); @@ -718,12 +720,12 @@ void Voice::processNetwork(const data::NetData& dmrData) // Regenerate the Slot Type SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::VOICE_LC_HEADER); slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -742,15 +744,15 @@ void Voice::processNetwork(const data::NetData& dmrData) m_slot->m_voice->m_netEmbeddedWriteN = 1U; m_slot->m_voice->m_netTalkerId = TalkerID::NONE; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { m_slot->m_txQueue.clear(); - m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + m_slot->s_modem->writeDMRAbort(m_slot->m_slotNo); } - for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) - m_slot->addFrame(m_slot->m_idle, true); + for (uint32_t i = 0U; i < m_slot->s_jitterSlots; i++) + m_slot->addFrame(m_slot->s_idle, true); - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) m_slot->addFrame(data, true); } @@ -767,11 +769,12 @@ void Voice::processNetwork(const data::NetData& dmrData) m_slot->setShortLC(m_slot->m_slotNo, dstId, flco, Slot::SLCO_ACT_TYPE::VOICE); if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, + LogInfoEx(LOG_NET, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X, FID = $%02X, PF = %u", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), m_slot->m_netLC->getFLCO(), m_slot->m_netLC->getFID(), m_slot->m_netLC->getPF()); } ::ActivityLog("DMR", false, "Slot %u network voice header from %u to %s%u", m_slot->m_slotNo, srcId, flco == FLCO::GROUP ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "DMR Voice Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); } else if (dataType == DataType::VOICE_PI_HEADER) { if (m_slot->m_netState != RS_NET_AUDIO) { @@ -792,31 +795,31 @@ void Voice::processNetwork(const data::NetData& dmrData) m_slot->m_netTimeoutTimer.start(); m_slot->m_netTimeout = false; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { m_slot->m_txQueue.clear(); - m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + m_slot->s_modem->writeDMRAbort(m_slot->m_slotNo); } - for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) - m_slot->addFrame(m_slot->m_idle, true); + for (uint32_t i = 0U; i < m_slot->s_jitterSlots; i++) + m_slot->addFrame(m_slot->s_idle, true); // Create a dummy start frame uint8_t start[DMR_FRAME_LENGTH_BYTES + 2U]; - Sync::addDMRDataSync(start + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(start + 2U, m_slot->s_duplex); lc::FullLC fullLC; fullLC.encode(*m_slot->m_netLC, start + 2U, DataType::VOICE_LC_HEADER); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::VOICE_LC_HEADER); slotType.encode(start + 2U); start[0U] = modem::TAG_DATA; start[1U] = 0x00U; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) m_slot->addFrame(start); } @@ -839,6 +842,7 @@ void Voice::processNetwork(const data::NetData& dmrData) ::ActivityLog("DMR", false, "Slot %u network late entry from %u to %s%u", m_slot->m_slotNo, srcId, m_slot->m_netLC->getFLCO() == FLCO::GROUP ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "DMR Voice Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); } lc::FullLC fullLC; @@ -856,12 +860,12 @@ void Voice::processNetwork(const data::NetData& dmrData) // Regenerate the Slot Type SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::VOICE_PI_HEADER); slotType.encode(data + 2U); // Convert the Data Sync to be from the BS or MS as needed - Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(data + 2U, m_slot->s_duplex); data[0U] = modem::TAG_DATA; data[1U] = 0x00U; @@ -869,13 +873,13 @@ void Voice::processNetwork(const data::NetData& dmrData) m_slot->addFrame(data, true); if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_slot->m_slotNo, + LogInfoEx(LOG_NET, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_slot->m_slotNo, m_slot->m_netPrivacyLC->getAlgId(), m_slot->m_netPrivacyLC->getKId(), m_slot->m_netPrivacyLC->getDstId()); uint8_t mi[MI_LENGTH_BYTES]; m_slot->m_netPrivacyLC->getMI(mi); - LogMessage(LOG_NET, DMR_DT_VOICE_PI_HEADER ", slot = %u, Enc Sync, MI = %02X %02X %02X %02X", + LogInfoEx(LOG_NET, DMR_DT_VOICE_PI_HEADER ", slot = %u, Enc Sync, MI = %02X %02X %02X %02X", m_slot->m_slotNo, mi[0U], mi[1U], mi[2U], mi[3U]); } } @@ -900,31 +904,31 @@ void Voice::processNetwork(const data::NetData& dmrData) m_slot->m_netTimeoutTimer.start(); m_slot->m_netTimeout = false; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { m_slot->m_txQueue.clear(); - m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + m_slot->s_modem->writeDMRAbort(m_slot->m_slotNo); } - for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) - m_slot->addFrame(m_slot->m_idle, true); + for (uint32_t i = 0U; i < m_slot->s_jitterSlots; i++) + m_slot->addFrame(m_slot->s_idle, true); // Create a dummy start frame uint8_t start[DMR_FRAME_LENGTH_BYTES + 2U]; - Sync::addDMRDataSync(start + 2U, m_slot->m_duplex); + Sync::addDMRDataSync(start + 2U, m_slot->s_duplex); lc::FullLC fullLC; fullLC.encode(*m_slot->m_netLC, start + 2U, DataType::VOICE_LC_HEADER); SlotType slotType; - slotType.setColorCode(m_slot->m_colorCode); + slotType.setColorCode(m_slot->s_colorCode); slotType.setDataType(DataType::VOICE_LC_HEADER); slotType.encode(start + 2U); start[0U] = modem::TAG_DATA; start[1U] = 0x00U; - if (m_slot->m_duplex) { + if (m_slot->s_duplex) { for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) m_slot->addFrame(start); } @@ -951,6 +955,7 @@ void Voice::processNetwork(const data::NetData& dmrData) ::ActivityLog("DMR", false, "Slot %u network late entry from %u to %s%u", m_slot->m_slotNo, srcId, m_slot->m_netLC->getFLCO() == FLCO::GROUP ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "DMR Voice Call, slot = %u, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); } if (m_slot->m_netState == RS_NET_AUDIO) { @@ -965,7 +970,7 @@ void Voice::processNetwork(const data::NetData& dmrData) } if (m_verbose) { - LogMessage(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", + LogInfoEx(LOG_NET, "DMR Slot %u, VOICE_SYNC audio, sequence no = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); } @@ -979,7 +984,7 @@ void Voice::processNetwork(const data::NetData& dmrData) data[1U] = 0x00U; // Convert the Audio Sync to be from the BS or MS as needed - Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + Sync::addDMRAudioSync(data + 2U, m_slot->s_duplex); // Initialise the lost packet data if (m_slot->m_netFrames == 0U) { @@ -1021,7 +1026,7 @@ void Voice::processNetwork(const data::NetData& dmrData) } if (m_verbose) { - LogMessage(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), + LogInfoEx(LOG_NET, DMR_DT_VOICE ", audio, slot = %u, srcId = %u, dstId = %u, seqNo = %u, fid = $%02X, pf = %u, errs = %u/141 (%.1f%%)", m_slot->m_slotNo, m_slot->m_netLC->getSrcId(), m_slot->m_netLC->getDstId(), m_netN, fid, pf, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); } @@ -1114,7 +1119,7 @@ void Voice::processNetwork(const data::NetData& dmrData) } // Regenerate the EMB - emb.setColorCode(m_slot->m_colorCode); + emb.setColorCode(m_slot->s_colorCode); emb.setLCSS(lcss); emb.encode(data + 2U); @@ -1205,7 +1210,7 @@ bool Voice::checkRFTrafficCollision(uint32_t dstId) } if (m_slot->m_enableTSCC && dstId == m_slot->m_netLastDstId) { - if (m_slot->m_affiliations->isNetGranted(dstId)) { + if (m_slot->s_affiliations->isNetGranted(dstId)) { LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing granted network traffic (Are we in a voting condition?)", m_slot->m_slotNo); m_slot->m_rfState = RS_RF_LISTENING; return true; @@ -1295,7 +1300,7 @@ void Voice::logGPSPosition(const uint32_t srcId, const uint8_t* data) longitude *= float(longitudeVal); latitude *= float(latitudeVal); - LogMessage(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); + LogInfoEx(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); } /* Helper to insert AMBE null frames for missing audio. */ @@ -1369,7 +1374,7 @@ void Voice::insertSilence(uint32_t count) uint8_t fid = m_slot->m_netLC->getFID(); data::EMB emb; - emb.setColorCode(m_slot->m_colorCode); + emb.setColorCode(m_slot->s_colorCode); for (uint32_t i = 0U; i < count; i++) { // only use our silence frame if its AMBE audio data @@ -1381,7 +1386,7 @@ void Voice::insertSilence(uint32_t count) } if (n == 0U) { - Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + Sync::addDMRAudioSync(data + 2U, m_slot->s_duplex); } else { uint8_t lcss = m_netEmbeddedLC.getData(data + 2U, n); diff --git a/src/host/modem/Modem.cpp b/src/host/modem/Modem.cpp index 578b24815..b565ff147 100644 --- a/src/host/modem/Modem.cpp +++ b/src/host/modem/Modem.cpp @@ -449,7 +449,7 @@ void Modem::setCloseHandler(std::function handler) bool Modem::open() { - LogMessage(LOG_MODEM, "Initializing modem"); + LogInfoEx(LOG_MODEM, "Initializing modem"); m_gotModemStatus = false; bool ret = m_port->open(); @@ -512,7 +512,7 @@ bool Modem::open() m_error = false; - LogMessage(LOG_MODEM, "Modem Ready [Direct Mode]"); + LogInfoEx(LOG_MODEM, "Modem Ready [Direct Mode]"); return true; } @@ -907,7 +907,7 @@ void Modem::clock(uint32_t ms) void Modem::close() { - LogMessage(LOG_MODEM, "Closing the modem"); + LogInfoEx(LOG_MODEM, "Closing the modem"); m_port->close(); m_gotModemStatus = false; @@ -1821,7 +1821,7 @@ bool Modem::getFirmwareVersion() continue; if (resp == RTM_OK && m_buffer[2U] == CMD_GET_VERSION) { - LogMessage(LOG_MODEM, "Protocol: %02x, CPU: %02X", m_buffer[3U], m_buffer[4U]); + LogInfoEx(LOG_MODEM, "Protocol: %02x, CPU: %02X", m_buffer[3U], m_buffer[4U]); m_protoVer = m_buffer[3U]; if (m_protoVer >= 2U) { @@ -1832,19 +1832,19 @@ bool Modem::getFirmwareVersion() switch (m_buffer[4U]) { case 0U: - LogMessage(LOG_MODEM, "Atmel ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U]); + LogInfoEx(LOG_MODEM, "Atmel ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U]); break; case 1U: - LogMessage(LOG_MODEM, "NXP ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U]); + LogInfoEx(LOG_MODEM, "NXP ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U]); break; case 2U: - LogMessage(LOG_MODEM, "ST-Micro ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U]); + LogInfoEx(LOG_MODEM, "ST-Micro ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[5U], m_buffer[6U], m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U]); break; case 15U: - LogMessage(LOG_MODEM, "Null Modem, UDID: N/A"); + LogInfoEx(LOG_MODEM, "Null Modem, UDID: N/A"); break; default: - LogMessage(LOG_MODEM, "Unknown CPU type: %u", m_buffer[4U]); + LogInfoEx(LOG_MODEM, "Unknown CPU type: %u", m_buffer[4U]); break; } @@ -2206,7 +2206,7 @@ bool Modem::readFlash() void Modem::processFlashConfig(const uint8_t *buffer) { if (m_ignoreModemConfigArea) { - LogMessage(LOG_MODEM, "Modem configuration area checking is disabled!"); + LogInfoEx(LOG_MODEM, "Modem configuration area checking is disabled!"); return; } diff --git a/src/host/modem/Modem.h b/src/host/modem/Modem.h index fe1e4db9f..219a8cd56 100644 --- a/src/host/modem/Modem.h +++ b/src/host/modem/Modem.h @@ -30,7 +30,7 @@ #include "common/RingBuffer.h" #include "common/Timer.h" #include "modem/port/IModemPort.h" -#include "network/RESTAPI.h" +#include "restapi/RESTAPI.h" #include #include @@ -103,159 +103,159 @@ namespace modem * @brief Modem response types. */ enum RESP_TYPE_DVM { - RTM_OK, //! OK - RTM_TIMEOUT, //! Timeout - RTM_ERROR //! Error + RTM_OK, //!< OK + RTM_TIMEOUT, //!< Timeout + RTM_ERROR //!< Error }; /** * @brief Modem operation states. */ enum DVM_STATE { - STATE_IDLE = 0U, //! Idle + STATE_IDLE = 0U, //!< Idle // DMR - STATE_DMR = 1U, //! DMR + STATE_DMR = 1U, //!< DMR // Project 25 - STATE_P25 = 2U, //! Project 25 + STATE_P25 = 2U, //!< Project 25 // NXDN - STATE_NXDN = 3U, //! NXDN + STATE_NXDN = 3U, //!< NXDN // CW - STATE_CW = 10U, //! Continuous Wave + STATE_CW = 10U, //!< Continuous Wave // Calibration States - STATE_P25_CAL_1K = 92U, //! Project 25 Calibration 1K + STATE_P25_CAL_1K = 92U, //!< Project 25 Calibration 1K - STATE_DMR_DMO_CAL_1K = 93U, //! DMR DMO Calibration 1K - STATE_DMR_CAL_1K = 94U, //! DMR Calibration 1K - STATE_DMR_LF_CAL = 95U, //! DMR Low Frequency Calibration + STATE_DMR_DMO_CAL_1K = 93U, //!< DMR DMO Calibration 1K + STATE_DMR_CAL_1K = 94U, //!< DMR Calibration 1K + STATE_DMR_LF_CAL = 95U, //!< DMR Low Frequency Calibration - STATE_RSSI_CAL = 96U, //! RSSI Calibration + STATE_RSSI_CAL = 96U, //!< RSSI Calibration - STATE_P25_CAL = 97U, //! Project 25 Calibration - STATE_DMR_CAL = 98U, //! DMR Calibration - STATE_NXDN_CAL = 99U //! NXDN Calibration + STATE_P25_CAL = 97U, //!< Project 25 Calibration + STATE_DMR_CAL = 98U, //!< DMR Calibration + STATE_NXDN_CAL = 99U //!< NXDN Calibration }; /** * @brief Modem commands. */ enum DVM_COMMANDS { - CMD_GET_VERSION = 0x00U, //! Get Modem Version - CMD_GET_STATUS = 0x01U, //! Get Modem Status - CMD_SET_CONFIG = 0x02U, //! Set Modem Configuration - CMD_SET_MODE = 0x03U, //! Set Modem Mode - - CMD_SET_SYMLVLADJ = 0x04U, //! Set Symbol Level Adjustments - CMD_SET_RXLEVEL = 0x05U, //! Set Rx Level - CMD_SET_RFPARAMS = 0x06U, //! (Hotspot) Set RF Parameters - - CMD_CAL_DATA = 0x08U, //! Calibration Data - CMD_RSSI_DATA = 0x09U, //! RSSI Data - - CMD_SEND_CWID = 0x0AU, //! Send Continous Wave ID (Morse) - - CMD_SET_BUFFERS = 0x0FU, //! Set FIFO Buffer Lengths - - CMD_DMR_DATA1 = 0x18U, //! DMR Data Slot 1 - CMD_DMR_LOST1 = 0x19U, //! DMR Data Lost Slot 1 - CMD_DMR_DATA2 = 0x1AU, //! DMR Data Slot 2 - CMD_DMR_LOST2 = 0x1BU, //! DMR Data Lost Slot 2 - CMD_DMR_SHORTLC = 0x1CU, //! DMR Short Link Control - CMD_DMR_START = 0x1DU, //! DMR Start Transmit - CMD_DMR_ABORT = 0x1EU, //! DMR Abort - CMD_DMR_CACH_AT_CTRL = 0x1FU, //! DMR Set CACH AT Control - CMD_DMR_CLEAR1 = 0x20U, //! DMR Clear Slot 1 Buffer - CMD_DMR_CLEAR2 = 0x21U, //! DMR Clear Slot 2 Buffer - - CMD_P25_DATA = 0x31U, //! Project 25 Data - CMD_P25_LOST = 0x32U, //! Project 25 Data Lost - CMD_P25_CLEAR = 0x33U, //! Project 25 Clear Buffer - - CMD_NXDN_DATA = 0x41U, //! NXDN Data - CMD_NXDN_LOST = 0x42U, //! NXDN Data Lost - CMD_NXDN_CLEAR = 0x43U, //! NXDN Clear Buffer - - CMD_ACK = 0x70U, //! Command ACK - CMD_NAK = 0x7FU, //! Command NACK - - CMD_FLSH_READ = 0xE0U, //! Read Flash Partition - CMD_FLSH_WRITE = 0xE1U, //! Write Flash Partition - - CMD_RESET_MCU = 0xEAU, //! Soft Reboot MCU - - CMD_DEBUG1 = 0xF1U, //! - CMD_DEBUG2 = 0xF2U, //! - CMD_DEBUG3 = 0xF3U, //! - CMD_DEBUG4 = 0xF4U, //! - CMD_DEBUG5 = 0xF5U, //! - CMD_DEBUG_DUMP = 0xFAU, //! + CMD_GET_VERSION = 0x00U, //!< Get Modem Version + CMD_GET_STATUS = 0x01U, //!< Get Modem Status + CMD_SET_CONFIG = 0x02U, //!< Set Modem Configuration + CMD_SET_MODE = 0x03U, //!< Set Modem Mode + + CMD_SET_SYMLVLADJ = 0x04U, //!< Set Symbol Level Adjustments + CMD_SET_RXLEVEL = 0x05U, //!< Set Rx Level + CMD_SET_RFPARAMS = 0x06U, //!< (Hotspot) Set RF Parameters + + CMD_CAL_DATA = 0x08U, //!< Calibration Data + CMD_RSSI_DATA = 0x09U, //!< RSSI Data + + CMD_SEND_CWID = 0x0AU, //!< Send Continous Wave ID (Morse) + + CMD_SET_BUFFERS = 0x0FU, //!< Set FIFO Buffer Lengths + + CMD_DMR_DATA1 = 0x18U, //!< DMR Data Slot 1 + CMD_DMR_LOST1 = 0x19U, //!< DMR Data Lost Slot 1 + CMD_DMR_DATA2 = 0x1AU, //!< DMR Data Slot 2 + CMD_DMR_LOST2 = 0x1BU, //!< DMR Data Lost Slot 2 + CMD_DMR_SHORTLC = 0x1CU, //!< DMR Short Link Control + CMD_DMR_START = 0x1DU, //!< DMR Start Transmit + CMD_DMR_ABORT = 0x1EU, //!< DMR Abort + CMD_DMR_CACH_AT_CTRL = 0x1FU, //!< DMR Set CACH AT Control + CMD_DMR_CLEAR1 = 0x20U, //!< DMR Clear Slot 1 Buffer + CMD_DMR_CLEAR2 = 0x21U, //!< DMR Clear Slot 2 Buffer + + CMD_P25_DATA = 0x31U, //!< Project 25 Data + CMD_P25_LOST = 0x32U, //!< Project 25 Data Lost + CMD_P25_CLEAR = 0x33U, //!< Project 25 Clear Buffer + + CMD_NXDN_DATA = 0x41U, //!< NXDN Data + CMD_NXDN_LOST = 0x42U, //!< NXDN Data Lost + CMD_NXDN_CLEAR = 0x43U, //!< NXDN Clear Buffer + + CMD_ACK = 0x70U, //!< Command ACK + CMD_NAK = 0x7FU, //!< Command NACK + + CMD_FLSH_READ = 0xE0U, //!< Read Flash Partition + CMD_FLSH_WRITE = 0xE1U, //!< Write Flash Partition + + CMD_RESET_MCU = 0xEAU, //!< Soft Reboot MCU + + CMD_DEBUG1 = 0xF1U, //!< + CMD_DEBUG2 = 0xF2U, //!< + CMD_DEBUG3 = 0xF3U, //!< + CMD_DEBUG4 = 0xF4U, //!< + CMD_DEBUG5 = 0xF5U, //!< + CMD_DEBUG_DUMP = 0xFAU, //!< }; /** * @brief Modem command tags. */ enum CMD_TAGS { - TAG_HEADER = 0x00U, //! Header + TAG_HEADER = 0x00U, //!< Header - TAG_DATA = 0x01U, //! Data + TAG_DATA = 0x01U, //!< Data - TAG_LOST = 0x02U, //! Lost Data - TAG_EOT = 0x03U, //! End of Transmission + TAG_LOST = 0x02U, //!< Lost Data + TAG_EOT = 0x03U, //!< End of Transmission }; /** * @brief Modem response reason codes. */ enum CMD_REASON_CODE { - RSN_OK = 0U, //! OK - RSN_NAK = 1U, //! Negative Acknowledge + RSN_OK = 0U, //!< OK + RSN_NAK = 1U, //!< Negative Acknowledge - RSN_ILLEGAL_LENGTH = 2U, //! Illegal Length - RSN_INVALID_REQUEST = 4U, //! Invalid Request - RSN_RINGBUFF_FULL = 8U, //! Ring Buffer Full + RSN_ILLEGAL_LENGTH = 2U, //!< Illegal Length + RSN_INVALID_REQUEST = 4U, //!< Invalid Request + RSN_RINGBUFF_FULL = 8U, //!< Ring Buffer Full - RSN_INVALID_FDMA_PREAMBLE = 10U, //! Invalid FDMA Preamble Length - RSN_INVALID_MODE = 11U, //! Invalid Mode + RSN_INVALID_FDMA_PREAMBLE = 10U, //!< Invalid FDMA Preamble Length + RSN_INVALID_MODE = 11U, //!< Invalid Mode - RSN_INVALID_DMR_CC = 12U, //! Invalid DMR CC - RSN_INVALID_DMR_SLOT = 13U, //! Invalid DMR Slot - RSN_INVALID_DMR_START = 14U, //! Invaild DMR Start Transmit - RSN_INVALID_DMR_RX_DELAY = 15U, //! Invalid DMR Rx Delay + RSN_INVALID_DMR_CC = 12U, //!< Invalid DMR CC + RSN_INVALID_DMR_SLOT = 13U, //!< Invalid DMR Slot + RSN_INVALID_DMR_START = 14U, //!< Invaild DMR Start Transmit + RSN_INVALID_DMR_RX_DELAY = 15U, //!< Invalid DMR Rx Delay - RSN_INVALID_P25_CORR_COUNT = 16U, //! Invalid P25 Correlation Count + RSN_INVALID_P25_CORR_COUNT = 16U, //!< Invalid P25 Correlation Count - RSN_NO_INTERNAL_FLASH = 20U, //! No Internal Flash - RSN_FAILED_ERASE_FLASH = 21U, //! Failed to erase flash partition - RSN_FAILED_WRITE_FLASH = 22U, //! Failed to write flash partition - RSN_FLASH_WRITE_TOO_BIG = 23U, //! Data to large for flash partition + RSN_NO_INTERNAL_FLASH = 20U, //!< No Internal Flash + RSN_FAILED_ERASE_FLASH = 21U, //!< Failed to erase flash partition + RSN_FAILED_WRITE_FLASH = 22U, //!< Failed to write flash partition + RSN_FLASH_WRITE_TOO_BIG = 23U, //!< Data to large for flash partition - RSN_HS_NO_DUAL_MODE = 32U, //! (Hotspot) No Dual Mode Operation + RSN_HS_NO_DUAL_MODE = 32U, //!< (Hotspot) No Dual Mode Operation - RSN_DMR_DISABLED = 63U, //! DMR Disabled - RSN_P25_DISABLED = 64U, //! Project 25 Disabled - RSN_NXDN_DISABLED = 65U //! NXDN Disabled + RSN_DMR_DISABLED = 63U, //!< DMR Disabled + RSN_P25_DISABLED = 64U, //!< Project 25 Disabled + RSN_NXDN_DISABLED = 65U //!< NXDN Disabled }; /** * @brief Modem response state machine. */ enum RESP_STATE { - RESP_START, //! Start Handling Frame - RESP_LENGTH1, //! Frame Length 1 - RESP_LENGTH2, //! Frame Length 2 - RESP_TYPE, //! Frame Type - RESP_DATA //! Frame Data + RESP_START, //!< Start Handling Frame + RESP_LENGTH1, //!< Frame Length 1 + RESP_LENGTH2, //!< Frame Length 2 + RESP_TYPE, //!< Frame Type + RESP_DATA //!< Frame Data }; /** * @brief Hotspot gain modes. */ enum ADF_GAIN_MODE { - ADF_GAIN_AUTO = 0U, //! Automatic - ADF_GAIN_AUTO_LIN = 1U, //! Automatic (Linear) - ADF_GAIN_LOW = 2U, //! Low - ADF_GAIN_HIGH = 3U //! High + ADF_GAIN_AUTO = 0U, //!< Automatic + ADF_GAIN_AUTO_LIN = 1U, //!< Automatic (Linear) + ADF_GAIN_LOW = 2U, //!< Low + ADF_GAIN_HIGH = 3U //!< High }; const uint8_t DVM_SHORT_FRAME_START = 0xFEU; diff --git a/src/host/modem/ModemV24.cpp b/src/host/modem/ModemV24.cpp index 5baf0d4de..ea6ef12f4 100644 --- a/src/host/modem/ModemV24.cpp +++ b/src/host/modem/ModemV24.cpp @@ -40,11 +40,10 @@ using namespace p25::dfsi::frames; /* Initializes a new instance of the ModemV24 class. */ ModemV24::ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, uint32_t p25TxQueueSize, - bool rtrt, bool diu, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug) : + bool rtrt, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug) : Modem(port, duplex, false, false, false, false, false, 80U, 7U, 8U, 1U, p25QueueSize, 1U, false, false, dumpModemStatus, trace, debug), m_rtrt(rtrt), - m_diu(diu), m_superFrameCnt(0U), m_audio(), m_nid(nullptr), @@ -103,7 +102,7 @@ void ModemV24::setTIAFormat(bool set) bool ModemV24::open() { - LogMessage(LOG_MODEM, "Initializing modem"); + LogInfoEx(LOG_MODEM, "Initializing modem"); m_gotModemStatus = false; bool ret = m_port->open(); @@ -138,9 +137,9 @@ bool ModemV24::open() m_error = false; if (m_useTIAFormat) - LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]"); + LogInfoEx(LOG_MODEM, "Modem Ready [Direct Mode / TIA-102]"); else - LogMessage(LOG_MODEM, "Modem Ready [Direct Mode / V.24]"); + LogInfoEx(LOG_MODEM, "Modem Ready [Direct Mode / V.24]"); return true; } @@ -410,7 +409,7 @@ void ModemV24::clock(uint32_t ms) void ModemV24::close() { - LogMessage(LOG_MODEM, "Closing the modem"); + LogInfoEx(LOG_MODEM, "Closing the modem"); m_port->close(); m_gotModemStatus = false; @@ -551,6 +550,77 @@ void ModemV24::storeConvertedRx(const uint8_t* buffer, uint32_t length) m_rxP25Queue.addData(buffer, length); } +/* Internal helper to store converted PDU Rx frames. */ + +void ModemV24::storeConvertedRxPDU(data::DataHeader& dataHeader, uint8_t* pduUserData) +{ + assert(pduUserData != nullptr); + + uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (dataHeader.getPadLength() > 0U) + bitLength += (dataHeader.getPadLength() * 8U); + + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; + + DECLARE_UINT8_ARRAY(pdu, (bitLength / 8U) + 1U); + + uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + + // generate the PDU header and 1/2 rate Trellis + dataHeader.encode(block); + Utils::setBitRange(block, pdu, offset, P25_PDU_FEC_LENGTH_BITS); + offset += P25_PDU_FEC_LENGTH_BITS; + + if (blocksToFollow > 0U) { + uint32_t dataOffset = 0U; + uint32_t packetLength = dataHeader.getPDULength(); + + // generate the PDU data + for (uint32_t i = 0U; i < blocksToFollow; i++) { + data::DataBlock dataBlock = data::DataBlock(); + dataBlock.setFormat(dataHeader); + dataBlock.setSerialNo(i); + dataBlock.setData(pduUserData + dataOffset); + dataBlock.setLastBlock((i + 1U) == blocksToFollow); + + ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + dataBlock.encode(block); + Utils::setBitRange(block, pdu, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } + } + + uint8_t data[P25_PDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); + + // add the data + uint32_t newBitLength = P25Utils::encodeByLength(pdu, data + 2U, bitLength); + uint32_t newByteLength = newBitLength / 8U; + if ((newBitLength % 8U) > 0U) + newByteLength++; + + // generate Sync + Sync::addP25Sync(data + 2U); + + // generate NID + m_nid->encode(data + 2U, DUID::PDU); + + // add status bits + P25Utils::addStatusBits(data + 2U, newBitLength, true, true); + P25Utils::setStatusBitsStartIdle(data + 2U); + + //Utils::dump("P25, Data::writeRF_PDU(), Raw PDU OSP", data, newByteLength + 2U); + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + storeConvertedRx(data, newByteLength + 2U); +} + /* Helper to generate a P25 TDU packet. */ void ModemV24::create_TDU(uint8_t* buffer) @@ -869,8 +939,251 @@ void ModemV24::convertToAirV24(const uint8_t *data, uint32_t length) } break; - case DFSIFrameType::MOT_PDU_SINGLE: - break; + case DFSIFrameType::MOT_PDU_SINGLE_UNCONF: + { + m_rxCall->resetCallData(); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); + data::DataHeader pduHeader = data::DataHeader(); + bool ret = pduHeader.decode(header, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + break; + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), + pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), + pduHeader.getHeaderOffset(), pduHeader.getLLId()); + } + + m_rxCall->dataCall = true; + m_rxCall->dataHeader = pduHeader; + + for (uint8_t i = 0U; i < pduHeader.getBlocksToFollow() + 1U; i++) { + uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + uint32_t offset = i * P25_PDU_UNCONFIRMED_LENGTH_BYTES; + ::memcpy(m_rxCall->pduUserData + offset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + } + + storeConvertedRxPDU(pduHeader, m_rxCall->pduUserData); + } + break; + + case DFSIFrameType::MOT_PDU_UNCONF_HEADER: + { + m_rxCall->resetCallData(); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); + data::DataHeader pduHeader = data::DataHeader(); + bool ret = pduHeader.decode(header, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + break; + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), + pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), + pduHeader.getHeaderOffset(), pduHeader.getLLId()); + } + + // make sure we don't get a PDU with more blocks then we support + if (pduHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { + LogError(LOG_MODEM, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", pduHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); + + m_rxCall->resetCallData(); + break; + } + + m_rxCall->dataCall = true; + m_rxCall->dataHeader = pduHeader; + + // PDU_UNCONF_HEADER only contains 3 blocks + for (uint8_t i = 0U; i < 3U; i++) { + uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + m_rxCall->pduUserDataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; + m_rxCall->pduTotalBlocks++; + } + } + break; + case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1: + case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_2: + case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_3: + case DFSIFrameType::MOT_PDU_UNCONF_BLOCK_4: + case DFSIFrameType::MOT_PDU_UNCONF_END: + { + // only process blocks if we've received a header and started a data call + if (!m_rxCall->dataCall) + break; + + uint32_t blockCnt = DFSI_PDU_BLOCK_CNT; + + // PDU_UNCONF_END are variable length depending on the message + if (frameType == DFSIFrameType::MOT_PDU_UNCONF_END) { + blockCnt = m_rxCall->dataHeader.getBlocksToFollow() - m_rxCall->pduTotalBlocks; + + // bryanb: I wonder if there's a chance somehow the calculation will be less then zero...reasonably + // as far as I can tell that should never happen as PDU_UNCONF_BLOCK_X should *always* contain + // 4 blocks of user data, and the PDU_UNCONF_END is always variable with at least the last data block + } + + // PDU_UNCONF_BLOCK_X and PDU_UNCONF_END only contains 4 blocks each + for (uint8_t i = 0U; i < blockCnt; i++) { + uint8_t dataBlock[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_UNCONFIRMED_LENGTH_BYTES), P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + m_rxCall->pduUserDataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; + m_rxCall->pduTotalBlocks++; + } + + if (frameType == DFSIFrameType::MOT_PDU_UNCONF_END) { + storeConvertedRxPDU(m_rxCall->dataHeader, m_rxCall->pduUserData); + } + } + break; + + case DFSIFrameType::MOT_PDU_SINGLE_CONF: + { + m_rxCall->resetCallData(); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); + data::DataHeader pduHeader = data::DataHeader(); + bool ret = pduHeader.decode(header, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + break; + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), + pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), + pduHeader.getHeaderOffset(), pduHeader.getLLId()); + } + + m_rxCall->dataCall = true; + m_rxCall->dataHeader = pduHeader; + + for (uint8_t i = 0U; i < pduHeader.getBlocksToFollow() + 1U; i++) { + uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); + + uint32_t offset = i * P25_PDU_CONFIRMED_LENGTH_BYTES; + ::memcpy(m_rxCall->pduUserData + offset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); + } + + storeConvertedRxPDU(pduHeader, m_rxCall->pduUserData); + } + break; + + case DFSIFrameType::MOT_PDU_CONF_HEADER: + { + m_rxCall->resetCallData(); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + ::memcpy(header, dfsiData + 1U, P25_PDU_HEADER_LENGTH_BYTES); + data::DataHeader pduHeader = data::DataHeader(); + bool ret = pduHeader.decode(header, true); + if (!ret) { + LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + break; + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertToAirV24()", "V.24 RX, PDU ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + pduHeader.getAckNeeded(), pduHeader.getOutbound(), pduHeader.getFormat(), pduHeader.getMFId(), pduHeader.getSAP(), pduHeader.getFullMessage(), + pduHeader.getBlocksToFollow(), pduHeader.getPadLength(), pduHeader.getPacketLength(), pduHeader.getSynchronize(), pduHeader.getNs(), pduHeader.getFSN(), pduHeader.getLastFragment(), + pduHeader.getHeaderOffset(), pduHeader.getLLId()); + } + + // make sure we don't get a PDU with more blocks then we support + if (pduHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { + LogError(LOG_MODEM, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", pduHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); + + m_rxCall->resetCallData(); + break; + } + + m_rxCall->dataCall = true; + m_rxCall->dataHeader = pduHeader; + + // PDU_CONF_HEADER only contains 3 blocks + for (uint8_t i = 0U; i < 3U; i++) { + uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); + + ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); + m_rxCall->pduUserDataOffset += P25_PDU_CONFIRMED_LENGTH_BYTES; + m_rxCall->pduTotalBlocks++; + } + } + break; + case DFSIFrameType::MOT_PDU_CONF_BLOCK_1: + case DFSIFrameType::MOT_PDU_CONF_BLOCK_2: + case DFSIFrameType::MOT_PDU_CONF_BLOCK_3: + case DFSIFrameType::MOT_PDU_CONF_BLOCK_4: + case DFSIFrameType::MOT_PDU_CONF_END: + { + // only process blocks if we've received a header and started a data call + if (!m_rxCall->dataCall) + break; + + uint32_t blockCnt = DFSI_PDU_BLOCK_CNT; + + // PDU_CONF_END are variable length depending on the message + if (frameType == DFSIFrameType::MOT_PDU_CONF_END) { + blockCnt = m_rxCall->dataHeader.getBlocksToFollow() - m_rxCall->pduTotalBlocks; + + // bryanb: I wonder if there's a chance somehow the calculation will be less then zero...reasonably + // as far as I can tell that should never happen as PDU_CONF_BLOCK_X should *always* contain + // 4 blocks of user data, and the PDU_CONF_END is always variable with at least the last data block + } + + // PDU_CONF_BLOCK_X only contains 4 blocks each + for (uint8_t i = 0U; i < blockCnt; i++) { + uint8_t dataBlock[P25_PDU_CONFIRMED_LENGTH_BYTES]; + ::memset(dataBlock, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + ::memcpy(dataBlock, dfsiData + 1U + P25_PDU_HEADER_LENGTH_BYTES + (i * P25_PDU_CONFIRMED_LENGTH_BYTES), P25_PDU_CONFIRMED_LENGTH_BYTES); + + ::memcpy(m_rxCall->pduUserData + m_rxCall->pduUserDataOffset, dataBlock, P25_PDU_CONFIRMED_LENGTH_BYTES); + m_rxCall->pduUserDataOffset += P25_PDU_CONFIRMED_LENGTH_BYTES; + m_rxCall->pduTotalBlocks++; + } + + if (frameType == DFSIFrameType::MOT_PDU_CONF_END) { + storeConvertedRxPDU(m_rxCall->dataHeader, m_rxCall->pduUserData); + } + } + break; case DFSIFrameType::MOT_TSBK: { @@ -2441,7 +2754,234 @@ void ModemV24::convertFromAirV24(uint8_t* data, uint32_t length) break; case DUID::PDU: - break; + { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + uint32_t bits = P25Utils::decode(data + 2U, buffer, 0U, P25_PDU_FRAME_LENGTH_BITS); + + uint8_t rawPDU[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(rawPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + uint32_t pduBitCnt = Utils::getBits(buffer, rawPDU, 0U, bits); + + uint32_t offset = P25_PREAMBLE_LENGTH_BITS + P25_PDU_FEC_LENGTH_BITS; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(rawPDU, buffer, P25_PREAMBLE_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); + data::DataHeader dataHeader = data::DataHeader(); + bool ret = dataHeader.decode(buffer); + if (!ret) { + LogWarning(LOG_MODEM, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + return; + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", + dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), + dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), + dataHeader.getHeaderOffset(), dataHeader.getLLId()); + } + + // make sure we don't get a PDU with more blocks then we support + if (dataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, too many PDU blocks to process, %u > %u", dataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); + return; + } + + uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); + uint32_t bitLength = ((blocksToFollow + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + if (pduBitCnt >= bitLength) { + // process all blocks in the data stream + // if the primary header has a header offset ensure data if offset by that amount + if (dataHeader.getHeaderOffset() > 0U) { + offset += dataHeader.getHeaderOffset() * 8; + } + + data::DataBlock* dataBlocks = new data::DataBlock[P25_MAX_PDU_BLOCKS]; + + // decode data blocks + for (uint32_t i = 0U; i < blocksToFollow; i++) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(rawPDU, buffer, offset, P25_PDU_FEC_LENGTH_BITS); + bool ret = dataBlocks[i].decode(buffer, dataHeader); + if (ret) { + // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks + // set this block as the last block for full packet CRC + if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) || (dataHeader.getFormat() == PDUFormatType::UNCONFIRMED)) { + if ((i + 1U) == blocksToFollow) { + dataBlocks[i].setLastBlock(true); + } + } + + if (m_debug) { + ::LogDebugEx(LOG_MODEM, "ModemV24::convertFromAirV24()", "PDU OSP, block %u, fmt = $%02X, lastBlock = %u", + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlocks[i].getSerialNo() : i, dataBlocks[i].getFormat(), + dataBlocks[i].getLastBlock()); + } + } + + offset += P25_PDU_FEC_LENGTH_BITS; + } + + if (blocksToFollow <= 3U) { + DECLARE_UINT8_ARRAY(pduBuf, ((blocksToFollow + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U); + uint32_t pduLen = (blocksToFollow + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_SINGLE_CONF : DFSIFrameType::MOT_PDU_SINGLE_UNCONF; + + dataHeader.encode(pduBuf + 1U, true); + for (uint32_t i = 0U; i < blocksToFollow; i++) { + dataBlocks[i].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); + } + + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::DATA); + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); + + queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::DATA); + + // create buffer and encode + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); + + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + } + else { + uint32_t remainderBlocks = (blocksToFollow - 3U) % DFSI_PDU_BLOCK_CNT; + uint32_t baseBlockCnt = (blocksToFollow - 3U) / DFSI_PDU_BLOCK_CNT; + uint32_t currentBlock = 0U; + + DECLARE_UINT8_ARRAY(pduBuf, ((DFSI_PDU_BLOCK_CNT + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U); + uint32_t pduLen = ((DFSI_PDU_BLOCK_CNT + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)) + 1U; + + MotStartOfStream start = MotStartOfStream(); + start.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + start.setParam1(DSFI_MOT_ICW_PARM_PAYLOAD); + start.setArgument1(MotStreamPayload::DATA); + + // assemble the first frame + pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_HEADER : DFSIFrameType::MOT_PDU_UNCONF_HEADER; + + dataHeader.encode(pduBuf + 1U, true); + for (uint32_t i = 0U; i < DFSI_PDU_BLOCK_CNT - 1U; i++) { + dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); + currentBlock++; + } + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); + + queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); + + // iterate through the count of full 4 block buffers and send + uint8_t currentOpcode = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_1 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1; + for (uint32_t i = 1U; i < baseBlockCnt; i++) { + // reset buffer and set data + ::memset(pduBuf, 0x00U, pduLen); + pduBuf[0U] = currentOpcode; + + for (uint32_t i = 0U; i < DFSI_PDU_BLOCK_CNT - 1U; i++) { + dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); + currentBlock++; + } + + currentOpcode++; + if (currentOpcode > ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_4 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_4)) + currentOpcode = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_BLOCK_1 : DFSIFrameType::MOT_PDU_UNCONF_BLOCK_1; + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); + + queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); + } + + // do we have any remaining blocks? + if (remainderBlocks > 0) { + // reset buffer and set data + ::memset(pduBuf, 0x00U, pduLen); + pduBuf[0U] = (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? DFSIFrameType::MOT_PDU_CONF_END : DFSIFrameType::MOT_PDU_UNCONF_END; + pduLen = 0U; + for (uint32_t i = 0U; i < remainderBlocks; i++) { + dataBlocks[currentBlock].encode(pduBuf + 1U + ((i + 1U) * ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES)), true); + pduLen += 1U + (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + currentBlock++; + } + + // create buffer for bytes and encode + uint8_t startBuf[DFSI_MOT_START_LEN]; + ::memset(startBuf, 0x00U, DFSI_MOT_START_LEN); + start.encode(startBuf); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), PDU StartOfStream", startBuf, DFSI_MOT_START_LEN); + + queueP25Frame(startBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + + if (m_trace) + Utils::dump(1U, "ModemV24::convertFromAirV24(), MotPDUFrame", pduBuf, pduLen); + + queueP25Frame(pduBuf, pduLen, STT_NON_IMBE_NO_JITTER); + + MotStartOfStream end = MotStartOfStream(); + end.setOpcode(m_rtrt ? MotStartStreamOpcode::TRANSMIT : MotStartStreamOpcode::RECEIVE); + end.setParam1(DFSI_MOT_ICW_PARM_STOP); + end.setArgument1(MotStreamPayload::DATA); + + // create buffer and encode + uint8_t endBuf[DFSI_MOT_START_LEN]; + ::memset(endBuf, 0x00U, DFSI_MOT_START_LEN); + end.encode(endBuf); + + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + queueP25Frame(endBuf, DFSI_MOT_START_LEN, STT_NON_IMBE_NO_JITTER); + } + } + + delete[] dataBlocks; + } + } + break; case DUID::TSDU: { diff --git a/src/host/modem/ModemV24.h b/src/host/modem/ModemV24.h index 8c05bb19f..25dfffe45 100644 --- a/src/host/modem/ModemV24.h +++ b/src/host/modem/ModemV24.h @@ -18,6 +18,8 @@ #include "Defines.h" #include "common/edac/RS634717.h" +#include "common/p25/data/DataHeader.h" +#include "common/p25/data/DataBlock.h" #include "common/p25/lc/LC.h" #include "common/p25/Audio.h" #include "common/p25/NID.h" @@ -39,10 +41,10 @@ namespace modem * @ingroup modem */ enum SERIAL_TX_TYPE { - STT_NO_DATA, //! No Data - STT_NON_IMBE, //! Non-IMBE Data/Signalling Frame - STT_NON_IMBE_NO_JITTER, //! Non-IMBE Data/Signalling Frame with Jitter Disabled - STT_IMBE //! IMBE Voice Frame + STT_NO_DATA, //!< No Data + STT_NON_IMBE, //!< Non-IMBE Data/Signalling Frame + STT_NON_IMBE_NO_JITTER, //!< Non-IMBE Data/Signalling Frame with Jitter Disabled + STT_IMBE //!< IMBE Voice Frame }; /** @} */ @@ -79,6 +81,11 @@ namespace modem VHDR2(nullptr), netLDU1(nullptr), netLDU2(nullptr), + pduUserData(nullptr), + dataHeader(), + dataCall(false), + pduUserDataOffset(0U), + pduTotalBlocks(0U), errors(0U) { MI = new uint8_t[P25DEF::MI_LENGTH_BYTES]; @@ -92,6 +99,9 @@ namespace modem ::memset(netLDU1, 0x00U, 9U * 25U); ::memset(netLDU2, 0x00U, 9U * 25U); + pduUserData = new uint8_t[P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + resetCallData(); } /** @@ -111,6 +121,8 @@ namespace modem delete[] netLDU1; if (netLDU2 != nullptr) delete[] netLDU2; + if (pduUserData != nullptr) + delete[] pduUserData; } /** @@ -149,6 +161,14 @@ namespace modem n = 0U; seqNo = 0U; + if (pduUserData != nullptr) + ::memset(pduUserData, 0x00U, P25DEF::P25_MAX_PDU_BLOCKS * P25DEF::P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + dataHeader.reset(); + + dataCall = false; + pduUserDataOffset = 0U; + pduTotalBlocks = 0U; + errors = 0U; } @@ -230,6 +250,28 @@ namespace modem */ uint8_t* netLDU2; + /** + * @brief User data associated with this call. + */ + uint8_t* pduUserData; + /** + * @brief Data call header. + */ + p25::data::DataHeader dataHeader; + + /** + * @brief Flag indicating the current call is a data call. + */ + bool dataCall; + /** + * @brief Offset index when populating the user data buffer. + */ + uint32_t pduUserDataOffset; + /** + * @brief Total count of PDU blocks. + */ + uint32_t pduTotalBlocks; + /** * @brief Total errors for a given call sequence. */ @@ -462,7 +504,7 @@ namespace modem * @param debug Flag indicating whether air interface modem debug is enabled. */ ModemV24(port::IModemPort* port, bool duplex, uint32_t p25QueueSize, uint32_t p25TxQueueSize, - bool rtrt, bool diu, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug); + bool rtrt, uint16_t jitter, bool dumpModemStatus, bool trace, bool debug); /** * @brief Finalizes a instance of the ModemV24 class. */ @@ -518,7 +560,6 @@ namespace modem private: bool m_rtrt; - bool m_diu; uint8_t m_superFrameCnt; @@ -556,6 +597,12 @@ namespace modem * @param length Length of buffer. */ void storeConvertedRx(const uint8_t* buffer, uint32_t length); + /** + * @brief Internal helper to store converted PDU Rx frames. + * @param dataHeader Instance of a PDU data header. + * @param pduUserData Buffer containing user data to transmit. + */ + void storeConvertedRxPDU(p25::data::DataHeader& dataHeader, uint8_t* pduUserData); /** * @brief Helper to generate a P25 TDU packet. * @param buffer Buffer to create TDU. diff --git a/src/host/modem/port/ModemNullPort.cpp b/src/host/modem/port/ModemNullPort.cpp index a3068d56f..91af7f45c 100644 --- a/src/host/modem/port/ModemNullPort.cpp +++ b/src/host/modem/port/ModemNullPort.cpp @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Modem Host Software - * @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2021 Jonathan Naylor, G4KLX * Copyright (C) 2021,2024 Bryan Biedenkapp, N2PLL * diff --git a/src/host/modem/port/PseudoPTYPort.cpp b/src/host/modem/port/PseudoPTYPort.cpp index 0ba8e05ba..bc576fc08 100644 --- a/src/host/modem/port/PseudoPTYPort.cpp +++ b/src/host/modem/port/PseudoPTYPort.cpp @@ -64,7 +64,7 @@ bool PseudoPTYPort::open() return false; } - ::LogMessage(LOG_HOST, "Made symbolic link from %s to %s", slave, m_symlink.c_str()); + ::LogInfoEx(LOG_HOST, "Made symbolic link from %s to %s", slave, m_symlink.c_str()); m_device = std::string(::ttyname(m_fd)); return setTermios(); } diff --git a/src/host/modem/port/specialized/V24UDPPort.cpp b/src/host/modem/port/specialized/V24UDPPort.cpp index 2005c9d86..6dbc890a7 100644 --- a/src/host/modem/port/specialized/V24UDPPort.cpp +++ b/src/host/modem/port/specialized/V24UDPPort.cpp @@ -313,7 +313,7 @@ void V24UDPPort::closeFSC() { if (m_controlSocket != nullptr) { if (m_fscState == CS_CONNECTED) { - LogMessage(LOG_MODEM, "V.24 UDP, Closing DFSI FSC Connection, vcBasePort = %u", m_localPort); + LogInfoEx(LOG_MODEM, "V.24 UDP, Closing DFSI FSC Connection, vcBasePort = %u", m_localPort); FSCDisconnect discoMessage = FSCDisconnect(); @@ -450,7 +450,7 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) network->m_heartbeatTimer.start(); network->m_timeoutTimer.start(); - LogMessage(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u", remoteCtrlPort, network->m_localPort, vcBasePort); + LogInfoEx(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u", remoteCtrlPort, network->m_localPort, vcBasePort); } break; @@ -529,7 +529,7 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) network->m_remoteCtrlAddr = req->address; network->m_remoteCtrlAddrLen = req->addrLen; - LogMessage(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u, hostHBInterval = %u", remoteCtrlPort, network->m_localPort, vcBasePort, connMessage->getHostHeartbeatPeriod()); + LogInfoEx(LOG_MODEM, "V.24 UDP, Incoming DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u, hostHBInterval = %u", remoteCtrlPort, network->m_localPort, vcBasePort, connMessage->getHostHeartbeatPeriod()); // setup local RTP VC port (where we receive traffic) network->createVCPort(network->m_localPort); @@ -565,7 +565,7 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) ackResp.encode(buffer); if (network->m_ctrlFrameQueue->write(buffer, FSCACK::LENGTH + 3U, req->address, req->addrLen)) - LogMessage(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u", remoteCtrlPort, network->m_localPort, vcBasePort); + LogInfoEx(LOG_MODEM, "V.24 UDP, Established DFSI FSC Connection, ctrlRemotePort = %u, vcLocalPort = %u, vcRemotePort = %u", remoteCtrlPort, network->m_localPort, vcBasePort); } break; @@ -619,7 +619,7 @@ void V24UDPPort::taskCtrlNetworkRx(V24PacketRequest* req) case FSCMessageType::FSC_DISCONNECT: { - LogMessage(LOG_MODEM, "V.24 UDP, DFSI FSC Disconnect, vcBasePort = %u", network->m_localPort); + LogInfoEx(LOG_MODEM, "V.24 UDP, DFSI FSC Disconnect, vcBasePort = %u", network->m_localPort); if (network->m_socket != nullptr) { network->m_socket->close(); @@ -780,7 +780,7 @@ void V24UDPPort::createRemoteVCPort(std::string address, uint16_t port) void V24UDPPort::writeConnect() { - LogMessage(LOG_MODEM, "V.24 UDP, Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); + LogInfoEx(LOG_MODEM, "V.24 UDP, Attempting DFSI FSC Connection, peerId = %u, vcBasePort = %u", m_peerId, m_localPort); FSCConnect connect = FSCConnect(); connect.setFSHeartbeatPeriod(m_heartbeatInterval); diff --git a/src/host/modem/port/specialized/V24UDPPort.h b/src/host/modem/port/specialized/V24UDPPort.h index 8cbea5212..e671b5d73 100644 --- a/src/host/modem/port/specialized/V24UDPPort.h +++ b/src/host/modem/port/specialized/V24UDPPort.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Modem Host Software - * @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ @@ -48,11 +44,11 @@ namespace modem * @ingroup fne_network */ struct V24PacketRequest : thread_t { - sockaddr_storage address; //! IP Address and Port. - uint32_t addrLen; //! - network::frame::RTPHeader rtpHeader; //! RTP Header - int length = 0U; //! Length of raw data buffer - uint8_t *buffer; //! Raw data buffer + sockaddr_storage address; //!< IP Address and Port. + uint32_t addrLen; //!< + network::frame::RTPHeader rtpHeader; //!< RTP Header + int length = 0U; //!< Length of raw data buffer + uint8_t *buffer; //!< Raw data buffer }; // --------------------------------------------------------------------------- diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 5d4f21922..b5d024636 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -46,7 +46,7 @@ const uint8_t SCRAMBLER[] = { // Static Class Members // --------------------------------------------------------------------------- -std::mutex Control::m_queueLock; +std::mutex Control::s_queueLock; // --------------------------------------------------------------------------- // Public Class Members @@ -222,7 +222,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_legacyGroupReg = nxdnProtocol["legacyGroupReg"].as(false); yaml::Node rfssConfig = systemConf["config"]; - m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + m_defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as(0U); yaml::Node controlCh = rfssConfig["controlCh"]; m_notifyCC = controlCh["notifyEnable"].as(false); @@ -278,7 +278,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); // set the grant release callback - m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { + m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t srcId, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { ::lookups::VoiceChData voiceChData = m_affiliations->rfCh()->getRFChData(chNo); @@ -315,7 +315,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw // set the In-Call Control function callback if (m_network != nullptr) { - m_network->setNXDNICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId); }); + m_network->setNXDNICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId); }); } if (printOptions) { @@ -328,11 +329,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Disable Grant Source ID Check: yes"); } if (m_supervisor) - LogMessage(LOG_NXDN, "Host is configured to operate as a NXDN control channel, site controller mode."); + LogInfoEx(LOG_NXDN, "Host is configured to operate as a NXDN control channel, site controller mode."); } if (m_defaultNetIdleTalkgroup != 0U) { - LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); + LogInfo(" Default Network Idle Talkgroup: %u", m_defaultNetIdleTalkgroup); } LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); @@ -410,7 +411,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) // Convert the raw RSSI to dBm int rssi = m_rssiMapper->interpolate(raw); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, raw RSSI = %u, reported RSSI = %d dBm", raw, rssi); + LogInfoEx(LOG_RF, "NXDN, raw RSSI = %u, reported RSSI = %d dBm", raw, rssi); } // RSSI is always reported as positive @@ -507,7 +508,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) uint32_t Control::peekFrameLength() { - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; @@ -553,7 +554,7 @@ uint32_t Control::getFrame(uint8_t* data) { assert(data != nullptr); - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; @@ -653,7 +654,7 @@ void Control::clock() if (m_rfTGHang.hasExpired()) { m_rfTGHang.stop(); if (m_verbose) { - LogMessage(LOG_RF, "talkgroup hang has expired, lastDstId = %u", m_rfLastDstId); + LogInfoEx(LOG_RF, "talkgroup hang has expired, lastDstId = %u", m_rfLastDstId); } m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -684,7 +685,7 @@ void Control::clock() if (m_netTGHang.hasExpired()) { m_netTGHang.stop(); if (m_verbose) { - LogMessage(LOG_NET, "talkgroup hang has expired, lastDstId = %u", m_netLastDstId); + LogInfoEx(LOG_NET, "talkgroup hang has expired, lastDstId = %u", m_netLastDstId); } m_netLastDstId = 0U; m_netLastSrcId = 0U; @@ -758,9 +759,9 @@ void Control::permittedTG(uint32_t dstId) if (m_verbose) { if (dstId == 0U) - LogMessage(LOG_NXDN, "non-authoritative TG unpermit"); + LogInfoEx(LOG_NXDN, "non-authoritative TG unpermit"); else - LogMessage(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId); + LogInfoEx(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId); } m_permittedDstId = dstId; @@ -775,7 +776,7 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp) } if (m_verbose) { - LogMessage(LOG_NXDN, "network TG grant demand, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_NXDN, "network TG grant demand, srcId = %u, dstId = %u", srcId, dstId); } m_control->writeRF_Message_Grant(srcId, dstId, 4U, grp); @@ -864,7 +865,7 @@ void Control::addFrame(const uint8_t *data, bool net, bool imm) { assert(data != nullptr); - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (!net) { if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired()) @@ -941,7 +942,6 @@ void Control::processNetwork() // don't process network frames if the RF modem isn't in a listening state if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { - m_network->resetNXDN(); return; } @@ -1024,7 +1024,7 @@ void Control::processFrameLoss() float(m_voice->m_rfFrames) / 12.5F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits), m_frameLossCnt); } - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); m_affiliations->releaseGrant(m_rfLC.getDstId(), false); @@ -1093,7 +1093,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_NXDN, "CC %s:%u, notifying CC of call termination, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + LogInfoEx(LOG_NXDN, "CC %s:%u, notifying CC of call termination, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); } // callback REST API to release the granted TG on the specified control channel @@ -1115,7 +1115,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) } } else - ::LogMessage(LOG_NXDN, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogInfoEx(LOG_NXDN, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); }, m_controlChData.address(), m_controlChData.port()); m_rfLastDstId = 0U; @@ -1159,7 +1159,7 @@ void Control::notifyCC_TouchGrant(uint32_t dstId) } } else - ::LogMessage(LOG_NXDN, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogInfoEx(LOG_NXDN, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); }, m_controlChData.address(), m_controlChData.port()); } @@ -1209,7 +1209,7 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) // LogDebugEx(LOG_NXDN, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId); if (m_verbose) { - LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); + LogInfoEx(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); } if (m_affiliations->isGranted(dstId)) { @@ -1218,7 +1218,7 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } m_affiliations->releaseGrant(dstId, false); @@ -1257,7 +1257,7 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply) ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } m_affiliations->touchGrant(dstId); diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index f65fa1dbe..e02de635a 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -301,7 +301,7 @@ namespace nxdn RingBuffer m_txImmQueue; RingBuffer m_txQueue; - static std::mutex m_queueLock; + static std::mutex s_queueLock; RPT_RF_STATE m_rfState; uint32_t m_rfLastDstId; diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index e4e1fb862..47519dd48 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -90,25 +90,25 @@ using namespace nxdn::packet; // Macro helper to verbose log a generic message. #define VERBOSE_LOG_MSG(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic message. #define VERBOSE_LOG_MSG_DST(_PCKT_STR, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, "NXDN, %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ + LogInfoEx(LOG_RF, "NXDN, %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ } // Macro helper to verbose log a generic network message. #define VERBOSE_LOG_MSG_NET(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_NET, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_NET, "NXDN, %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic network message. #define DEBUG_LOG_MSG(_PCKT_STR) \ if (m_debug) { \ - LogMessage(LOG_RF, "NXDN, %s", _PCKT_STR.c_str()); \ + LogInfoEx(LOG_RF, "NXDN, %s", _PCKT_STR.c_str()); \ } // --------------------------------------------------------------------------- @@ -192,7 +192,7 @@ bool ControlSignaling::process(FuncChannelType::E fct, ChOption::E option, uint8 IS_SUPPORT_CONTROL_CHECK(rcch->toString(true), MessageType::RCCH_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, %s, srcId = %u, locId = $%06X, regOption = $%02X", + LogInfoEx(LOG_RF, "NXDN, %s, srcId = %u, locId = $%06X, regOption = $%02X", rcch->toString(true).c_str(), srcId, rcch->getLocId(), rcch->getRegOption()); } @@ -205,7 +205,7 @@ bool ControlSignaling::process(FuncChannelType::E fct, ChOption::E option, uint8 IS_SUPPORT_CONTROL_CHECK(rcch->toString(true), MessageType::RCCH_GRP_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u, locId = $%06X", + LogInfoEx(LOG_RF, "NXDN, %s, srcId = %u, dstId = %u, locId = $%06X", rcch->toString(true).c_str(), srcId, dstId, rcch->getLocId()); } @@ -252,7 +252,7 @@ bool ControlSignaling::processNetwork(FuncChannelType::E fct, ChOption::E option if (m_nxdn->m_dedicatedControl) { if (!m_nxdn->m_affiliations->isGranted(dstId)) { if (m_verbose) { - LogMessage(LOG_NET, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), srcId, dstId); } @@ -645,7 +645,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin rcch->setPriority(priority); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, "NXDN, %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", rcch->toString().c_str(), rcch->getEmergency(), rcch->getEncrypted(), rcch->getPriority(), rcch->getGrpVchNo(), rcch->getSrcId(), rcch->getDstId()); } @@ -673,7 +673,7 @@ void ControlSignaling::writeRF_Message_Deny(uint32_t srcId, uint32_t dstId, uint rcch->setDstId(dstId); if (m_verbose) { - LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X (%s), service = $%02X, srcId = %u, dstId = %u", reason, NXDNUtils::causeToString(reason).c_str(), service, srcId, dstId); } @@ -774,7 +774,7 @@ void ControlSignaling::writeRF_Message_U_Reg_Rsp(uint32_t srcId, uint32_t dstId, if (rcch->getCauseResponse() == CauseResponse::MM_REG_ACCEPTED) { if (m_verbose) { - LogMessage(LOG_RF, "NXDN, %s, srcId = %u, locId = $%06X", + LogInfoEx(LOG_RF, "NXDN, %s, srcId = %u, locId = $%06X", rcch->toString().c_str(), srcId, locId); } diff --git a/src/host/nxdn/packet/Data.cpp b/src/host/nxdn/packet/Data.cpp index 0d432a6ca..97b0ed981 100644 --- a/src/host/nxdn/packet/Data.cpp +++ b/src/host/nxdn/packet/Data.cpp @@ -147,11 +147,12 @@ bool Data::process(ChOption::E option, uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", srcId, dstId, lc.getPacketInfo().getDelivery(), lc.getPacketInfo().getBlockCount(), lc.getPacketInfo().getPadCount(), lc.getPacketInfo().getStart(), lc.getPacketInfo().getFragmentCount()); } ::ActivityLog("NXDN", true, "RF data transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "NXDN Data Call, srcId = %u, dstId = %u", srcId, dstId); m_nxdn->m_rfLC = lc; m_nxdn->m_voice->m_rfFrames = 0U; @@ -198,7 +199,7 @@ bool Data::process(ChOption::E option, uint8_t* data, uint32_t len) if (data[0U] == modem::TAG_EOT) { ::ActivityLog("NXDN", true, "RF ended RF data transmission"); - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", m_nxdn->m_voice->m_rfFrames); m_nxdn->writeEndRF(); @@ -315,11 +316,12 @@ bool Data::processNetwork(ChOption::E option, lc::RTCH& netLC, uint8_t* data, ui } if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_DCALL_HDR ", srcId = %u, dstId = %u, ack = %u, blocksToFollow = %u, padCount = %u, firstFragment = %u, fragmentCount = %u", srcId, dstId, lc.getPacketInfo().getDelivery(), lc.getPacketInfo().getBlockCount(), lc.getPacketInfo().getPadCount(), lc.getPacketInfo().getStart(), lc.getPacketInfo().getFragmentCount()); } ::ActivityLog("NXDN", false, "network data transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "NXDN Data Call, srcId = %u, dstId = %u", srcId, dstId); m_nxdn->m_netLC = lc; m_nxdn->m_voice->m_netFrames = 0U; @@ -362,7 +364,7 @@ bool Data::processNetwork(ChOption::E option, lc::RTCH& netLC, uint8_t* data, ui if (data[0U] == modem::TAG_EOT) { ::ActivityLog("NXDN", true, "network ended RF data transmission"); - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", m_nxdn->m_voice->m_netFrames); m_nxdn->writeEndNet(); diff --git a/src/host/nxdn/packet/Voice.cpp b/src/host/nxdn/packet/Voice.cpp index 755ca1c77..5c1009ab0 100644 --- a/src/host/nxdn/packet/Voice.cpp +++ b/src/host/nxdn/packet/Voice.cpp @@ -231,7 +231,7 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u float(m_rfFrames) / 12.5F, float(m_rfErrs * 100U) / float(m_rfBits)); } - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); m_nxdn->writeEndRF(); @@ -248,11 +248,12 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u m_nxdn->m_rssiCount = 1U; if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%02X", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%02X", srcId, dstId, group, lc.getEmergency(), encrypted, lc.getPriority(), lc.getAlgId(), lc.getKId()); } ::ActivityLog("NXDN", true, "RF %svoice transmission from %u to %s%u", encrypted ? "encrypted " : "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "NXDN Voice Call, srcId = %u, dstId = %u", srcId, dstId); } return true; @@ -399,11 +400,12 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u m_nxdn->m_rssiCount = 1U; if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%04X", + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%04X", srcId, dstId, group, m_nxdn->m_rfLC.getEmergency(), encrypted, m_nxdn->m_rfLC.getPriority(), m_nxdn->m_rfLC.getAlgId(), m_nxdn->m_rfLC.getKId()); } ::ActivityLog("NXDN", true, "RF %slate entry from %u to %s%u", encrypted ? "encrypted ": "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "NXDN Voice Call, srcId = %u, dstId = %u", srcId, dstId); // create a dummy start message uint8_t start[NXDN_FRAME_LENGTH_BYTES + 2U]; @@ -502,7 +504,7 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u m_rfBits += 188U; if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/188 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 1.88F); + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/188 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 1.88F); } } else if (option == ChOption::STEAL_FACCH1_1) { channel::FACCH1 facch11; @@ -532,7 +534,7 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u m_rfBits += 94U; if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 0.94F); + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 0.94F); } } else if (option == ChOption::STEAL_FACCH1_2) { edac::AMBEFEC ambe; @@ -557,7 +559,7 @@ bool Voice::process(FuncChannelType::E fct, ChOption::E option, uint8_t* data, u m_rfBits += 94U; if (m_verbose) { - LogMessage(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, audio, errs = %u/94 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 0.94F); + LogInfoEx(LOG_RF, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, audio, errs = %u/94 (%.1f%%)", m_nxdn->m_rfLC.getSrcId(), m_nxdn->m_rfLC.getDstId(), errors, float(errors) / 0.94F); } channel::FACCH1 facch12; @@ -745,7 +747,7 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& ::ActivityLog("NXDN", false, "network end of transmission, %.1f seconds", float(m_netFrames) / 12.5F); - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", m_netFrames); + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_TX_REL ", total frames: %d", m_netFrames); m_nxdn->writeEndNet(); } else { @@ -754,11 +756,12 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& m_nxdn->m_netState = RS_NET_AUDIO; if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%02X", + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%02X", srcId, dstId, group, lc.getEmergency(), encrypted, lc.getPriority(), lc.getAlgId(), lc.getKId()); } ::ActivityLog("NXDN", false, "network %svoice transmission from %u to %s%u", encrypted ? "encrypted " : "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "NXDN Voice Call, srcId = %u, dstId = %u", srcId, dstId); } return true; @@ -890,11 +893,12 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& m_nxdn->m_netState = RS_NET_AUDIO; if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%04X", + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, algo = $%02X, kid = $%04X", srcId, dstId, group, m_nxdn->m_netLC.getEmergency(), encrypted, m_nxdn->m_netLC.getPriority(), m_nxdn->m_netLC.getAlgId(), m_nxdn->m_netLC.getKId()); } ::ActivityLog("NXDN", false, "network %slate entry from %u to %s%u", encrypted ? "encrypted ": "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "NXDN Voice Call, srcId = %u, dstId = %u", srcId, dstId); // create a dummy start message uint8_t start[NXDN_FRAME_LENGTH_BYTES + 2U]; @@ -971,7 +975,7 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& m_rfBits += 188U; if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/141 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 1.88F); + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/141 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 1.88F); } } else if (option == ChOption::STEAL_FACCH1_1) { channel::FACCH1 facch1; @@ -990,7 +994,7 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& m_rfBits += 94U; if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 0.94F); + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 0.94F); } } else if (option == ChOption::STEAL_FACCH1_2) { edac::AMBEFEC ambe; @@ -1004,7 +1008,7 @@ bool Voice::processNetwork(FuncChannelType::E fct, ChOption::E option, lc::RTCH& m_rfBits += 94U; if (m_verbose) { - LogMessage(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 0.94F); + LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u, errs = %u/94 (%.1f%%)", m_nxdn->m_netLC.getSrcId(), m_nxdn->m_netLC.getDstId(), errors, float(errors) / 0.94F); } channel::FACCH1 facch1; bool valid = facch1.decode(data + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index edbefed48..d9112f7dc 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -41,8 +41,8 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U; // Static Class Members // --------------------------------------------------------------------------- -std::mutex Control::m_queueLock; -std::mutex Control::m_activeTGLock; +std::mutex Control::s_queueLock; +std::mutex Control::s_activeTGLock; // --------------------------------------------------------------------------- // Public Class Members @@ -248,7 +248,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw yaml::Node rfssConfig = systemConf["config"]; m_control->m_patchSuperGroup = (uint32_t)::strtoul(rfssConfig["pSuperGroup"].as("FFFE").c_str(), NULL, 16); m_control->m_announcementGroup = (uint32_t)::strtoul(rfssConfig["announcementGroup"].as("FFFE").c_str(), NULL, 16); - m_defaultNetIdleTalkgroup = (uint32_t)::strtoul(rfssConfig["defaultNetIdleTalkgroup"].as("0").c_str(), NULL, 16); + m_defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as(0U); yaml::Node secureConfig = rfssConfig["secure"]; std::string key = secureConfig["key"].as(); @@ -452,7 +452,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); // set the grant release callback - m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) { + m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t srcId, uint32_t dstId, uint8_t slot) { // callback REST API to clear TG permit for the granted TG on the specified voice channel if (m_authoritative && m_supervisor) { ::lookups::VoiceChData voiceChData = m_affiliations->rfCh()->getRFChData(chNo); @@ -483,7 +483,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw // set the In-Call Control function callback if (m_network != nullptr) { - m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId) { processInCallCtrl(command, dstId); }); + m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, + uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId); }); } // throw a warning if we are notifying a CC of our presence (this indicates we're a VC) *AND* we have the control @@ -502,7 +503,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Disable Grant Source ID Check: yes"); } if (m_supervisor) - LogMessage(LOG_P25, "Host is configured to operate as a P25 control channel, site controller mode."); + LogInfoEx(LOG_P25, "Host is configured to operate as a P25 control channel, site controller mode."); } if (m_controlOnly) { @@ -520,7 +521,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw LogInfo(" Patch Super Group: $%04X", m_control->m_patchSuperGroup); LogInfo(" Announcement Group: $%04X", m_control->m_announcementGroup); if (m_defaultNetIdleTalkgroup != 0U) { - LogInfo(" Default Network Idle Talkgroup: $%04X", m_defaultNetIdleTalkgroup); + LogInfo(" Default Network Idle Talkgroup: %u", m_defaultNetIdleTalkgroup); } LogInfo(" Notify Control: %s", m_notifyCC ? "yes" : "no"); @@ -580,7 +581,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw // are we overriding the NAC for split NAC operations? uint32_t txNAC = (uint32_t)::strtoul(systemConf["config"]["txNAC"].as("F7E").c_str(), NULL, 16); if (txNAC != NAC_DIGITAL_SQ && txNAC != m_nac) { - LogMessage(LOG_P25, "Split NAC operations, setting Tx NAC to $%03X", txNAC); + LogInfoEx(LOG_P25, "Split NAC operations, setting Tx NAC to $%03X", txNAC); m_txNAC = txNAC; m_nid.setTxNAC(m_txNAC); } @@ -684,7 +685,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) // Convert the raw RSSI to dBm int rssi = m_rssiMapper->interpolate(raw); if (m_verbose) { - LogMessage(LOG_RF, "P25, raw RSSI = %u, reported RSSI = %d dBm", raw, rssi); + LogInfoEx(LOG_RF, "P25, raw RSSI = %u, reported RSSI = %d dBm", raw, rssi); } // RSSI is always reported as positive @@ -760,7 +761,7 @@ bool Control::processFrame(uint8_t* data, uint32_t len) uint32_t Control::peekFrameLength() { - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; @@ -811,7 +812,7 @@ uint32_t Control::getFrame(uint8_t* data) { assert(data != nullptr); - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (m_txQueue.isEmpty() && m_txImmQueue.isEmpty()) return 0U; @@ -936,7 +937,7 @@ void Control::clock() if (m_rfTGHang.hasExpired()) { m_rfTGHang.stop(); if (m_verbose) { - LogMessage(LOG_RF, "talkgroup hang has expired, lastDstId = %u", m_rfLastDstId); + LogInfoEx(LOG_RF, "talkgroup hang has expired, lastDstId = %u", m_rfLastDstId); } m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -972,8 +973,17 @@ void Control::clock() if (m_netTGHang.hasExpired()) { m_netTGHang.stop(); if (m_verbose) { - LogMessage(LOG_NET, "talkgroup hang has expired, lastDstId = %u", m_netLastDstId); + LogInfoEx(LOG_NET, "talkgroup hang has expired, lastDstId = %u", m_netLastDstId); } + + // if the group is still granted at this point -- forcibly release it + if (m_affiliations->isGranted(m_netLastDstId)) { + if (!m_dedicatedControl) { + m_affiliations->releaseGrant(m_netLastDstId, false); + m_network->resetP25(); + } + } + m_netLastDstId = 0U; m_netLastSrcId = 0U; } @@ -1153,7 +1163,7 @@ void Control::clockSiteData(uint32_t ms) } } else - ::LogMessage(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); + ::LogInfoEx(LOG_P25, "VC %s:%u, active TG update, activeCnt = %u", voiceChData.address().c_str(), voiceChData.port(), activeCnt); }, voiceChData.address(), voiceChData.port()); } } @@ -1183,7 +1193,7 @@ void Control::clockSiteData(uint32_t ms) } } else - ::LogMessage(LOG_P25, "VC %s:%u, clear active TG update", voiceChData.address().c_str(), voiceChData.port()); + ::LogInfoEx(LOG_P25, "VC %s:%u, clear active TG update", voiceChData.address().c_str(), voiceChData.port()); }, voiceChData.address(), voiceChData.port()); } } @@ -1205,9 +1215,9 @@ void Control::permittedTG(uint32_t dstId, bool dataPermit) if (m_verbose) { if (dstId == 0U) - LogMessage(LOG_P25, "non-authoritative TG unpermit"); + LogInfoEx(LOG_P25, "non-authoritative TG unpermit"); else - LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); + LogInfoEx(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); } m_permittedDstId = dstId; @@ -1227,7 +1237,7 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp) } if (m_verbose) { - LogMessage(LOG_P25, "network TG grant demand, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_P25, "network TG grant demand, srcId = %u, dstId = %u", srcId, dstId); } m_control->writeRF_TSDU_Grant(srcId, dstId, 4U, grp); @@ -1307,7 +1317,7 @@ void Control::addFrame(const uint8_t* data, uint32_t length, bool net, bool imm) { assert(data != nullptr); - std::lock_guard lock(m_queueLock); + std::lock_guard lock(s_queueLock); if (!net) { if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired()) @@ -1399,7 +1409,6 @@ void Control::processNetwork() if (m_netState != RS_NET_DATA) { // don't process network frames if the RF modem isn't in a listening state if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) { - m_network->resetP25(); return; } } @@ -1451,13 +1460,14 @@ void Control::processNetwork() } uint32_t blockLength = GET_UINT24(buffer, 8U); + uint8_t currentBlock = buffer[21U]; if (m_debug) { LogDebug(LOG_NET, "P25, duid = $%02X, MFId = $%02X, blockLength = %u, len = %u", duid, MFId, blockLength, length); } if (!m_dedicatedControl) - m_data->processNetwork(data.get(), frameLength, blockLength); + m_data->processNetwork(data.get(), frameLength, currentBlock, blockLength); return; } @@ -1589,7 +1599,7 @@ void Control::processNetwork() (control.getPriority() & 0x07U); // Priority if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR " remote grant demand, srcId = %u, dstId = %u, unitToUnit = %u, encrypted = %u", srcId, dstId, unitToUnit, grantEncrypt); + LogInfoEx(LOG_NET, P25_TSDU_STR " remote grant demand, srcId = %u, dstId = %u, unitToUnit = %u, encrypted = %u", srcId, dstId, unitToUnit, grantEncrypt); } // are we denying the grant? @@ -1636,7 +1646,7 @@ void Control::processFrameLoss() float(m_voice->m_rfFrames) / 5.56F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits), m_frameLossCnt); } - LogMessage(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_voice->m_rfFrames, m_voice->m_rfBits, m_voice->m_rfUndecodableLC, m_voice->m_rfErrs, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); m_affiliations->releaseGrant(m_voice->m_rfLC.getDstId(), false); @@ -1741,7 +1751,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_P25, "CC %s:%u, notifying CC of call termination, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + LogInfoEx(LOG_P25, "CC %s:%u, notifying CC of call termination, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); } // callback REST API to release the granted TG on the specified control channel @@ -1763,7 +1773,7 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) } } else - ::LogMessage(LOG_P25, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogInfoEx(LOG_P25, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); }, m_controlChData.address(), m_controlChData.port()); m_rfLastDstId = 0U; @@ -1807,7 +1817,7 @@ void Control::notifyCC_TouchGrant(uint32_t dstId) } } else - ::LogMessage(LOG_P25, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + ::LogInfoEx(LOG_P25, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); }, m_controlChData.address(), m_controlChData.port()); } @@ -1988,7 +1998,7 @@ void Control::RPC_activeTG(json::object& req, json::object& reply) } json::array active = req["active"].get(); - std::lock_guard lock(m_activeTGLock); + std::lock_guard lock(s_activeTGLock); m_activeTG.clear(); if (active.size() > 0) { @@ -2002,7 +2012,7 @@ void Control::RPC_activeTG(json::object& req, json::object& reply) } } - ::LogMessage(LOG_P25, "active TG update, activeCnt = %u", m_activeTG.size()); + ::LogInfoEx(LOG_P25, "active TG update, activeCnt = %u", m_activeTG.size()); } /* (RPC Handler) Clear active TGID list from the authoritative CC host. */ @@ -2012,7 +2022,7 @@ void Control::RPC_clearActiveTG(json::object& req, json::object& reply) g_RPC->defaultResponse(reply, "OK", network::NetRPC::OK); if (m_activeTG.size() > 0) { - std::lock_guard lock(m_activeTGLock); + std::lock_guard lock(s_activeTGLock); m_activeTG.clear(); } } @@ -2044,7 +2054,7 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) // LogDebugEx(LOG_P25, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId); if (m_verbose) { - LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); + LogInfoEx(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); } if (m_affiliations->isGranted(dstId)) { @@ -2053,7 +2063,7 @@ void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } m_affiliations->releaseGrant(dstId, false); @@ -2092,7 +2102,7 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply) ::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo); if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + LogInfoEx(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chNo = %u-%u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); } m_affiliations->touchGrant(dstId); @@ -2138,7 +2148,7 @@ void Control::generateLLA_AM1_Parameters() ::memcpy(m_llaKS, KS, AUTH_KEY_LENGTH_BYTES); if (m_verbose) { - LogMessage(LOG_P25, "P25, generated LLA AM1 parameters"); + LogInfoEx(LOG_P25, "P25, generated LLA AM1 parameters"); } // cleanup diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index 8ebc30385..e14665ccd 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -330,7 +330,7 @@ namespace p25 RingBuffer m_txImmQueue; RingBuffer m_txQueue; - static std::mutex m_queueLock; + static std::mutex s_queueLock; RPT_RF_STATE m_rfState; uint32_t m_rfLastDstId; @@ -387,7 +387,7 @@ namespace p25 uint32_t m_aveRSSI; uint32_t m_rssiCount; - static std::mutex m_activeTGLock; + static std::mutex s_activeTGLock; bool m_ccNotifyActiveTG; bool m_disableAdjSiteBroadcast; diff --git a/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp b/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp index a38f0d258..0e4132f22 100644 --- a/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp +++ b/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.cpp @@ -55,8 +55,8 @@ void OSP_DVM_GIT_HASH::encode(uint8_t* data, bool rawTSBK, bool noTrellis) tsbkValue = (tsbkValue << 8) + (g_gitHashBytes[2U]); // ... tsbkValue = (tsbkValue << 8) + (g_gitHashBytes[3U]); // ... tsbkValue = (tsbkValue << 16) + 0U; - tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID - tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.h b/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.h index 8062920c2..f4e8308ee 100644 --- a/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.h +++ b/src/host/p25/lc/tsbk/OSP_DVM_GIT_HASH.h @@ -4,9 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * @package DVM / Modem Host Software - * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) - * * Copyright (C) 2022 Bryan Biedenkapp, N2PLL * */ diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 7cd0971fc..39ea922b7 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -114,25 +114,25 @@ using namespace p25::packet; // Macro helper to verbose log a generic TSBK. #define VERBOSE_LOG_TSBK(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic TSBK. #define VERBOSE_LOG_TSBK_DST(_PCKT_STR, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_RF, P25_TSDU_STR ", %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, dstId = %u", _PCKT_STR.c_str(), _DSTID); \ } // Macro helper to verbose log a generic network TSBK. #define VERBOSE_LOG_TSBK_NET(_PCKT_STR, _SRCID, _DSTID) \ if (m_verbose) { \ - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", _PCKT_STR.c_str(), _SRCID, _DSTID); \ } // Macro helper to verbose log a generic network TSBK. #define DEBUG_LOG_TSBK(_PCKT_STR) \ if (m_debug) { \ - LogMessage(LOG_RF, P25_TSDU_STR ", %s", _PCKT_STR.c_str()); \ + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s", _PCKT_STR.c_str()); \ } #define RF_TO_WRITE_NET(OSP) \ @@ -286,7 +286,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getResponse(), srcId, dstId); } @@ -331,7 +331,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = $%04X, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = $%04X, srcId = %u", tsbk->toString(true).c_str(), isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId); } @@ -353,7 +353,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = %u, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, dataServiceOptions = $%02X, dataAccessControl = %u, srcId = %u", tsbk->toString(true).c_str(), isp->getDataServiceOptions(), isp->getDataAccessControl(), srcId); } @@ -372,7 +372,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", tsbk->toString(true).c_str(), iosp->getStatus(), srcId); } @@ -392,7 +392,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getMessage(), srcId, dstId); } @@ -415,7 +415,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", tsbk->toString(true).c_str(), srcId, dstId, iosp->getTxMult()); } @@ -448,7 +448,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), srcId, dstId); } @@ -469,7 +469,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, reason = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, reason = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), isp->getAIV(), isp->getService(), isp->getResponse(), srcId, dstId); } @@ -482,7 +482,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, op = $%02X, arg = %u, tgt = %u", tsbk->toString(true).c_str(), iosp->getExtendedFunction(), srcId, dstId); } @@ -564,7 +564,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, anncId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, anncId = %u", tsbk->toString(true).c_str(), srcId, dstId, isp->getAnnounceGroup()); } @@ -588,7 +588,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true), TSBKO::ISP_U_DEREG_REQ, srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", tsbk->toString(true).c_str(), srcId, tsbk->getSysId(), tsbk->getNetId()); } @@ -605,7 +605,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true), TSBKO::IOSP_U_REG, srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X, netId = $%05X", tsbk->toString(true).c_str(), srcId, tsbk->getSysId(), tsbk->getNetId()); } @@ -637,7 +637,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", tsbk->toString(true).c_str(), srcId); } @@ -752,7 +752,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr } if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); } @@ -774,7 +774,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr } if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); } @@ -801,7 +801,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr uint32_t chNo = tsbk->getGrpVchNo(); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, chNo = %u, srcId = %u, dstId = %u", tsbk->toString().c_str(), chNo, srcId, dstId); } @@ -831,7 +831,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr if (m_p25->m_enableControl && m_p25->m_dedicatedControl) { if (!m_p25->m_affiliations->isGranted(dstId)) { if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchId(), tsbk->getGrpVchNo(), srcId, dstId); } @@ -852,7 +852,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr IOSP_UU_ANS* iosp = static_cast(tsbk.get()); if (iosp->getResponse() > 0U) { if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getResponse(), srcId, dstId); } } @@ -868,7 +868,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u", tsbk->toString(true).c_str(), iosp->getStatus(), srcId); } @@ -882,7 +882,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getMessage(), srcId, dstId); } @@ -933,7 +933,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), dstId, srcId); } @@ -947,7 +947,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId); } @@ -1070,7 +1070,7 @@ void ControlSignaling::writeAdjSSNetwork() osp->setAdjSiteSvcClass(m_p25->m_siteData.serviceClass()); if (m_verbose) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, network announce, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", osp->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, network announce, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", osp->toString().c_str(), m_p25->m_siteData.sysId(), m_p25->m_siteData.rfssId(), m_p25->m_siteData.siteId(), m_p25->m_siteData.channelId(), m_p25->m_siteData.channelNo(), m_p25->m_siteData.serviceClass()); } @@ -1107,7 +1107,7 @@ void ControlSignaling::writeRF_TSDU_Radio_Mon(uint32_t srcId, uint32_t dstId, ui iosp->setTxMult(txMult); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId, txMult); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u, txMult = %u", iosp->toString().c_str(), srcId, dstId, txMult); } ::ActivityLog("P25", true, "Radio Unit Monitor request from %u to %u", srcId, dstId); @@ -1135,7 +1135,7 @@ void ControlSignaling::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32 } if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); } @@ -1409,7 +1409,7 @@ void ControlSignaling::writeRF_TDULC(lc::TDULC* lc, bool noNetwork) } //if (m_verbose) { - // LogMessage(LOG_RF, P25_TDULC_STR ", lc = $%02X, srcId = %u", m_rfTDULC.getLCO(), m_rfTDULC.getSrcId()); + // LogInfoEx(LOG_RF, P25_TDULC_STR ", lc = $%02X, srcId = %u", m_rfTDULC.getLCO(), m_rfTDULC.getSrcId()); //} } @@ -1438,7 +1438,7 @@ void ControlSignaling::writeNet_TDULC(lc::TDULC* lc) m_p25->addFrame(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U, true); if (m_verbose) { - LogMessage(LOG_NET, P25_TDULC_STR ", lc = $%02X, srcId = %u", lc->getLCO(), lc->getSrcId()); + LogInfoEx(LOG_NET, P25_TDULC_STR ", lc = $%02X, srcId = %u", lc->getLCO(), lc->getSrcId()); } if (m_p25->m_voice->m_netFrames > 0) { @@ -1699,7 +1699,7 @@ void ControlSignaling::writeRF_TSDU_AMBT(lc::AMBT* ambt, bool imm) Utils::dump(1U, "!!! *PDU (AMBT) TSBK Block Data", pduUserData, P25_PDU_UNCONFIRMED_LENGTH_BYTES * header.getBlocksToFollow()); } - m_p25->m_data->writeRF_PDU_User(header, false, pduUserData, imm); + m_p25->m_data->writeRF_PDU_User(header, false, false, pduUserData, imm); } /* @@ -1744,7 +1744,7 @@ void ControlSignaling::writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint3 } if (m_verbose) { - LogMessage(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), srcId = %u, dstId = %u", srcId, dstId); } lc = std::make_unique(); @@ -2371,7 +2371,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ osp->setForceChannelId(true); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", osp->toString().c_str(), osp->getEmergency(), osp->getEncrypted(), osp->getPriority(), osp->getGrpVchId(), osp->getGrpVchNo(), osp->getSrcId(), osp->getDstId()); } @@ -2395,7 +2395,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!voiceChData.isExplicitCh()) { if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchId(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); } @@ -2475,7 +2475,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ osp->setForceChannelId(true); if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", osp->toString().c_str(), osp->getEmergency(), osp->getEncrypted(), osp->getPriority(), osp->getGrpVchId(), osp->getGrpVchNo(), osp->getSrcId(), osp->getDstId()); } @@ -2499,7 +2499,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (!voiceChData.isExplicitCh()) { if (m_verbose) { - LogMessage((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", + LogInfoEx((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u, dstId = %u", iosp->toString().c_str(), iosp->getEmergency(), iosp->getEncrypted(), iosp->getPriority(), iosp->getGrpVchId(), iosp->getGrpVchNo(), iosp->getSrcId(), iosp->getDstId()); } @@ -2708,7 +2708,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 ::ActivityLog("P25", true, "SNDCP grant request from %u", srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, chNo = %u-%u, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, chNo = %u-%u, srcId = %u", osp->toString().c_str(), voiceChData.chId(), osp->getDataChnNo(), osp->getSrcId()); } @@ -2750,7 +2750,7 @@ void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bo } if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u", iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), srcId); } @@ -2770,7 +2770,7 @@ void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", osp->toString().c_str(), osp->getAIV(), reason, P25Utils::denyRsnToString(reason).c_str(), osp->getSrcId(), osp->getDstId()); } @@ -2850,7 +2850,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI if (iosp->getResponse() == ResponseCode::ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, anncId = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, anncId = %u, srcId = %u, dstId = %u", iosp->toString().c_str(), m_announcementGroup, srcId, dstId); } @@ -2867,7 +2867,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI // is the RF talkgroup hang timer running? if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { if (m_verbose) { - LogMessage(LOG_RF, "talkgroup hang has terminated, lastDstId = %u", m_p25->m_rfLastDstId); + LogInfoEx(LOG_RF, "talkgroup hang has terminated, lastDstId = %u", m_p25->m_rfLastDstId); } m_p25->m_rfTGHang.stop(); @@ -2907,7 +2907,7 @@ void ControlSignaling::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId, uint32_t sysId) if (iosp->getResponse() == ResponseCode::ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X", iosp->toString().c_str(), srcId, sysId); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, sysId = $%03X", iosp->toString().c_str(), srcId, sysId); } ::ActivityLog("P25", true, "unit registration request from %u", srcId); @@ -2945,7 +2945,7 @@ void ControlSignaling::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) osp->setDstId(srcId); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", osp->toString().c_str(), srcId); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u", osp->toString().c_str(), srcId); } ::ActivityLog("P25", true, "unit deregistration request from %u", srcId); @@ -2970,7 +2970,7 @@ void ControlSignaling::writeRF_TSDU_Queue(uint32_t srcId, uint32_t dstId, uint8_ osp->setGroup(grp); if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X (%s), srcId = %u, dstId = %u", osp->toString().c_str(), osp->getAIV(), reason, P25Utils::queueRsnToString(reason).c_str(), osp->getSrcId(), osp->getDstId()); } @@ -3033,7 +3033,7 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, if (osp->getResponse() == ResponseCode::ACCEPT) { if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", osp->toString().c_str(), srcId, dstId); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, dstId = %u", osp->toString().c_str(), srcId, dstId); } ::ActivityLog("P25", true, "location registration request from %u", srcId); @@ -3077,7 +3077,7 @@ void ControlSignaling::writeRF_TSDU_Auth_Dmd(uint32_t srcId) m_llaDemandTable[srcId] = challenge; if (m_verbose) { - LogMessage(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, srcId = %u, RC = %X", osp->toString().c_str(), srcId, challenge); } writeRF_TSDU_AMBT(osp.get(), true); diff --git a/src/host/p25/packet/Data.cpp b/src/host/p25/packet/Data.cpp index 017c2c478..67d60a9e2 100644 --- a/src/host/p25/packet/Data.cpp +++ b/src/host/p25/packet/Data.cpp @@ -47,11 +47,8 @@ const uint32_t SNDCP_STANDBY_TIMEOUT = 60U; void Data::resetRF() { - m_rfDataBlockCnt = 0U; m_rfPDUCount = 0U; m_rfPDUBits = 0U; - - m_rfDataHeader.reset(); } /* Process a data frame from the RF interface. */ @@ -82,9 +79,6 @@ bool Data::process(uint8_t* data, uint32_t len) m_inbound = true; if (m_p25->m_rfState != RS_RF_DATA) { - m_rfDataHeader.reset(); - m_rfExtendedAddress = false; - m_rfDataBlockCnt = 0U; m_rfPDUCount = 0U; m_rfPDUBits = 0U; @@ -104,38 +98,14 @@ bool Data::process(uint8_t* data, uint32_t len) ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); uint32_t bits = P25Utils::decode(data + 2U, buffer, start, start + P25_PDU_FRAME_LENGTH_BITS); - m_rfPDUBits = Utils::getBits(buffer, m_rfPDU, 0U, bits); + m_rfPDUBits += Utils::getBits(buffer, m_rfPDU, 0U, bits); - uint32_t offset = P25_PREAMBLE_LENGTH_BITS + P25_PDU_FEC_LENGTH_BITS; if (m_rfPDUCount == 0U) { ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); Utils::getBitRange(m_rfPDU, buffer, P25_PREAMBLE_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); - bool ret = m_rfDataHeader.decode(buffer); - if (!ret) { - LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - - m_rfDataHeader.reset(); - m_rfExtendedAddress = false; - m_rfPDUCount = 0U; - m_rfPDUBits = 0U; - m_p25->m_rfState = m_prevRfState; - return false; - } - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, llId = %u", - m_rfDataHeader.getAckNeeded(), m_rfDataHeader.getOutbound(), m_rfDataHeader.getFormat(), m_rfDataHeader.getMFId(), m_rfDataHeader.getSAP(), m_rfDataHeader.getFullMessage(), - m_rfDataHeader.getBlocksToFollow(), m_rfDataHeader.getPadLength(), m_rfDataHeader.getPacketLength(), m_rfDataHeader.getSynchronize(), m_rfDataHeader.getNs(), m_rfDataHeader.getFSN(), m_rfDataHeader.getLastFragment(), - m_rfDataHeader.getHeaderOffset(), m_rfDataHeader.getLLId()); - } - - // make sure we don't get a PDU with more blocks then we support - if (m_rfDataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { - LogError(LOG_RF, P25_PDU_STR ", ISP, too many PDU blocks to process, %u > %u", m_rfDataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); - m_rfDataHeader.reset(); - m_rfExtendedAddress = false; + bool ret = m_rfAssembler->disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + if (!ret) { m_rfPDUCount = 0U; m_rfPDUBits = 0U; m_p25->m_rfState = m_prevRfState; @@ -143,347 +113,269 @@ bool Data::process(uint8_t* data, uint32_t len) } // if we're a dedicated CC or in control only mode, we only want to handle AMBTs. Otherwise return - if ((m_p25->m_dedicatedControl || m_p25->m_controlOnly) && m_rfDataHeader.getFormat() != PDUFormatType::AMBT) { + if ((m_p25->m_dedicatedControl || m_p25->m_controlOnly) && m_rfAssembler->dataHeader.getFormat() != PDUFormatType::AMBT) { if (m_debug) { LogDebug(LOG_RF, "CC only mode, ignoring non-AMBT PDU from RF"); } m_p25->m_ccHalted = false; - m_rfDataHeader.reset(); - m_rfExtendedAddress = false; m_rfPDUCount = 0U; m_rfPDUBits = 0U; m_p25->m_rfState = m_prevRfState; return false; } - } - if (m_p25->m_rfState == RS_RF_DATA) { - uint32_t blocksToFollow = m_rfDataHeader.getBlocksToFollow(); - uint32_t dataOffset = 0U; + // did we receive a response header? + if (m_rfAssembler->dataHeader.getFormat() == PDUFormatType::RSP) { + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + m_rfAssembler->dataHeader.getFormat(), m_rfAssembler->dataHeader.getResponseClass(), m_rfAssembler->dataHeader.getResponseType(), m_rfAssembler->dataHeader.getResponseStatus(), + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getSrcLLId()); + + if (m_rfAssembler->dataHeader.getResponseClass() == PDUAckClass::ACK && m_rfAssembler->dataHeader.getResponseType() == PDUAckType::ACK) { + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getResponseStatus()); + if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { + delete m_retryPDUData; + m_retryPDUData = nullptr; + + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + } + } else { + if (m_rfAssembler->dataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (m_rfAssembler->dataHeader.getResponseType()) { + case PDUAckType::NACK_ILLEGAL: + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", + m_rfAssembler->dataHeader.getLLId()); + break; + case PDUAckType::NACK_PACKET_CRC: + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_SEQ: + case PDUAckType::NACK_OUT_OF_SEQ: + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getResponseStatus()); + break; + case PDUAckType::NACK_UNDELIVERABLE: + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getResponseStatus()); + break; - // process second header if we're using enhanced addressing - if (m_rfDataHeader.getSAP() == PDUSAP::EXT_ADDR && - m_rfDataHeader.getFormat() == PDUFormatType::UNCONFIRMED) { - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - Utils::getBitRange(m_rfPDU, buffer, offset, P25_PDU_FEC_LENGTH_BITS); - bool ret = m_rfDataHeader.decodeExtAddr(buffer); - if (!ret) { - LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); + default: + break; + } + } else if (m_rfAssembler->dataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", + m_rfAssembler->dataHeader.getLLId()); - m_rfDataHeader.reset(); - m_rfPDUCount = 0U; - m_rfPDUBits = 0U; - m_p25->m_rfState = m_prevRfState; - return false; - } + // really this is supposed to check the bit field in the included response + // and only return those bits -- but we're responding with the entire previous packet... + if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { + if (m_retryCount < MAX_PDU_RETRY_CNT) { + m_p25->writeRF_Preamble(); + writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); + m_retryCount++; + } + else { + delete m_retryPDUData; + m_retryPDUData = nullptr; + + m_retryPDUBitLength = 0U; + m_retryCount = 0U; + + LogInfoEx(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", + m_rfAssembler->dataHeader.getLLId()); - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, extended address, sap = $%02X, srcLlId = %u", - m_rfDataHeader.getEXSAP(), m_rfDataHeader.getSrcLLId()); + writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfAssembler->dataHeader.getNs(), m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getSrcLLId()); + } + } + } } - m_rfExtendedAddress = true; + // rewrite the response to the network + writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); - offset += P25_PDU_FEC_LENGTH_BITS; - m_rfPDUCount++; - blocksToFollow--; + // only repeat the PDU locally if the packet isn't for the FNE + if (m_repeatPDU && m_rfAssembler->dataHeader.getLLId() != WUID_FNE) { + writeRF_PDU_Ack_Response(m_rfAssembler->dataHeader.getResponseClass(), m_rfAssembler->dataHeader.getResponseType(), m_rfAssembler->dataHeader.getResponseStatus(), + m_rfAssembler->dataHeader.getLLId(), m_rfAssembler->dataHeader.getSrcLLId()); + } - // if we are using a secondary header place it in the PDU user data buffer - m_rfDataHeader.getExtAddrData(m_rfPduUserData + dataOffset); - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - m_rfPduUserDataLength += P25_PDU_HEADER_LENGTH_BYTES; - } + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_rfPduUserDataLength = 0U; + ::memset(m_rfPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - uint32_t srcId = (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId(); - uint32_t dstId = m_rfDataHeader.getLLId(); + m_p25->m_rfState = RS_RF_LISTENING; + m_inbound = false; + return true; + } m_rfPDUCount++; - uint32_t bitLength = ((blocksToFollow + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + } - m_rfDataBlockCnt = 0U; + if (m_p25->m_rfState == RS_RF_DATA) { + uint32_t blocksToFollow = m_rfAssembler->dataHeader.getBlocksToFollow(); + uint32_t bitLength = ((blocksToFollow + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; if (m_rfPDUBits >= bitLength) { - // process all blocks in the data stream - // if the primary header has a header offset ensure data if offset by that amount - if (m_rfDataHeader.getHeaderOffset() > 0U) { - offset += m_rfDataHeader.getHeaderOffset() * 8; - m_rfPduUserDataLength -= m_rfDataHeader.getHeaderOffset(); - } - - // decode data blocks - for (uint32_t i = 0U; i < blocksToFollow; i++) { + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS + P25_PDU_FEC_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + LogDebugEx(LOG_P25, "Data::process()", "blocksToFollow = %u, bitLength = %u, rfPDUBits = %u, rfPDUCount = %u", blocksToFollow, bitLength, m_rfPDUBits, m_rfPDUCount); ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - Utils::getBitRange(m_rfPDU, buffer, offset, P25_PDU_FEC_LENGTH_BITS); - bool ret = m_rfData[i].decode(buffer, m_rfDataHeader); - if (ret) { - // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks - // set this block as the last block for full packet CRC - if ((m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) || (m_rfDataHeader.getFormat() == PDUFormatType::UNCONFIRMED)) { - if ((m_rfDataBlockCnt + 1U) == blocksToFollow) { - m_rfData[i].setLastBlock(true); - } - } - - // are we processing extended address data from the first block? - if (m_rfDataHeader.getSAP() == PDUSAP::EXT_ADDR && m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED && - m_rfData[i].getSerialNo() == 0U) { - uint8_t secondHeader[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; - ::memset(secondHeader, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); - m_rfData[i].getData(secondHeader); - - m_rfDataHeader.decodeExtAddr(secondHeader); - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, srcLlId = %u", - m_rfData[i].getSerialNo(), m_rfData[i].getFormat(), m_rfData[i].getLastBlock(), m_rfDataHeader.getEXSAP(), m_rfDataHeader.getSrcLLId()); - } - - srcId = m_rfDataHeader.getSrcLLId(); - m_rfExtendedAddress = true; - } - else { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u", - (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_rfData[i].getSerialNo() : m_rfDataBlockCnt, m_rfData[i].getFormat(), - m_rfData[i].getLastBlock()); - } - } - - m_rfData[i].getData(m_rfPduUserData + dataOffset); - dataOffset += (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - m_rfPduUserDataLength = dataOffset; - m_rfDataBlockCnt++; + Utils::getBitRange(m_rfPDU, buffer, i, P25_PDU_FEC_LENGTH_BITS); + + bool ret = m_rfAssembler->disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; } - else { - if (m_rfData[i].getFormat() == PDUFormatType::CONFIRMED) { - LogWarning(LOG_RF, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC), block %u", i); - - // to prevent data block offset errors fill the bad block with 0's - uint8_t blankBuf[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; - ::memset(blankBuf, 0x00U, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); - ::memcpy(m_rfPduUserData + dataOffset, blankBuf, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); - dataOffset += P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; - m_rfPduUserDataLength = dataOffset; - } - else { - LogWarning(LOG_RF, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); - - // to prevent data block offset errors fill the bad block with 0's - uint8_t blankBuf[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; - ::memset(blankBuf, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); - ::memcpy(m_rfPduUserData + dataOffset, blankBuf, P25_PDU_UNCONFIRMED_LENGTH_BYTES); - dataOffset += P25_PDU_UNCONFIRMED_LENGTH_BYTES; - m_rfPduUserDataLength = dataOffset; - } - if (m_dumpPDUData) { - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - } - } - - offset += P25_PDU_FEC_LENGTH_BITS; + m_rfPDUCount++; } + } else { + LogDebugEx(LOG_P25, "Data::process()", "blocksToFollow = %u, bitLength = %u, rfPDUBits = %u, rfPDUCount = %u", blocksToFollow, bitLength, m_rfPDUBits, m_rfPDUCount); + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(m_rfPDU, buffer, P25_PREAMBLE_LENGTH_BITS + P25_PDU_FEC_LENGTH_BITS, P25_PDU_FEC_LENGTH_BITS); - if (m_rfDataHeader.getBlocksToFollow() > 0U) { - bool crcRet = edac::CRC::checkCRC32(m_rfPduUserData, m_rfPduUserDataLength); - if (!crcRet) { - LogWarning(LOG_RF, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_rfDataHeader.getBlocksToFollow(), m_rfPduUserDataLength); - if (!m_p25->m_ignorePDUCRC) { - uint8_t sap = (m_rfExtendedAddress) ? m_rfDataHeader.getEXSAP() : m_rfDataHeader.getSAP(); - if (sap != PDUSAP::TRUNK_CTRL) // ignore CRC errors on TSBK data - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_PACKET_CRC, m_rfDataHeader.getNs(), (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId(), - m_rfExtendedAddress, WUID_FNE); - } - } + bool ret = m_rfAssembler->disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; } - if (m_dumpPDUData && m_rfDataBlockCnt > 0U) { - Utils::dump(1U, "P25, PDU Packet", m_rfPduUserData, m_rfPduUserDataLength); - } + m_rfPDUCount++; + } - if (m_rfDataBlockCnt < blocksToFollow) { - LogWarning(LOG_RF, P25_PDU_STR ", incomplete PDU (%d / %d blocks)", m_rfDataBlockCnt, blocksToFollow); - } + if (m_rfAssembler->getComplete()) { + m_rfPduUserDataLength = m_rfAssembler->getUserDataLength(); + m_rfAssembler->getUserData(m_rfPduUserData); - // did we receive a response header? - if (m_rfDataHeader.getFormat() == PDUFormatType::RSP) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - m_rfDataHeader.getFormat(), m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), - m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); + uint8_t sap = (m_rfAssembler->getExtendedAddress()) ? m_rfAssembler->dataHeader.getEXSAP() : m_rfAssembler->dataHeader.getSAP(); + if (m_rfAssembler->getAuxiliaryES()) + sap = m_rfAssembler->dataHeader.getEXSAP(); - if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK && m_rfDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { - delete m_retryPDUData; - m_retryPDUData = nullptr; + uint32_t srcId = (m_rfAssembler->getExtendedAddress()) ? m_rfAssembler->dataHeader.getSrcLLId() : m_rfAssembler->dataHeader.getLLId(); + uint32_t dstId = m_rfAssembler->dataHeader.getLLId(); - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - } - } else { - if (m_rfDataHeader.getResponseClass() == PDUAckClass::NACK) { - switch (m_rfDataHeader.getResponseType()) { - case PDUAckType::NACK_ILLEGAL: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", - m_rfDataHeader.getLLId()); - break; - case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - case PDUAckType::NACK_SEQ: - case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", - m_rfDataHeader.getLLId(), m_rfDataHeader.getResponseStatus()); - break; - - default: - break; - } - } else if (m_rfDataHeader.getResponseClass() == PDUAckClass::ACK_RETRY) { - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u", - m_rfDataHeader.getLLId()); - - // really this is supposed to check the bit field in the included response - // and only return those bits -- but we're responding with the entire previous packet... - if (m_retryPDUData != nullptr && m_retryPDUBitLength > 0U) { - if (m_retryCount < MAX_PDU_RETRY_CNT) { - m_p25->writeRF_Preamble(); - writeRF_PDU(m_retryPDUData, m_retryPDUBitLength, false, true); - m_retryCount++; - } - else { - delete m_retryPDUData; - m_retryPDUData = nullptr; - - m_retryPDUBitLength = 0U; - m_retryCount = 0U; - - LogMessage(LOG_RF, P25_PDU_STR ", ISP, response, OSP ACK RETRY, llId = %u, exceeded retries, undeliverable", - m_rfDataHeader.getLLId()); - - writeRF_PDU_Ack_Response(PDUAckClass::NACK, PDUAckType::NACK_UNDELIVERABLE, m_rfDataHeader.getNs(), m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); - } - } + // handle standard P25 service access points + switch (sap) { + case PDUSAP::ARP: + { + /* bryanb: quick and dirty ARP logging */ + uint8_t arpPacket[P25_PDU_ARP_PCKT_LENGTH]; + ::memset(arpPacket, 0x00U, P25_PDU_ARP_PCKT_LENGTH); + ::memcpy(arpPacket, m_rfPduUserData, P25_PDU_ARP_PCKT_LENGTH); + + uint16_t opcode = GET_UINT16(arpPacket, 6U); + uint32_t srcHWAddr = GET_UINT24(arpPacket, 8U); + uint32_t srcProtoAddr = GET_UINT32(arpPacket, 11U); + //uint32_t tgtHWAddr = GET_UINT24(arpPacket, 15U); + uint32_t tgtProtoAddr = GET_UINT32(arpPacket, 18U); + + if (m_verbose) { + if (opcode == P25_PDU_ARP_REQUEST) { + LogInfoEx(LOG_RF, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); + } else if (opcode == P25_PDU_ARP_REPLY) { + LogInfoEx(LOG_RF, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); } } - // rewrite the response to the network - writeNetwork(0U, buffer, P25_PDU_FEC_LENGTH_BYTES, true); - - // only repeat the PDU locally if the packet isn't for the FNE - if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { - writeRF_PDU_Ack_Response(m_rfDataHeader.getResponseClass(), m_rfDataHeader.getResponseType(), m_rfDataHeader.getResponseStatus(), - m_rfDataHeader.getLLId(), m_rfDataHeader.getSrcLLId()); - } + writeNet_PDU_User(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), + m_rfPduUserData); + writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on } - else { - uint8_t sap = (m_rfExtendedAddress) ? m_rfDataHeader.getEXSAP() : m_rfDataHeader.getSAP(); - - // handle standard P25 service access points - switch (sap) { - case PDUSAP::ARP: - { - /* bryanb: quick and dirty ARP logging */ - uint8_t arpPacket[P25_PDU_ARP_PCKT_LENGTH]; - ::memset(arpPacket, 0x00U, P25_PDU_ARP_PCKT_LENGTH); - ::memcpy(arpPacket, m_rfPduUserData + P25_PDU_HEADER_LENGTH_BYTES, P25_PDU_ARP_PCKT_LENGTH); - - uint16_t opcode = GET_UINT16(arpPacket, 6U); - uint32_t srcHWAddr = GET_UINT24(arpPacket, 8U); - uint32_t srcProtoAddr = GET_UINT32(arpPacket, 11U); - //uint32_t tgtHWAddr = GET_UINT24(arpPacket, 15U); - uint32_t tgtProtoAddr = GET_UINT32(arpPacket, 18U); - - if (m_verbose) { - if (opcode == P25_PDU_ARP_REQUEST) { - LogMessage(LOG_RF, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); - } else if (opcode == P25_PDU_ARP_REPLY) { - LogMessage(LOG_RF, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); - } - } + break; + case PDUSAP::SNDCP_CTRL_DATA: + { + if (m_rfAssembler->getUndecodableBlockCount() > 0U) + break; - writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); - writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on + if (m_verbose) { + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), blocksToFollow = %u", + m_rfAssembler->dataHeader.getBlocksToFollow()); } - break; - case PDUSAP::SNDCP_CTRL_DATA: - { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP_CTRL_DATA (SNDCP Control Data), blocksToFollow = %u", - m_rfDataHeader.getBlocksToFollow()); - } - processSNDCPControl(m_rfPduUserData); - writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); + processSNDCPControl(m_rfPduUserData); + writeNet_PDU_User(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), + m_rfPduUserData); + } + break; + case PDUSAP::CONV_DATA_REG: + { + if (m_rfAssembler->getUndecodableBlockCount() > 0U) { + break; } - break; - case PDUSAP::CONV_DATA_REG: - { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), blocksToFollow = %u", - m_rfDataHeader.getBlocksToFollow()); - } - processConvDataReg(m_rfPduUserData); - writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); + if (m_verbose) { + LogInfoEx(LOG_RF, P25_PDU_STR ", CONV_DATA_REG (Conventional Data Registration), blocksToFollow = %u", + m_rfAssembler->dataHeader.getBlocksToFollow()); } - break; - case PDUSAP::UNENC_KMM: - case PDUSAP::ENC_KMM: - { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", - m_rfDataHeader.getBlocksToFollow()); - } - writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); + processConvDataReg(m_rfPduUserData); + writeNet_PDU_User(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), + m_rfPduUserData); + } + break; + case PDUSAP::UNENC_KMM: + case PDUSAP::ENC_KMM: + { + if (m_rfAssembler->getUndecodableBlockCount() > 0U) + break; + + if (m_verbose) { + LogInfoEx(LOG_RF, P25_PDU_STR ", KMM (Key Management Message), blocksToFollow = %u", + m_rfAssembler->dataHeader.getBlocksToFollow()); } - break; - case PDUSAP::TRUNK_CTRL: - { - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", TRUNK_CTRL (Alternate MBT Packet), lco = $%02X, blocksToFollow = %u", - m_rfDataHeader.getAMBTOpcode(), m_rfDataHeader.getBlocksToFollow()); - } - m_p25->m_control->processMBT(m_rfDataHeader, m_rfData); + writeNet_PDU_User(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), + m_rfPduUserData); + } + break; + case PDUSAP::TRUNK_CTRL: + { + if (m_rfAssembler->getUndecodableBlockCount() > 0U) + break; + + if (m_verbose) { + LogInfoEx(LOG_RF, P25_PDU_STR ", TRUNK_CTRL (Alternate MBT Packet), lco = $%02X, blocksToFollow = %u", + m_rfAssembler->dataHeader.getAMBTOpcode(), m_rfAssembler->dataHeader.getBlocksToFollow()); } - break; - default: - writeNet_PDU_User(m_rfDataHeader, m_rfExtendedAddress, m_rfPduUserData); - // only repeat the PDU locally if the packet isn't for the FNE - if (m_repeatPDU && m_rfDataHeader.getLLId() != WUID_FNE) { - ::ActivityLog("P25", true, "RF data transmission from %u to %u, %u blocks", srcId, dstId, m_rfDataHeader.getBlocksToFollow()); + m_p25->m_control->processMBT(m_rfAssembler->dataHeader, m_rfAssembler->dataBlocks); + } + break; + default: + writeNet_PDU_User(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), + m_rfPduUserData); - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", repeating PDU, llId = %u", (m_rfExtendedAddress) ? m_rfDataHeader.getSrcLLId() : m_rfDataHeader.getLLId()); - } + // only repeat the PDU locally if the packet isn't for the FNE + if (m_repeatPDU && m_rfAssembler->dataHeader.getLLId() != WUID_FNE) { + ::ActivityLog("P25", true, "RF data transmission from %u to %u, %u blocks", srcId, dstId, m_rfAssembler->dataHeader.getBlocksToFollow()); + LogInfoEx(LOG_RF, "P25 Data Call (Local Repeat), srcId = %u, dstId = %u", srcId, dstId); - writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on - ::ActivityLog("P25", true, "end of RF data transmission"); + if (m_verbose) { + LogInfoEx(LOG_RF, P25_PDU_STR ", repeating PDU, llId = %u", (m_rfAssembler->getExtendedAddress()) ? m_rfAssembler->dataHeader.getSrcLLId() : m_rfAssembler->dataHeader.getLLId()); } - break; + + writeRF_PDU_Buffered(); // re-generate buffered PDU and send it on + ::ActivityLog("P25", true, "end of RF data transmission"); } + break; } - m_rfDataHeader.reset(); - m_rfExtendedAddress = false; - m_rfDataBlockCnt = 0U; m_rfPDUCount = 0U; m_rfPDUBits = 0U; m_rfPduUserDataLength = 0U; + ::memset(m_rfPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - m_p25->m_rfState = m_prevRfState; - } // switch (m_rfDataHeader.getSAP()) + m_p25->m_rfState = RS_RF_LISTENING; + } } m_inbound = false; @@ -498,101 +390,59 @@ bool Data::process(uint8_t* data, uint32_t len) /* Process a data frame from the network. */ -bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) +bool Data::processNetwork(uint8_t* data, uint32_t len, uint8_t currentBlock, uint32_t blockLength) { - if (m_p25->m_netState != RS_NET_DATA) { - m_netDataHeader.reset(); - m_netDataOffset = 0U; - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; - - ::memset(m_netPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - + if ((m_p25->m_netState != RS_NET_DATA) || (currentBlock == 0U)) { m_p25->m_netState = RS_NET_DATA; m_inbound = false; - uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - ::memcpy(buffer, data + 24U, P25_PDU_FEC_LENGTH_BYTES); - - bool ret = m_netDataHeader.decode(buffer); + bool ret = m_netAssembler->disassemble(data + 24U, blockLength, true); if (!ret) { - LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - - m_netDataHeader.reset(); - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; - m_p25->m_netState = RS_NET_IDLE; - return false; - } - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, hdrOffset = %u, llId = %u", - m_netDataHeader.getAckNeeded(), m_netDataHeader.getOutbound(), m_netDataHeader.getFormat(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMessage(), - m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getPacketLength(), m_netDataHeader.getSynchronize(), m_netDataHeader.getNs(), m_netDataHeader.getFSN(), - m_netDataHeader.getHeaderOffset(), m_netDataHeader.getLLId()); - } - - // make sure we don't get a PDU with more blocks then we support - if (m_netDataHeader.getBlocksToFollow() >= P25_MAX_PDU_BLOCKS) { - LogError(LOG_NET, P25_PDU_STR ", too many PDU blocks to process, %u > %u", m_netDataHeader.getBlocksToFollow(), P25_MAX_PDU_BLOCKS); - - m_netDataHeader.reset(); - m_netDataOffset = 0U; - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; m_p25->m_netState = RS_NET_IDLE; return false; } // if we're a dedicated CC or in control only mode, we only want to handle AMBTs. Otherwise return - if ((m_p25->m_dedicatedControl || m_p25->m_controlOnly) && m_netDataHeader.getFormat() != PDUFormatType::AMBT) { + if ((m_p25->m_dedicatedControl || m_p25->m_controlOnly) && m_netAssembler->dataHeader.getFormat() != PDUFormatType::AMBT) { if (m_debug) { LogDebug(LOG_NET, "CC only mode, ignoring non-AMBT PDU from network"); } - m_netDataHeader.reset(); - m_netDataOffset = 0U; - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; m_p25->m_netState = RS_NET_IDLE; return false; } - m_netPDUCount++; - // did we receive a response header? - if (m_netDataHeader.getFormat() == PDUFormatType::RSP) { + if (m_netAssembler->dataHeader.getFormat() == PDUFormatType::RSP) { m_p25->m_netState = RS_NET_IDLE; if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", - m_netDataHeader.getFormat(), m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(), - m_netDataHeader.getLLId(), m_netDataHeader.getSrcLLId()); + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, fmt = $%02X, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLlId = %u", + m_netAssembler->dataHeader.getFormat(), m_netAssembler->dataHeader.getResponseClass(), m_netAssembler->dataHeader.getResponseType(), m_netAssembler->dataHeader.getResponseStatus(), + m_netAssembler->dataHeader.getLLId(), m_netAssembler->dataHeader.getSrcLLId()); - if (m_netDataHeader.getResponseClass() == PDUAckClass::ACK && m_netDataHeader.getResponseType() == PDUAckType::ACK) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", - m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); + if (m_netAssembler->dataHeader.getResponseClass() == PDUAckClass::ACK && m_netAssembler->dataHeader.getResponseType() == PDUAckType::ACK) { + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, OSP ACK, llId = %u, all blocks received OK, n = %u", + m_netAssembler->dataHeader.getLLId(), m_netAssembler->dataHeader.getResponseStatus()); } else { - if (m_netDataHeader.getResponseClass() == PDUAckClass::NACK) { - switch (m_netDataHeader.getResponseType()) { + if (m_netAssembler->dataHeader.getResponseClass() == PDUAckClass::NACK) { + switch (m_netAssembler->dataHeader.getResponseType()) { case PDUAckType::NACK_ILLEGAL: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, illegal format, llId = %u", - m_netDataHeader.getLLId()); + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, OSP NACK, illegal format, llId = %u", + m_netAssembler->dataHeader.getLLId()); break; case PDUAckType::NACK_PACKET_CRC: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", - m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, OSP NACK, packet CRC error, llId = %u, n = %u", + m_netAssembler->dataHeader.getLLId(), m_netAssembler->dataHeader.getResponseStatus()); break; case PDUAckType::NACK_SEQ: case PDUAckType::NACK_OUT_OF_SEQ: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", - m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, OSP NACK, packet out of sequence, llId = %u, seqNo = %u", + m_netAssembler->dataHeader.getLLId(), m_netAssembler->dataHeader.getResponseStatus()); break; case PDUAckType::NACK_UNDELIVERABLE: - LogMessage(LOG_NET, P25_PDU_STR ", ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", - m_netDataHeader.getLLId(), m_netDataHeader.getResponseStatus()); + LogInfoEx(LOG_NET, P25_PDU_STR ", FNE ISP, response, OSP NACK, packet undeliverable, llId = %u, n = %u", + m_netAssembler->dataHeader.getLLId(), m_netAssembler->dataHeader.getResponseStatus()); break; default: @@ -602,193 +452,80 @@ bool Data::processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength) } } - writeRF_PDU_Ack_Response(m_netDataHeader.getResponseClass(), m_netDataHeader.getResponseType(), m_netDataHeader.getResponseStatus(), - m_netDataHeader.getLLId(), (m_netDataHeader.getSrcLLId() > 0U), m_netDataHeader.getSrcLLId()); - - m_netDataHeader.reset(); - m_netExtendedAddress = false; - m_netDataOffset = 0U; - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; - m_netPduUserDataLength = 0U; + writeRF_PDU_Ack_Response(m_netAssembler->dataHeader.getResponseClass(), m_netAssembler->dataHeader.getResponseType(), m_netAssembler->dataHeader.getResponseStatus(), + m_netAssembler->dataHeader.getLLId(), (m_netAssembler->dataHeader.getSrcLLId() > 0U), m_netAssembler->dataHeader.getSrcLLId()); } return true; } if (m_p25->m_netState == RS_NET_DATA) { - m_inbound = false; // forcibly set inbound to false - ::memcpy(m_netPDU + m_netDataOffset, data + 24U, blockLength); - m_netDataOffset += blockLength; - m_netPDUCount++; - m_netDataBlockCnt++; - - if (m_netDataBlockCnt >= m_netDataHeader.getBlocksToFollow()) { - uint32_t blocksToFollow = m_netDataHeader.getBlocksToFollow(); - uint32_t offset = 0U; - uint32_t dataOffset = 0U; - - uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; - - // process second header if we're using enhanced addressing - if (m_netDataHeader.getSAP() == PDUSAP::EXT_ADDR && - m_netDataHeader.getFormat() == PDUFormatType::UNCONFIRMED) { - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - ::memcpy(buffer, m_netPDU, P25_PDU_FEC_LENGTH_BYTES); - - bool ret = m_netDataHeader.decodeExtAddr(buffer); - if (!ret) { - LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); - - m_netDataHeader.reset(); - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; - m_p25->m_netState = RS_NET_IDLE; - return false; - } - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, extended address, sap = $%02X, srcLlId = %u", - m_netDataHeader.getEXSAP(), m_netDataHeader.getSrcLLId()); - } - - m_netExtendedAddress = true; - - offset += P25_PDU_FEC_LENGTH_BYTES; - blocksToFollow--; - - // if we are using a secondary header place it in the PDU user data buffer - m_netDataHeader.getExtAddrData(m_netPduUserData + dataOffset); - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - m_netPduUserDataLength += P25_PDU_HEADER_LENGTH_BYTES; - } + // block 0 is always the PDU header block -- if we got here with that bail bail bail + if (currentBlock == 0U) { + return false; // bail + } - m_netDataBlockCnt = 0U; + bool ret = m_netAssembler->disassemble(data + 24U, blockLength); + if (!ret) { + m_p25->m_netState = RS_NET_IDLE; + return false; + } + else { + if (m_netAssembler->getComplete()) { + m_netPduUserDataLength = m_netAssembler->getUserDataLength(); + m_netAssembler->getUserData(m_netPduUserData); - // decode data blocks - for (uint32_t i = 0U; i < blocksToFollow; i++) { - ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - ::memcpy(buffer, m_netPDU + offset, P25_PDU_FEC_LENGTH_BYTES); - - bool ret = m_netData[i].decode(buffer, m_netDataHeader); - if (ret) { - // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks - // set this block as the last block for full packet CRC - if ((m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) || (m_netDataHeader.getFormat() == PDUFormatType::UNCONFIRMED)) { - if ((m_netDataBlockCnt + 1U) == blocksToFollow) { - m_netData[i].setLastBlock(true); - } - } + uint32_t srcId = (m_netAssembler->getExtendedAddress()) ? m_netAssembler->dataHeader.getSrcLLId() : m_netAssembler->dataHeader.getLLId(); + uint32_t dstId = m_netAssembler->dataHeader.getLLId(); - // are we processing extended address data from the first block? - if (m_netDataHeader.getSAP() == PDUSAP::EXT_ADDR && m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED && - m_netData[i].getSerialNo() == 0U) { - uint8_t secondHeader[P25_PDU_HEADER_LENGTH_BYTES]; - ::memset(secondHeader, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); - m_netData[i].getData(secondHeader); + uint8_t sap = (m_netAssembler->getExtendedAddress()) ? m_netAssembler->dataHeader.getEXSAP() : m_netAssembler->dataHeader.getSAP(); + if (m_netAssembler->getAuxiliaryES()) + sap = m_netAssembler->dataHeader.getEXSAP(); - m_netDataHeader.decodeExtAddr(secondHeader); - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u, sap = $%02X, srcLlId = %u", - m_netData[i].getSerialNo(), m_netData[i].getFormat(), m_netData[i].getLastBlock(), m_netDataHeader.getEXSAP(), m_netDataHeader.getSrcLLId()); + // handle standard P25 service access points + switch (sap) { + case PDUSAP::ARP: + { + /* bryanb: quick and dirty ARP logging */ + uint8_t arpPacket[P25_PDU_ARP_PCKT_LENGTH]; + ::memset(arpPacket, 0x00U, P25_PDU_ARP_PCKT_LENGTH); + ::memcpy(arpPacket, m_netPduUserData, P25_PDU_ARP_PCKT_LENGTH); + + uint16_t opcode = GET_UINT16(arpPacket, 6U); + uint32_t srcHWAddr = GET_UINT24(arpPacket, 8U); + uint32_t srcProtoAddr = GET_UINT32(arpPacket, 11U); + //uint32_t tgtHWAddr = GET_UINT24(arpPacket, 15U); + uint32_t tgtProtoAddr = GET_UINT32(arpPacket, 18U); + + if (m_verbose) { + if (opcode == P25_PDU_ARP_REQUEST) { + LogInfoEx(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); + } else if (opcode == P25_PDU_ARP_REPLY) { + LogInfoEx(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); } - - m_netExtendedAddress = true; - } - else { - LogMessage(LOG_NET, P25_PDU_STR ", ISP, block %u, fmt = $%02X, lastBlock = %u", - (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_netData[i].getSerialNo() : m_netDataBlockCnt, m_netData[i].getFormat(), - m_netData[i].getLastBlock()); } - m_netData[i].getData(m_netPduUserData + dataOffset); - dataOffset += (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - m_netPduUserDataLength = dataOffset; - - m_netDataBlockCnt++; - } - else { - if (m_netData[i].getFormat() == PDUFormatType::CONFIRMED) - LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC), block %u", i); - else - LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC), block %u", i); - - if (m_dumpPDUData) { - Utils::dump(1U, "P25, Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); - } + writeNet_PDU_Buffered(); // re-generate buffered PDU and send it on } + break; + default: + ::ActivityLog("P25", false, "Net data transmission from %u to %u, %u blocks", srcId, dstId, m_netAssembler->dataHeader.getBlocksToFollow()); + LogInfoEx(LOG_NET, "P25 Data Call, srcId = %u, dstId = %u", srcId, dstId); - offset += P25_PDU_FEC_LENGTH_BYTES; - } - - if (m_netDataHeader.getBlocksToFollow() > 0U) { - bool crcRet = edac::CRC::checkCRC32(m_netPduUserData, m_netPduUserDataLength); - if (!crcRet) { - LogWarning(LOG_NET, P25_PDU_STR ", failed CRC-32 check, blocks %u, len %u", m_netDataHeader.getBlocksToFollow(), m_netPduUserDataLength); - } - } - - if (m_dumpPDUData && m_netDataBlockCnt > 0U) { - Utils::dump(1U, "P25, PDU Packet", m_netPduUserData, m_netPduUserDataLength); - } - - if (m_netDataBlockCnt < blocksToFollow) { - LogWarning(LOG_NET, P25_PDU_STR ", incomplete PDU (%d / %d blocks)", m_netDataBlockCnt, blocksToFollow); - } - - uint32_t srcId = (m_netExtendedAddress) ? m_netDataHeader.getSrcLLId() : m_netDataHeader.getLLId(); - uint32_t dstId = m_netDataHeader.getLLId(); - - uint8_t sap = (m_netExtendedAddress) ? m_netDataHeader.getEXSAP() : m_netDataHeader.getSAP(); - - // handle standard P25 service access points - switch (sap) { - case PDUSAP::ARP: - { - /* bryanb: quick and dirty ARP logging */ - uint8_t arpPacket[P25_PDU_ARP_PCKT_LENGTH]; - ::memset(arpPacket, 0x00U, P25_PDU_ARP_PCKT_LENGTH); - ::memcpy(arpPacket, m_netPduUserData + P25_PDU_HEADER_LENGTH_BYTES, P25_PDU_ARP_PCKT_LENGTH); - - uint16_t opcode = GET_UINT16(arpPacket, 6U); - uint32_t srcHWAddr = GET_UINT24(arpPacket, 8U); - uint32_t srcProtoAddr = GET_UINT32(arpPacket, 11U); - //uint32_t tgtHWAddr = GET_UINT24(arpPacket, 15U); - uint32_t tgtProtoAddr = GET_UINT32(arpPacket, 18U); - - if (m_verbose) { - if (opcode == P25_PDU_ARP_REQUEST) { - LogMessage(LOG_NET, P25_PDU_STR ", ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); - } else if (opcode == P25_PDU_ARP_REPLY) { - LogMessage(LOG_NET, P25_PDU_STR ", ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); + if (m_verbose) { + LogInfoEx(LOG_NET, P25_PDU_STR ", transmitting network PDU, llId = %u", (m_netAssembler->getExtendedAddress()) ? m_netAssembler->dataHeader.getSrcLLId() : m_netAssembler->dataHeader.getLLId()); } - } - writeNet_PDU_Buffered(); // re-generate buffered PDU and send it on - } - break; - default: - ::ActivityLog("P25", false, "Net data transmission from %u to %u, %u blocks", srcId, dstId, m_netDataHeader.getBlocksToFollow()); + writeNet_PDU_Buffered(); // re-generate buffered PDU and send it on - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", transmitting network PDU, llId = %u", (m_netExtendedAddress) ? m_netDataHeader.getSrcLLId() : m_netDataHeader.getLLId()); + ::ActivityLog("P25", false, "end of Net data transmission"); + break; } - writeNet_PDU_Buffered(); // re-generate buffered PDU and send it on - - ::ActivityLog("P25", false, "end of Net data transmission"); - break; + m_netPduUserDataLength = 0U; + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_network->resetP25(); } - - m_netDataHeader.reset(); - m_netExtendedAddress = false; - m_netDataOffset = 0U; - m_netDataBlockCnt = 0U; - m_netPDUCount = 0U; - m_netPduUserDataLength = 0U; - - m_p25->m_netState = RS_NET_IDLE; } } @@ -815,190 +552,26 @@ bool Data::hasLLIdFNEReg(uint32_t llId) const /* Helper to write user data as a P25 PDU packet. */ -void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool imm) +void Data::writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData, bool imm) { assert(pduUserData != nullptr); m_p25->writeRF_TDU(true, imm); - uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - if (dataHeader.getPadLength() > 0U) - bitLength += (dataHeader.getPadLength() * 8U); + uint32_t bitLength = 0U; + UInt8Array data = m_rfAssembler->assemble(dataHeader, extendedAddress, auxiliaryES, pduUserData, &bitLength); - uint32_t offset = P25_PREAMBLE_LENGTH_BITS; - - DECLARE_UINT8_ARRAY(data, (bitLength / 8U) + 1U); - - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", - dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), - dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), - dataHeader.getHeaderOffset(), bitLength, dataHeader.getLLId()); - } - - // generate the PDU header and 1/2 rate Trellis - dataHeader.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - offset += P25_PDU_FEC_LENGTH_BITS; - - if (blocksToFollow > 0U) { - uint32_t dataOffset = 0U; - uint32_t packetLength = dataHeader.getPDULength(); - - // generate the second PDU header - if ((dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData, true); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataHeader.encodeExtAddr(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - bitLength += P25_PDU_FEC_LENGTH_BITS; - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - - blocksToFollow--; - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - } - - // are we processing extended address data from the first block? - if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - } - - if (dataHeader.getFormat() != PDUFormatType::AMBT) { - edac::CRC::addCRC32(pduUserData, packetLength); - } - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - DataBlock dataBlock = DataBlock(); - dataBlock.setFormat(dataHeader); - dataBlock.setSerialNo(i); - dataBlock.setData(pduUserData + dataOffset); - dataBlock.setLastBlock((i + 1U) == blocksToFollow); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", - (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), - dataBlock.getLastBlock()); - } - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataBlock.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - } - } - - writeRF_PDU(data, bitLength, imm); + writeRF_PDU(data.get(), bitLength, imm); } /* Helper to write user data as a P25 PDU packet. */ -void Data::writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData) +void Data::writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData) { assert(pduUserData != nullptr); - uint32_t bitLength = ((dataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - if (dataHeader.getPadLength() > 0U) - bitLength += (dataHeader.getPadLength() * 8U); - - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = dataHeader.getBlocksToFollow(); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, packetLength = %u, S = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", - dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), - dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getPacketLength(), dataHeader.getSynchronize(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), - dataHeader.getHeaderOffset(), bitLength, dataHeader.getLLId()); - } - - // generate the PDU header and 1/2 rate Trellis - dataHeader.encode(block); - writeNetwork(0U, block, P25_PDU_FEC_LENGTH_BYTES, false); - - if (blocksToFollow > 0U) { - uint32_t dataOffset = 0U; - uint32_t packetLength = dataHeader.getPDULength(); - uint32_t netDataBlockCnt = 1U; - - // generate the second PDU header - if ((dataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData, true); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataHeader.encodeExtAddr(block); - writeNetwork(1U, block, P25_PDU_FEC_LENGTH_BYTES, false); - netDataBlockCnt++; - - bitLength += P25_PDU_FEC_LENGTH_BITS; - - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - - blocksToFollow--; - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - } - - // are we processing extended address data from the first block? - if ((dataHeader.getFormat() == PDUFormatType::CONFIRMED) && (dataHeader.getSAP() == PDUSAP::EXT_ADDR) && extendedAddress) { - dataHeader.encodeExtAddr(pduUserData); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, sap = $%02X, srcLlId = %u", - dataHeader.getEXSAP(), dataHeader.getSrcLLId()); - } - } - - if (dataHeader.getFormat() != PDUFormatType::AMBT) { - edac::CRC::addCRC32(pduUserData, packetLength); - } - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - DataBlock dataBlock = DataBlock(); - dataBlock.setFormat(dataHeader); - dataBlock.setSerialNo(i); - dataBlock.setData(pduUserData + dataOffset); - dataBlock.setLastBlock((i + 1U) == blocksToFollow); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", - (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? dataBlock.getSerialNo() : i, dataBlock.getFormat(), - dataBlock.getLastBlock()); - } - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - dataBlock.encode(block); - writeNetwork(netDataBlockCnt, block, P25_PDU_FEC_LENGTH_BYTES, dataBlock.getLastBlock()); - netDataBlockCnt++; - - dataOffset += (dataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - } - } + uint32_t bitLength = 0U; + m_netAssembler->assemble(dataHeader, extendedAddress, auxiliaryES, pduUserData, &bitLength); } /* Updates the processor by the passed number of milliseconds. */ @@ -1042,7 +615,7 @@ void Data::clock(uint32_t ms) m_sndcpReadyTimers[llId].start(); m_sndcpStateTable[llId] = SNDCPState::READY_S; if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, llId = %u, state = %u", llId, (uint8_t)state); + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP, llId = %u, state = %u", llId, (uint8_t)state); } } } @@ -1054,7 +627,7 @@ void Data::clock(uint32_t ms) m_sndcpStateTable[llId] = SNDCPState::IDLE; if (m_verbose) { - LogMessage(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId); + LogInfoEx(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId); } std::unique_ptr lc = std::make_unique(); @@ -1097,7 +670,7 @@ void Data::sndcpInitialize(uint32_t llId) m_sndcpStandbyTimers[llId] = Timer(1000U, SNDCP_STANDBY_TIMEOUT); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, first initialize, llId = %u, state = %u", llId, (uint8_t)SNDCPState::IDLE); + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP, first initialize, llId = %u, state = %u", llId, (uint8_t)SNDCPState::IDLE); } } } @@ -1120,7 +693,7 @@ void Data::sndcpReset(uint32_t llId, bool callTerm) { if (isSNDCPInitialized(llId)) { if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP, reset, llId = %u, state = %u", llId, (uint8_t)m_sndcpStateTable[llId]); + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP, reset, llId = %u, state = %u", llId, (uint8_t)m_sndcpStateTable[llId]); } m_sndcpStateTable[llId] = SNDCPState::CLOSED; @@ -1129,7 +702,7 @@ void Data::sndcpReset(uint32_t llId, bool callTerm) if (callTerm) { if (m_verbose) { - LogMessage(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId); + LogInfoEx(LOG_RF, P25_TDULC_STR ", CALL_TERM (Call Termination), llId = %u", llId); } std::unique_ptr lc = std::make_unique(); @@ -1153,20 +726,11 @@ void Data::sndcpReset(uint32_t llId, bool callTerm) Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verbose) : m_p25(p25), m_prevRfState(RS_RF_LISTENING), - m_rfData(nullptr), - m_rfDataHeader(), - m_rfExtendedAddress(false), - m_rfDataBlockCnt(0U), + m_rfAssembler(nullptr), m_rfPDU(nullptr), m_rfPDUCount(0U), m_rfPDUBits(0U), - m_netData(nullptr), - m_netDataHeader(), - m_netExtendedAddress(false), - m_netDataOffset(0U), - m_netDataBlockCnt(0U), - m_netPDU(nullptr), - m_netPDUCount(0U), + m_netAssembler(nullptr), m_retryPDUData(nullptr), m_retryPDUBitLength(0U), m_retryCount(0U), @@ -1184,15 +748,18 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb m_verbose(verbose), m_debug(debug) { - m_rfData = new data::DataBlock[P25_MAX_PDU_BLOCKS]; + data::Assembler::setVerbose(verbose); + data::Assembler::setDumpPDUData(dumpPDUData); + + m_rfAssembler = new Assembler(); + m_netAssembler = new Assembler(); m_rfPDU = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES + 2U]; ::memset(m_rfPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); - m_netData = new data::DataBlock[P25_MAX_PDU_BLOCKS]; - - m_netPDU = new uint8_t[P25_PDU_FRAME_LENGTH_BYTES + 2U]; - ::memset(m_netPDU, 0x00U, P25_PDU_FRAME_LENGTH_BYTES + 2U); + m_netAssembler->setBlockWriter([=](const void* userContext, const uint8_t currentBlock, const uint8_t *data, uint32_t len, bool lastBlock) { + writeNetwork(currentBlock, data, len, lastBlock); + }); m_rfPduUserData = new uint8_t[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; ::memset(m_rfPduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); @@ -1210,10 +777,10 @@ Data::Data(Control* p25, bool dumpPDUData, bool repeatPDU, bool debug, bool verb Data::~Data() { - delete[] m_rfData; - delete[] m_netData; + delete m_rfAssembler; + delete m_netAssembler; + delete[] m_rfPDU; - delete[] m_netPDU; if (m_retryPDUData != nullptr) delete m_retryPDUData; @@ -1234,7 +801,7 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) uint32_t ipAddr = (pduUserData[8U] << 24) + (pduUserData[9U] << 16) + (pduUserData[10U] << 8) + pduUserData[11U]; if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + LogInfoEx(LOG_RF, P25_PDU_STR ", CONNECT (Registration Request Connect), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); } if (!acl::AccessControl::validateSrcId(llId)) { @@ -1248,7 +815,7 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) } if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); + LogInfoEx(LOG_RF, P25_PDU_STR ", ACCEPT (Registration Response Accept), llId = %u, ipAddr = %s", llId, __IP_FROM_UINT(ipAddr).c_str()); } writeRF_PDU_Reg_Response(PDURegType::ACCEPT, llId, ipAddr); @@ -1260,11 +827,11 @@ bool Data::processConvDataReg(const uint8_t* pduUserData) uint32_t llId = (pduUserData[1U] << 16) + (pduUserData[2U] << 8) + pduUserData[3U]; if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); + LogInfoEx(LOG_RF, P25_PDU_STR ", DISCONNECT (Registration Request Disconnect), llId = %u", llId); } // acknowledge - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfAssembler->dataHeader.getNs(), llId, false); if (hasLLIdFNEReg(llId)) { // remove dynamic FNE registration table entry @@ -1303,14 +870,14 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) return false; } - uint32_t llId = m_rfDataHeader.getLLId(); + uint32_t llId = m_rfAssembler->dataHeader.getLLId(); switch (packet->getPDUType()) { case SNDCP_PDUType::ACT_TDS_CTX: { SNDCPCtxActRequest* isp = static_cast(packet.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId, + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP context activation request, llId = %u, nsapi = %u, ipAddr = %s, nat = $%02X, dsut = $%02X, mdpco = $%02X", llId, isp->getNSAPI(), __IP_FROM_UINT(isp->getIPAddress()).c_str(), isp->getNAT(), isp->getDSUT(), isp->getMDPCO()); } @@ -1322,7 +889,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) rspHeader.setAckNeeded(true); rspHeader.setOutbound(true); rspHeader.setSAP(PDUSAP::SNDCP_CTRL_DATA); - rspHeader.setNs(m_rfDataHeader.getNs()); + rspHeader.setNs(m_rfAssembler->dataHeader.getNs()); rspHeader.setLLId(llId); rspHeader.setBlocksToFollow(1U); @@ -1334,7 +901,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) osp->encode(txPduUserData); rspHeader.calculateLength(2U); - writeRF_PDU_User(rspHeader, false, txPduUserData); + writeRF_PDU_User(rspHeader, false, false, txPduUserData); return true; } @@ -1349,7 +916,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) osp->encode(txPduUserData); rspHeader.calculateLength(2U); - writeRF_PDU_User(rspHeader, false, txPduUserData); + writeRF_PDU_User(rspHeader, false, false, txPduUserData); sndcpReset(llId, true); } @@ -1364,7 +931,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) osp->encode(txPduUserData); rspHeader.calculateLength(2U); - writeRF_PDU_User(rspHeader, false, txPduUserData); + writeRF_PDU_User(rspHeader, false, false, txPduUserData); sndcpReset(llId, true); @@ -1400,7 +967,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) osp->encode(txPduUserData); rspHeader.calculateLength(2U); - writeRF_PDU_User(rspHeader, false, txPduUserData); + writeRF_PDU_User(rspHeader, false, false, txPduUserData); sndcpReset(llId, true); } @@ -1413,11 +980,11 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) { SNDCPCtxDeactivation* isp = static_cast(packet.get()); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId, + LogInfoEx(LOG_RF, P25_PDU_STR ", SNDCP context deactivation request, llId = %u, deactType = %02X", llId, isp->getDeactType()); } - writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfDataHeader.getNs(), llId, false); + writeRF_PDU_Ack_Response(PDUAckClass::ACK, PDUAckType::ACK, m_rfAssembler->dataHeader.getNs(), llId, false); sndcpReset(llId, true); } break; @@ -1435,7 +1002,7 @@ bool Data::processSNDCPControl(const uint8_t* pduUserData) /* Write data processed from RF to the network. */ -void Data::writeNetwork(const uint8_t currentBlock, const uint8_t *data, uint32_t len, bool lastBlock) +void Data::writeNetwork(const uint8_t currentBlock, const uint8_t* data, uint32_t len, bool lastBlock) { assert(data != nullptr); @@ -1445,7 +1012,7 @@ void Data::writeNetwork(const uint8_t currentBlock, const uint8_t *data, uint32_ if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) return; - m_p25->m_network->writeP25PDU(m_rfDataHeader, currentBlock, data, len, lastBlock); + m_p25->m_network->writeP25PDU(m_rfAssembler->dataHeader, currentBlock, data, len, lastBlock); } /* Helper to write a P25 PDU packet. */ @@ -1457,6 +1024,9 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac m_p25->writeRF_TDU(true, imm); + for (uint8_t i = 0U; i < 5U; i++) + m_p25->writeRF_Nulls(); + if (!ackRetry) { if (m_retryPDUData != nullptr) delete m_retryPDUData; @@ -1471,7 +1041,7 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac m_retryPDUData = new uint8_t[retryByteLength]; ::memcpy(m_retryPDUData, pdu, retryByteLength); } else { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, ack retry, bitLength = %u", + LogInfoEx(LOG_RF, P25_PDU_STR ", OSP, ack retry, bitLength = %u", m_retryPDUBitLength); } @@ -1510,185 +1080,18 @@ void Data::writeRF_PDU(const uint8_t* pdu, uint32_t bitLength, bool imm, bool ac void Data::writeNet_PDU_Buffered() { - uint32_t bitLength = ((m_netDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - if (m_netDataHeader.getPadLength() > 0U) - bitLength += (m_netDataHeader.getPadLength() * 8U); - - uint32_t offset = P25_PREAMBLE_LENGTH_BITS; - - DECLARE_UINT8_ARRAY(data, (bitLength / 8U) + 1U); - - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = m_netDataHeader.getBlocksToFollow(); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", - m_netDataHeader.getAckNeeded(), m_netDataHeader.getOutbound(), m_netDataHeader.getFormat(), m_netDataHeader.getMFId(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMessage(), - m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadLength(), m_netDataHeader.getNs(), m_netDataHeader.getFSN(), m_netDataHeader.getLastFragment(), - m_netDataHeader.getHeaderOffset(), bitLength, m_netDataHeader.getLLId()); - } - - // generate the PDU header and 1/2 rate Trellis - m_netDataHeader.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - offset += P25_PDU_FEC_LENGTH_BITS; - - if (blocksToFollow > 0U) { - uint32_t dataOffset = 0U; - - // generate the second PDU header - if ((m_netDataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (m_netDataHeader.getSAP() == PDUSAP::EXT_ADDR) && m_netExtendedAddress) { - m_netDataHeader.encodeExtAddr(m_netPduUserData, true); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - m_netDataHeader.encodeExtAddr(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - bitLength += P25_PDU_FEC_LENGTH_BITS; - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - - blocksToFollow--; - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - m_netDataHeader.getEXSAP(), m_netDataHeader.getSrcLLId()); - } - } - - // are we processing extended address data from the first block? - if ((m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) && (m_netDataHeader.getSAP() == PDUSAP::EXT_ADDR) && m_netExtendedAddress) { - m_netDataHeader.encodeExtAddr(m_netPduUserData); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - m_netDataHeader.getEXSAP(), m_netDataHeader.getSrcLLId()); - } - } - - edac::CRC::addCRC32(m_netPduUserData, m_netPduUserDataLength); - - if (m_dumpPDUData) { - Utils::dump("P25, OSP PDU User Data (NET)", m_netPduUserData, m_netPduUserDataLength); - } - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - m_netData[i].setFormat(m_netDataHeader); - m_netData[i].setSerialNo(i); - m_netData[i].setData(m_netPduUserData + dataOffset); - - if (m_verbose) { - LogMessage(LOG_NET, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", - (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_netData[i].getSerialNo() : i, m_netData[i].getFormat(), - m_netData[i].getLastBlock()); - } - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - m_netData[i].encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += (m_netDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - } - } - - writeRF_PDU(data, bitLength); + uint32_t bitLength = 0U; + UInt8Array data = m_rfAssembler->assemble(m_netAssembler->dataHeader, m_netAssembler->getExtendedAddress(), m_netAssembler->getAuxiliaryES(), m_netPduUserData, &bitLength); + writeRF_PDU(data.get(), bitLength); } /* Helper to re-write a received P25 PDU packet. */ void Data::writeRF_PDU_Buffered() { - uint32_t bitLength = ((m_rfDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; - if (m_rfDataHeader.getPadLength() > 0U) - bitLength += (m_rfDataHeader.getPadLength() * 8U); - - uint32_t offset = P25_PREAMBLE_LENGTH_BITS; - - DECLARE_UINT8_ARRAY(data, (bitLength / 8U) + 1U); - - uint8_t block[P25_PDU_FEC_LENGTH_BYTES]; - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - - uint32_t blocksToFollow = m_rfDataHeader.getBlocksToFollow(); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u, bitLength = %u, llId = %u", - m_rfDataHeader.getAckNeeded(), m_rfDataHeader.getOutbound(), m_rfDataHeader.getFormat(), m_rfDataHeader.getMFId(), m_rfDataHeader.getSAP(), m_rfDataHeader.getFullMessage(), - m_rfDataHeader.getBlocksToFollow(), m_rfDataHeader.getPadLength(), m_rfDataHeader.getNs(), m_rfDataHeader.getFSN(), m_rfDataHeader.getLastFragment(), - m_rfDataHeader.getHeaderOffset(), bitLength, m_rfDataHeader.getLLId()); - } - - // generate the PDU header and 1/2 rate Trellis - m_rfDataHeader.encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - offset += P25_PDU_FEC_LENGTH_BITS; - - if (blocksToFollow > 0U) { - uint32_t dataOffset = 0U; - - // generate the second PDU header - if ((m_rfDataHeader.getFormat() == PDUFormatType::UNCONFIRMED) && (m_rfDataHeader.getSAP() == PDUSAP::EXT_ADDR) && m_rfExtendedAddress) { - m_rfDataHeader.encodeExtAddr(m_rfPduUserData, true); - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - m_rfDataHeader.encodeExtAddr(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - bitLength += P25_PDU_FEC_LENGTH_BITS; - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += P25_PDU_HEADER_LENGTH_BYTES; - - blocksToFollow--; - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - m_rfDataHeader.getEXSAP(), m_rfDataHeader.getSrcLLId()); - } - } - - // are we processing extended address data from the first block? - if ((m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) && (m_rfDataHeader.getSAP() == PDUSAP::EXT_ADDR) && m_rfExtendedAddress) { - m_rfDataHeader.encodeExtAddr(m_rfPduUserData); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, extended address, sap = $%02X, srcLlId = %u", - m_rfDataHeader.getEXSAP(), m_rfDataHeader.getSrcLLId()); - } - } - - edac::CRC::addCRC32(m_rfPduUserData, m_rfPduUserDataLength); - - if (m_dumpPDUData) { - Utils::dump("P25, OSP PDU User Data (RF)", m_rfPduUserData, m_rfPduUserDataLength); - } - - // generate the PDU data - for (uint32_t i = 0U; i < blocksToFollow; i++) { - m_rfData[i].setFormat(m_rfDataHeader); - m_rfData[i].setSerialNo(i); - m_rfData[i].setData(m_rfPduUserData + dataOffset); - m_rfData[i].setLastBlock((i + 1U) == blocksToFollow); - - if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, block %u, fmt = $%02X, lastBlock = %u", - (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? m_rfData[i].getSerialNo() : i, m_rfData[i].getFormat(), - m_rfData[i].getLastBlock()); - } - - ::memset(block, 0x00U, P25_PDU_FEC_LENGTH_BYTES); - m_rfData[i].encode(block); - Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); - - offset += P25_PDU_FEC_LENGTH_BITS; - dataOffset += (m_rfDataHeader.getFormat() == PDUFormatType::CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; - } - } - - writeRF_PDU(data, bitLength); + uint32_t bitLength = 0U; + UInt8Array data = m_rfAssembler->assemble(m_rfAssembler->dataHeader, m_rfAssembler->getExtendedAddress(), m_rfAssembler->getAuxiliaryES(), m_rfPduUserData, &bitLength); + writeRF_PDU(data.get(), bitLength); } /* Helper to write a PDU registration response. */ @@ -1703,7 +1106,7 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA DataHeader rspHeader = DataHeader(); rspHeader.setFormat(PDUFormatType::CONFIRMED); - rspHeader.setMFId(m_rfDataHeader.getMFId()); + rspHeader.setMFId(m_rfAssembler->dataHeader.getMFId()); rspHeader.setAckNeeded(true); rspHeader.setOutbound(true); rspHeader.setSAP(PDUSAP::CONV_DATA_REG); @@ -1722,10 +1125,11 @@ void Data::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, uint32_t ipA pduUserData[11U] = (ipAddr >> 0) & 0xFFU; } - Utils::dump(1U, "Data::writeRF_PDU_Reg_Response() pduUserData", pduUserData, 12U); + if (m_dumpPDUData) + Utils::dump(1U, "P25, PDU Registration Response", pduUserData, 12U); rspHeader.calculateLength(12U); - writeRF_PDU_User(rspHeader, false, pduUserData); + writeRF_PDU_User(rspHeader, false, false, pduUserData); } /* Helper to write a PDU acknowledge response. */ @@ -1745,7 +1149,7 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a DataHeader rspHeader = DataHeader(); rspHeader.setFormat(PDUFormatType::RSP); - rspHeader.setMFId(m_rfDataHeader.getMFId()); + rspHeader.setMFId(m_rfAssembler->dataHeader.getMFId()); rspHeader.setOutbound(true); rspHeader.setResponseClass(ackClass); rspHeader.setResponseType(ackType); @@ -1762,12 +1166,12 @@ void Data::writeRF_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, uint8_t a rspHeader.setBlocksToFollow(0U); - // Generate the PDU header and 1/2 rate Trellis + // generate the PDU header and 1/2 rate Trellis rspHeader.encode(block); Utils::setBitRange(block, data, offset, P25_PDU_FEC_LENGTH_BITS); if (m_verbose) { - LogMessage(LOG_RF, P25_PDU_STR ", OSP, response, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLLId = %u", + LogInfoEx(LOG_RF, P25_PDU_STR ", OSP, response, rspClass = $%02X, rspType = $%02X, rspStatus = $%02X, llId = %u, srcLLId = %u", rspHeader.getResponseClass(), rspHeader.getResponseType(), rspHeader.getResponseStatus(), rspHeader.getLLId(), rspHeader.getSrcLLId()); } diff --git a/src/host/p25/packet/Data.h b/src/host/p25/packet/Data.h index 21b94ef8f..9d125dc09 100644 --- a/src/host/p25/packet/Data.h +++ b/src/host/p25/packet/Data.h @@ -21,6 +21,7 @@ #include "common/p25/data/DataBlock.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/LowSpeedData.h" +#include "common/p25/data/Assembler.h" #include "common/p25/lc/LC.h" #include "common/Timer.h" #include "p25/Control.h" @@ -67,10 +68,11 @@ namespace p25 * @brief Process a data frame from the network. * @param data Buffer containing data frame. * @param len Length of data frame. + * @param currentBlock * @param blockLength * @returns bool True, if data frame is processed, otherwise false. */ - bool processNetwork(uint8_t* data, uint32_t len, uint32_t blockLength); + bool processNetwork(uint8_t* data, uint32_t len, uint8_t currentBlock, uint32_t blockLength); /** @} */ /** @@ -83,18 +85,20 @@ namespace p25 /** * @brief Helper to write user data as a P25 PDU packet. * @param dataHeader Instance of a PDU data header. - * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param extendedAddress Flag indicating whether or not extended addressing is in use. + * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. * @param imm Flag indicating the PDU should be written to the immediate queue. */ - void writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData, bool imm = false); + void writeRF_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData, bool imm = false); /** * @brief Helper to write user data as a P25 PDU packet. * @param dataHeader Instance of a PDU data header. * @param extendedAddress Flag indicating whether or not to extended addressing is in use. + * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. */ - void writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, uint8_t* pduUserData); + void writeNet_PDU_User(data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); /** * @brief Updates the processor by the passed number of milliseconds. @@ -127,21 +131,11 @@ namespace p25 RPT_RF_STATE m_prevRfState; - data::DataBlock* m_rfData; - data::DataHeader m_rfDataHeader; - bool m_rfExtendedAddress; - uint8_t m_rfDataBlockCnt; + data::Assembler* m_rfAssembler; uint8_t* m_rfPDU; uint32_t m_rfPDUCount; uint32_t m_rfPDUBits; - - data::DataBlock* m_netData; - data::DataHeader m_netDataHeader; - bool m_netExtendedAddress; - uint32_t m_netDataOffset; - uint8_t m_netDataBlockCnt; - uint8_t* m_netPDU; - uint32_t m_netPDUCount; + data::Assembler* m_netAssembler; uint8_t* m_retryPDUData; uint32_t m_retryPDUBitLength; diff --git a/src/host/p25/packet/Voice.cpp b/src/host/p25/packet/Voice.cpp index ef1e32dbf..ae4e45976 100644 --- a/src/host/p25/packet/Voice.cpp +++ b/src/host/p25/packet/Voice.cpp @@ -129,7 +129,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_HDU_STR ", HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + LogInfoEx(LOG_RF, P25_HDU_STR ", HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", lc.getDstId(), lc.getAlgId(), lc.getKId()); if (lc.getAlgId() != ALGO_UNENCRYPT) { uint8_t mi[MI_LENGTH_BYTES]; @@ -137,7 +137,7 @@ bool Voice::process(uint8_t* data, uint32_t len) lc.getMI(mi); - LogMessage(LOG_RF, P25_HDU_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_RF, P25_HDU_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } } @@ -350,7 +350,7 @@ bool Voice::process(uint8_t* data, uint32_t len) // bryanb: due to moronic reasons -- if this case happens, default the RID to something sane if (srcId == 0U && !lc.isStandardMFId()) { - LogMessage(LOG_RF, P25_HDU_STR " ** source RID was 0 with non-standard MFId defaulting source RID, dstId = %u, mfId = $%02X", dstId, lc.getMFId()); + LogInfoEx(LOG_RF, P25_HDU_STR " ** source RID was 0 with non-standard MFId defaulting source RID, dstId = %u, mfId = $%02X", dstId, lc.getMFId()); srcId = WUID_FNE; } @@ -363,7 +363,7 @@ bool Voice::process(uint8_t* data, uint32_t len) if (!group) controlByte |= network::NET_CTRL_U2U; // Unit-to-unit Flag - LogMessage(LOG_RF, P25_HDU_STR " remote grant demand, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_RF, P25_HDU_STR " remote grant demand, srcId = %u, dstId = %u", srcId, dstId); m_p25->m_network->writeP25TDU(lc, m_rfLSD, controlByte); } } @@ -374,6 +374,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_lastRejectId = 0U; ::ActivityLog("P25", true, "RF %svoice transmission from %u to %s%u", encrypted ? "encrypted ": "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_RF, "P25 Voice Call, srcId = %u, dstId = %u", srcId, dstId); uint8_t serviceOptions = (m_rfLC.getEmergency() ? 0x80U : 0x00U) + // Emergency Flag (m_rfLC.getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag @@ -511,7 +512,7 @@ bool Voice::process(uint8_t* data, uint32_t len) frameType = FrameType::HDU_VALID; if (m_verbose) { - LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + LogInfoEx(LOG_RF, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); } } else { @@ -671,7 +672,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLC.setLCO(LCO::RFSS_STS_BCAST); } else { - std::lock_guard lock(m_p25->m_activeTGLock); + std::lock_guard lock(m_p25->s_activeTGLock); if (m_p25->m_activeTG.size() > 0) { if (m_grpUpdtCount > m_p25->m_activeTG.size()) m_grpUpdtCount = 0U; @@ -763,7 +764,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_LDU1_STR ", audio, mfId = $%02X srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, errs = %u/1233 (%.1f%%)", + LogInfoEx(LOG_RF, P25_LDU1_STR ", audio, mfId = $%02X srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, errs = %u/1233 (%.1f%%)", m_rfLC.getMFId(), m_rfLC.getSrcId(), m_rfLC.getDstId(), m_rfLC.getGroup(), m_rfLC.getEmergency(), m_rfLC.getEncrypted(), m_rfLC.getPriority(), errors, float(errors) / 12.33F); } @@ -880,7 +881,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_LDU2_STR ", audio, algo = $%02X, kid = $%04X, errs = %u/1233 (%.1f%%)", + LogInfoEx(LOG_RF, P25_LDU2_STR ", audio, algo = $%02X, kid = $%04X, errs = %u/1233 (%.1f%%)", m_rfLC.getAlgId(), m_rfLC.getKId(), errors, float(errors) / 12.33F); if (m_rfLC.getAlgId() != ALGO_UNENCRYPT) { @@ -889,7 +890,7 @@ bool Voice::process(uint8_t* data, uint32_t len) m_rfLC.getMI(mi); - LogMessage(LOG_RF, P25_LDU2_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_RF, P25_LDU2_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } } @@ -977,7 +978,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + LogInfoEx(LOG_RF, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); } } else { @@ -1021,7 +1022,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_VSELP1_STR ", audio"); + LogInfoEx(LOG_RF, P25_VSELP1_STR ", audio"); } return true; @@ -1063,7 +1064,7 @@ bool Voice::process(uint8_t* data, uint32_t len) } if (m_verbose) { - LogMessage(LOG_RF, P25_VSELP2_STR ", audio"); + LogInfoEx(LOG_RF, P25_VSELP2_STR ", audio"); } return true; @@ -1106,7 +1107,7 @@ bool Voice::process(uint8_t* data, uint32_t len) float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits)); } - LogMessage(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", + LogInfoEx(LOG_RF, P25_TDU_STR ", total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); if (m_p25->m_dedicatedControl) { @@ -1358,7 +1359,7 @@ bool Voice::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::L m_netLastDUID = duid; - if (!m_p25->m_enableControl) { + if (!m_p25->m_dedicatedControl) { m_p25->m_affiliations->releaseGrant(m_netLC.getDstId(), false); } @@ -1570,16 +1571,19 @@ bool Voice::checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DU } } - // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* - // the RF TG hangtimer is running - if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { - m_p25->m_rfTGHang.stop(); - } + //LogDebugEx(LOG_NET, "Voice::checkNetTrafficCollision()", "rfLastDstId = %u, dstId = %u, defaultNetIdleTalkgroup = %u, rfTGHangRunning = %u", m_p25->m_rfLastDstId, dstId, + // m_p25->m_defaultNetIdleTalkgroup, m_p25->m_rfTGHang.isRunning()); // don't process network frames if the RF TG hang timer isn't running, the default net idle talkgroup is set and // the destination ID doesn't match the default net idle talkgroup if (m_p25->m_defaultNetIdleTalkgroup != 0U && dstId != 0U && !m_p25->m_rfTGHang.isRunning()) { - if (m_p25->m_defaultNetIdleTalkgroup != dstId) { + if (m_p25->m_defaultNetIdleTalkgroup != dstId && !m_p25->m_affiliations->hasGroupAff(dstId)) { + if (!m_p25->m_dedicatedControl) { + if (m_p25->m_affiliations->isGranted(dstId)) { + m_p25->m_affiliations->releaseGrant(dstId, false); + } + } + resetNet(); if (m_p25->m_network != nullptr) m_p25->m_network->resetP25(); @@ -1587,6 +1591,15 @@ bool Voice::checkNetTrafficCollision(uint32_t srcId, uint32_t dstId, defines::DU } } + // bryanb: only perform tail ride fix if default net idle talkgroup is not set + if (m_p25->m_defaultNetIdleTalkgroup == 0U) { + // bryanb: possible fix for a "tail ride" condition where network traffic immediately follows RF traffic *while* + // the RF TG hangtimer is running + if (m_p25->m_rfTGHang.isRunning() && !m_p25->m_rfTGHang.hasExpired()) { + m_p25->m_rfTGHang.stop(); + } + } + // perform authoritative network TG hangtimer and traffic preemption if (m_p25->m_authoritative) { // don't process network frames if the destination ID's don't match and the network TG hang timer is running @@ -1676,7 +1689,7 @@ void Voice::writeNet_TDU() m_p25->addFrame(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U, true); if (m_verbose) { - LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u", m_netLC.getSrcId()); + LogInfoEx(LOG_NET, P25_TDU_STR ", srcId = %u", m_netLC.getSrcId()); } if (m_netFrames > 0) { @@ -1740,7 +1753,7 @@ void Voice::writeNet_LDU1() if (m_netLastLDU1.getDstId() != 0U) { if (dstId != m_netLastLDU1.getDstId() && control.isStandardMFId()) { if (m_verbose) { - LogMessage(LOG_NET, P25_LDU1_STR ", dstId = %u doesn't match last LDU1 dstId = %u, fixing", + LogInfoEx(LOG_NET, P25_LDU1_STR ", dstId = %u doesn't match last LDU1 dstId = %u, fixing", dstId, m_netLastLDU1.getDstId()); } dstId = m_netLastLDU1.getDstId(); @@ -1751,7 +1764,7 @@ void Voice::writeNet_LDU1() if (m_netLastLDU1.getSrcId() != 0U) { if (srcId != m_netLastLDU1.getSrcId() && control.isStandardMFId()) { if (m_verbose) { - LogMessage(LOG_NET, P25_LDU1_STR ", srcId = %u doesn't match last LDU1 srcId = %u, fixing", + LogInfoEx(LOG_NET, P25_LDU1_STR ", srcId = %u doesn't match last LDU1 srcId = %u, fixing", srcId, m_netLastLDU1.getSrcId()); } srcId = m_netLastLDU1.getSrcId(); @@ -1759,7 +1772,7 @@ void Voice::writeNet_LDU1() } if (m_debug) { - LogMessage(LOG_NET, P25_LDU1_STR " service flags, emerg = %u, encrypt = %u, prio = %u, DFSI emerg = %u, DFSI encrypt = %u, DFSI prio = %u", + LogInfoEx(LOG_NET, P25_LDU1_STR " service flags, emerg = %u, encrypt = %u, prio = %u, DFSI emerg = %u, DFSI encrypt = %u, DFSI prio = %u", control.getEmergency(), control.getEncrypted(), control.getPriority(), m_dfsiLC.control()->getEmergency(), m_dfsiLC.control()->getEncrypted(), m_dfsiLC.control()->getPriority()); } @@ -1835,6 +1848,7 @@ void Voice::writeNet_LDU1() m_p25->writeRF_Preamble(); ::ActivityLog("P25", false, "network %svoice transmission from %u to %s%u", m_netLC.getEncrypted() ? "encrypted " : "", srcId, group ? "TG " : "", dstId); + LogInfoEx(LOG_NET, "P25 Voice Call, srcId = %u, dstId = %u", srcId, dstId); // conventional registration or DVRS support? if (((m_p25->m_enableControl && !m_p25->m_dedicatedControl) || m_p25->m_voiceOnControl) && !m_p25->m_disableNetworkGrant) { @@ -1951,23 +1965,23 @@ void Voice::writeNet_LDU1() m_p25->addFrame(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U, true); if (m_verbose) { - LogMessage(LOG_NET, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + LogInfoEx(LOG_NET, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); if (control.getAlgId() != ALGO_UNENCRYPT) { - LogMessage(LOG_NET, P25_HDU_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_NET, P25_HDU_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } } } else { if (m_verbose) { - LogMessage(LOG_NET, P25_HDU_STR ", not transmitted; network HDU late entry, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + LogInfoEx(LOG_NET, P25_HDU_STR ", not transmitted; network HDU late entry, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); } } } else { if (m_verbose) { - LogMessage(LOG_NET, P25_HDU_STR ", not transmitted; network HDU disabled, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + LogInfoEx(LOG_NET, P25_HDU_STR ", not transmitted; network HDU disabled, dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); } } } @@ -2024,7 +2038,7 @@ void Voice::writeNet_LDU1() m_netLC.setLCO(LCO::RFSS_STS_BCAST); } else { - std::lock_guard lock(m_p25->m_activeTGLock); + std::lock_guard lock(m_p25->s_activeTGLock); if (m_p25->m_activeTG.size() > 0) { if (m_grpUpdtCount > m_p25->m_activeTG.size()) m_grpUpdtCount = 0U; @@ -2093,7 +2107,7 @@ void Voice::writeNet_LDU1() m_p25->addFrame(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U, true); if (m_verbose) { - LogMessage(LOG_NET, P25_LDU1_STR " audio, mfId = $%02X, srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, sysId = $%03X, netId = $%05X", + LogInfoEx(LOG_NET, P25_LDU1_STR " audio, mfId = $%02X, srcId = %u, dstId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, sysId = $%03X, netId = $%05X", m_netLC.getMFId(), m_netLC.getSrcId(), m_netLC.getDstId(), m_netLC.getGroup(), m_netLC.getEmergency(), m_netLC.getEncrypted(), m_netLC.getPriority(), sysId, netId); } @@ -2181,10 +2195,10 @@ void Voice::writeNet_LDU2() m_p25->addFrame(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U, true); if (m_verbose) { - LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_netLC.getAlgId(), m_netLC.getKId()); + LogInfoEx(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", m_netLC.getAlgId(), m_netLC.getKId()); if (control.getAlgId() != ALGO_UNENCRYPT) { - LogMessage(LOG_NET, P25_LDU2_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_NET, P25_LDU2_STR ", Enc Sync, MI = %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } } diff --git a/src/host/p25/packet/Voice.h b/src/host/p25/packet/Voice.h index 2f03c8983..a5bb238fc 100644 --- a/src/host/p25/packet/Voice.h +++ b/src/host/p25/packet/Voice.h @@ -4,10 +4,6 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* @package DVM / Modem Host Software -* @derivedfrom MMDVMHost (https://github.com/g4klx/MMDVMHost) -* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) -* * Copyright (C) 2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * diff --git a/src/host/network/RESTAPI.cpp b/src/host/restapi/RESTAPI.cpp similarity index 99% rename from src/host/network/RESTAPI.cpp rename to src/host/restapi/RESTAPI.cpp index f82f2cae7..c652b7448 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/restapi/RESTAPI.cpp @@ -10,20 +10,20 @@ #include "Defines.h" #include "common/edac/SHA256.h" #include "common/lookups/AffiliationLookup.h" -#include "common/network/json/json.h" +#include "common/json/json.h" #include "common/Log.h" #include "common/Utils.h" #include "dmr/Control.h" #include "p25/Control.h" #include "nxdn/Control.h" #include "modem/Modem.h" -#include "network/RESTAPI.h" +#include "restapi/RESTAPI.h" #include "Host.h" #include "HostMain.h" using namespace network; -using namespace network::rest; -using namespace network::rest::http; +using namespace restapi; +using namespace restapi::http; using namespace modem; #include diff --git a/src/host/network/RESTAPI.h b/src/host/restapi/RESTAPI.h similarity index 84% rename from src/host/network/RESTAPI.h rename to src/host/restapi/RESTAPI.h index 2cffa9e76..40a36361b 100644 --- a/src/host/network/RESTAPI.h +++ b/src/host/restapi/RESTAPI.h @@ -17,13 +17,13 @@ #define __REST_API_H__ #include "Defines.h" -#include "common/network/rest/RequestDispatcher.h" -#include "common/network/rest/http/HTTPServer.h" -#include "common/network/rest/http/SecureHTTPServer.h" +#include "common/restapi/RequestDispatcher.h" +#include "common/restapi/http/HTTPServer.h" +#include "common/restapi/http/SecureHTTPServer.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/Thread.h" -#include "network/RESTDefines.h" +#include "restapi/RESTDefines.h" #include #include @@ -92,12 +92,12 @@ class HOST_SW_API RESTAPI : private Thread { void close(); private: - typedef network::rest::RequestDispatcher RESTDispatcherType; - typedef network::rest::http::HTTPPayload HTTPPayload; + typedef restapi::RequestDispatcher RESTDispatcherType; + typedef restapi::http::HTTPPayload HTTPPayload; RESTDispatcherType m_dispatcher; - network::rest::http::HTTPServer m_restServer; + restapi::http::HTTPServer m_restServer; #if defined(ENABLE_SSL) - network::rest::http::SecureHTTPServer m_restSecureServer; + restapi::http::SecureHTTPServer m_restSecureServer; bool m_enableSSL; #endif // ENABLE_SSL @@ -150,7 +150,7 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutAuth(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get version request. @@ -158,21 +158,21 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetVersion(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get status request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetStatus(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get voice channels request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put/set modem mode request. @@ -180,14 +180,14 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutModemMode(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutModemMode(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements put/request modem kill request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutModemKill(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutModemKill(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements set supervisory mode request. @@ -195,35 +195,35 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutSetSupervisor(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutSetSupervisor(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements permit TG request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutPermitTG(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutPermitTG(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements grant TG request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutGrantTG(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutGrantTG(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements release grants request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements release affiliations request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get RID whitelist request. @@ -231,14 +231,14 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get RID blacklist request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetRIDBlacklist(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetRIDBlacklist(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /* ** Digital Mobile Radio @@ -250,49 +250,49 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRBeacon(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRBeacon(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get DMR debug state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRDebug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRDebug(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get DMR dump CSBK state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRDumpCSBK(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRDumpCSBK(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements DMR RID operations request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutDMRRID(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements toggle DMR CC enable request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRCCEnable(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRCCEnable(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements toggle DMR CC broadcast request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get DMR affiliations request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /* ** Project 25 @@ -304,56 +304,56 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25CC(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25CC(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements P25 debug state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25Debug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25Debug(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements P25 dump TSBK state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25DumpTSBK(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25DumpTSBK(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements P25 RID operation request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutP25RID(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements toggle P25 CC request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25CCEnable(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25CCEnable(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements toggle P25 broadcast request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25CCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25CCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements transmitting raw TSBK request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_PutP25RawTSBK(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_PutP25RawTSBK(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get P25 affiliations request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetP25AffList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetP25AffList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /* ** Next Generation Digital Narrowband @@ -365,35 +365,35 @@ class HOST_SW_API RESTAPI : private Thread { * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetNXDNCC(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetNXDNCC(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements NXDN debug state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetNXDNDebug(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetNXDNDebug(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements NXDN dump RCCH state request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetNXDNDumpRCCH(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetNXDNDumpRCCH(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements toggle NXDN CC request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetNXDNCCEnable(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetNXDNCCEnable(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); /** * @brief REST API endpoint; implements get NXDN affiliations request. * @param request HTTP request. * @param reply HTTP reply. * @param match HTTP request matcher. */ - void restAPI_GetNXDNAffList(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); + void restAPI_GetNXDNAffList(const HTTPPayload& request, HTTPPayload& reply, const restapi::RequestMatch& match); }; #endif // __REST_API_H__ diff --git a/src/host/network/RESTDefines.h b/src/host/restapi/RESTDefines.h similarity index 100% rename from src/host/network/RESTDefines.h rename to src/host/restapi/RESTDefines.h diff --git a/src/host/setup/HostSetup.cpp b/src/host/setup/HostSetup.cpp index d9742e49a..fa773ae09 100644 --- a/src/host/setup/HostSetup.cpp +++ b/src/host/setup/HostSetup.cpp @@ -287,7 +287,7 @@ bool HostSetup::portModemOpen(Modem* modem) } } - LogMessage(LOG_MODEM, "Modem Ready [Calibration Mode]"); + LogInfoEx(LOG_MODEM, "Modem Ready [Calibration Mode]"); // handled modem open return true; @@ -363,7 +363,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp short low = buffer[6U] << 8 | buffer[7U]; short diff = high - low; short centre = (high + low) / 2; - LogMessage(LOG_CAL, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d", inverted ? "yes" : "no", high, low, diff, centre); + LogInfoEx(LOG_CAL, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d", inverted ? "yes" : "no", high, low, diff, centre); } break; case CMD_RSSI_DATA: @@ -376,7 +376,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp uint16_t max = buffer[3U] << 8 | buffer[4U]; uint16_t min = buffer[5U] << 8 | buffer[6U]; uint16_t ave = buffer[7U] << 8 | buffer[8U]; - LogMessage(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); + LogInfoEx(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); } break; @@ -401,7 +401,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp break; } - LogMessage(LOG_CAL, "DMR Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "DMR Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_dmrEnabled) { m_berBits = 0U; @@ -429,7 +429,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp break; } - LogMessage(LOG_CAL, "P25 Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "P25 Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_p25Enabled) { m_berBits = 0U; @@ -460,7 +460,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp break; } - LogMessage(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_nxdnEnabled) { m_berBits = 0U; @@ -500,7 +500,7 @@ bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspTyp m_modem->m_nxdnSpace = buffer[11U] * (nxdn::defines::NXDN_FRAME_LENGTH_BYTES); if (m_hasFetchedStatus && m_requestedStatus) { - LogMessage(LOG_CAL, "Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d, HS: %u]", + LogInfoEx(LOG_CAL, "Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d, HS: %u]", modemState, tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_isHotspot); } @@ -593,10 +593,10 @@ void HostSetup::saveConfig() } yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); - LogMessage(LOG_CAL, " - Saved configuration to %s", m_confFile.c_str()); + LogInfoEx(LOG_CAL, " - Saved configuration to %s", m_confFile.c_str()); if (m_isConnected) { if (writeFlash()) { - LogMessage(LOG_CAL, " - Wrote configuration area on modem"); + LogInfoEx(LOG_CAL, " - Wrote configuration area on modem"); } } } @@ -907,9 +907,9 @@ bool HostSetup::setTransmit() if (m_p25Enabled && m_p25TduTest) { if (m_transmit) - LogMessage(LOG_CAL, " - Modem start transmitting"); + LogInfoEx(LOG_CAL, " - Modem start transmitting"); else - LogMessage(LOG_CAL, " - Modem stop transmitting"); + LogInfoEx(LOG_CAL, " - Modem stop transmitting"); m_modem->clock(0U); return true; @@ -929,9 +929,9 @@ bool HostSetup::setTransmit() sleep(25U); if (m_transmit) - LogMessage(LOG_CAL, " - Modem start transmitting"); + LogInfoEx(LOG_CAL, " - Modem start transmitting"); else - LogMessage(LOG_CAL, " - Modem stop transmitting"); + LogInfoEx(LOG_CAL, " - Modem stop transmitting"); m_modem->clock(0U); @@ -971,7 +971,7 @@ void HostSetup::processDMRBER(const uint8_t* buffer, uint8_t seq) if (seq == 65U) { timerStart(); - LogMessage(LOG_CAL, "DMR voice header received"); + LogInfoEx(LOG_CAL, "DMR voice header received"); m_berBits = 0U; m_berErrs = 0U; @@ -982,7 +982,7 @@ void HostSetup::processDMRBER(const uint8_t* buffer, uint8_t seq) } else if (seq == 66U) { if (m_berFrames != 0U) { - LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); @@ -1005,7 +1005,7 @@ void HostSetup::processDMRBER(const uint8_t* buffer, uint8_t seq) float ber = float(errs) / 1.41F; if (ber < 10.0F) - LogMessage(LOG_CAL, "DMR audio seq. %d, FEC BER %% (errs): %.3f%% (%u/141)", seq & 0x0FU, ber, errs); + LogInfoEx(LOG_CAL, "DMR audio seq. %d, FEC BER %% (errs): %.3f%% (%u/141)", seq & 0x0FU, ber, errs); else { LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); m_berUncorrectable++; @@ -1040,7 +1040,7 @@ void HostSetup::processDMR1KBER(const uint8_t* buffer, uint8_t seq) m_berErrs += errs; m_berBits += 264; m_berFrames++; - LogMessage(LOG_CAL, "DMR voice header received, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", float(errs) / 2.64F, errs); + LogInfoEx(LOG_CAL, "DMR voice header received, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", float(errs) / 2.64F, errs); } else if (seq == 66U) { for (uint32_t i = 0U; i < 33U; i++) @@ -1051,7 +1051,7 @@ void HostSetup::processDMR1KBER(const uint8_t* buffer, uint8_t seq) m_berFrames++; if (m_berFrames != 0U) { - LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); @@ -1085,7 +1085,7 @@ void HostSetup::processDMR1KBER(const uint8_t* buffer, uint8_t seq) m_berFrames++; if (ber < 10.0F) - LogMessage(LOG_CAL, "DMR audio seq. %d, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", seq & 0x0FU, ber, errs); + LogInfoEx(LOG_CAL, "DMR audio seq. %d, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", seq & 0x0FU, ber, errs); else { LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); m_berUncorrectable++; @@ -1109,7 +1109,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) for (uint8_t i = 0U; i < P25_SYNC_LENGTH_BYTES; i++) syncErrs += Utils::countBits8(sync[i] ^ P25_SYNC_BYTES[i]); - LogMessage(LOG_CAL, "P25, sync word, errs = %u, sync word = %02X %02X %02X %02X %02X %02X", syncErrs, + LogInfoEx(LOG_CAL, "P25, sync word, errs = %u, sync word = %02X %02X %02X %02X %02X %02X", syncErrs, sync[0U], sync[1U], sync[2U], sync[3U], sync[4U], sync[5U]); uint8_t nid[P25_NID_LENGTH_BYTES]; @@ -1130,14 +1130,14 @@ void HostSetup::processP25BER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + LogInfoEx(LOG_CAL, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); uint8_t mi[MI_LENGTH_BYTES]; ::memset(mi, 0x00U, MI_LENGTH_BYTES); lc.getMI(mi); - LogMessage(LOG_CAL, P25_HDU_STR ", MI %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_CAL, P25_HDU_STR ", MI %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } @@ -1149,7 +1149,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) } else if (duid == DUID::TDU) { if (m_berFrames != 0U) { - LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); @@ -1174,7 +1174,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); } @@ -1207,7 +1207,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) float ber = float(errs) / 12.33F; if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU1_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + LogInfoEx(LOG_CAL, P25_LDU1_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); m_berUncorrectable++; @@ -1229,7 +1229,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", + LogInfoEx(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", lc.getMFId(), lc.getAlgId(), lc.getKId()); uint8_t mi[MI_LENGTH_BYTES]; @@ -1237,7 +1237,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) lc.getMI(mi); - LogMessage(LOG_CAL, P25_LDU2_STR ", MI %02X %02X %02X %02X %02X %02X %02X %02X %02X", + LogInfoEx(LOG_CAL, P25_LDU2_STR ", MI %02X %02X %02X %02X %02X %02X %02X %02X %02X", mi[0U], mi[1U], mi[2U], mi[3U], mi[4U], mi[5U], mi[6U], mi[7U], mi[8U]); } @@ -1270,7 +1270,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) float ber = float(errs) / 12.33F; if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU2_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + LogInfoEx(LOG_CAL, P25_LDU2_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); m_berUncorrectable++; @@ -1307,7 +1307,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) Utils::dump(1U, "P25, Unfixable PDU Data", pduBuffer, P25_PDU_FEC_LENGTH_BYTES); } else { - LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", + LogInfoEx(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padLength = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), dataHeader.getBlocksToFollow(), dataHeader.getPadLength(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), dataHeader.getHeaderOffset()); @@ -1327,7 +1327,7 @@ void HostSetup::processP25BER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_TSDU_STR ", mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, netId = %u, sysId = %u", + LogInfoEx(LOG_CAL, P25_TSDU_STR ", mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, netId = %u, sysId = %u", tsbk->getMFId(), tsbk->getLCO(), tsbk->getSrcId(), tsbk->getDstId(), tsbk->getService(), tsbk->getNetId(), tsbk->getSysId()); } } @@ -1356,7 +1356,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + LogInfoEx(LOG_RF, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); } m_berBits = 0U; @@ -1367,7 +1367,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) } else if (duid == DUID::TDU) { if (m_berFrames != 0U) { - LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); @@ -1392,7 +1392,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", + LogInfoEx(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); } @@ -1401,7 +1401,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) float ber = float(errs) / 12.33F; if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU1_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + LogInfoEx(LOG_CAL, P25_LDU1_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); m_berUncorrectable++; @@ -1423,7 +1423,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) m_berUndecodableLC++; } else { - LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", + LogInfoEx(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", lc.getMFId(), lc.getAlgId(), lc.getKId()); } @@ -1432,7 +1432,7 @@ void HostSetup::processP251KBER(const uint8_t* buffer) float ber = float(errs) / 12.33F; if (ber < 10.0F) - LogMessage(LOG_CAL, P25_LDU2_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + LogInfoEx(LOG_CAL, P25_LDU2_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); m_berUncorrectable++; @@ -1467,7 +1467,7 @@ void HostSetup::processNXDNBER(const uint8_t* buffer) if (usc == FuncChannelType::USC_SACCH_NS) { if (m_berFrames == 0U) { - LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), BER Start"); + LogInfoEx(LOG_CAL, "NXDN VCALL (Voice Call), BER Start"); timerStart(); m_berErrs = 0U; @@ -1476,7 +1476,7 @@ void HostSetup::processNXDNBER(const uint8_t* buffer) return; } else { float ber = float(m_berErrs * 100U) / float(m_berBits); - LogMessage(LOG_CAL, "NXDN TX_REL (Transmission Release), BER Test, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); + LogInfoEx(LOG_CAL, "NXDN TX_REL (Transmission Release), BER Test, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); // handle displaying TUI updateTUIBER(ber); @@ -1502,7 +1502,7 @@ void HostSetup::processNXDNBER(const uint8_t* buffer) m_berFrames++; float ber = float(errors) / 1.88F; - LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), BER Test, (errs): %.3f%% (%u/188)", ber, errors); + LogInfoEx(LOG_CAL, "NXDN VCALL (Voice Call), BER Test, (errs): %.3f%% (%u/188)", ber, errors); // handle displaying TUI updateTUIBER(ber); @@ -1813,7 +1813,7 @@ bool HostSetup::readFlash() void HostSetup::processFlashConfig(const uint8_t *buffer) { if (m_updateConfigFromModem) { - LogMessage(LOG_CAL, " - Restoring local configuration from configuration area on modem"); + LogInfoEx(LOG_CAL, " - Restoring local configuration from configuration area on modem"); // general config m_modem->m_rxInvert = (buffer[3U] & 0x01U) == 0x01U; @@ -1949,7 +1949,7 @@ bool HostSetup::eraseFlash() sleep(1000U); m_updateConfigFromModem = false; - LogMessage(LOG_CAL, " - Erased configuration area on modem"); + LogInfoEx(LOG_CAL, " - Erased configuration area on modem"); m_modem->clock(0U); return true; @@ -2074,7 +2074,7 @@ bool HostSetup::writeFlash() void HostSetup::writeBootload() { m_reqBootload = true; - LogMessage(LOG_CAL, "Rebooting modem into ST bootloader mode..."); + LogInfoEx(LOG_CAL, "Rebooting modem into ST bootloader mode..."); if (writeFlash()) { uint8_t buffer[4U]; @@ -2097,7 +2097,7 @@ void HostSetup::timerClock() m_timer += 1U; if (m_timer >= m_timeout) { - LogMessage(LOG_CAL, "Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); + LogInfoEx(LOG_CAL, "Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); m_berBits = 0U; m_berErrs = 0U; diff --git a/src/host/setup/SetupMainWnd.h b/src/host/setup/SetupMainWnd.h index a06c68998..401b90d6d 100644 --- a/src/host/setup/SetupMainWnd.h +++ b/src/host/setup/SetupMainWnd.h @@ -63,7 +63,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { explicit SetupMainWnd(HostSetup* setup, FWidget* widget = nullptr) : FWidget{widget}, m_setup(setup) { - __InternalOutputStream(m_logWnd); + log_internal::SetInternalOutputStream(m_logWnd); m_statusWnd.hide(); resetBERWnd(); @@ -127,7 +127,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_p25Cal.addCallback("toggled", [&]() { @@ -144,7 +144,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_dmrLFCal.addCallback("toggled", [&]() { @@ -161,7 +161,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_dmrCal1K.addCallback("toggled", [&]() { @@ -178,7 +178,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_dmrDMOCal1K.addCallback("toggled", [&]() { @@ -195,7 +195,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_p25Cal1K.addCallback("toggled", [&]() { @@ -212,7 +212,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_p25TDUTest.addCallback("toggled", [&]() { @@ -229,7 +229,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { m_setup->m_queue.clear(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_nxdnCal1K.addCallback("toggled", [&]() { @@ -248,7 +248,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); } else { @@ -267,7 +267,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(true); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_dmrFEC1K.addCallback("toggled", [&]() { @@ -282,7 +282,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(true); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_p25FEC.addCallback("toggled", [&]() { @@ -298,7 +298,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(true); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_p25FEC1K.addCallback("toggled", [&]() { @@ -314,7 +314,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(true); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_nxdnFEC.addCallback("toggled", [&]() { @@ -332,7 +332,7 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { updateDuplexState(); resetBERWnd(true); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); } else { @@ -351,42 +351,42 @@ class HOST_SW_API SetupMainWnd final : public finalcut::FWidget { resetBERWnd(); - LogMessage(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); + LogInfoEx(LOG_CAL, " - %s", m_setup->m_modeStr.c_str()); m_setup->writeConfig(); }); m_toggleTxInvert.addCallback("toggled", this, [&]() { if (!m_setup->m_isHotspot) { m_setup->m_modem->m_txInvert = !m_setup->m_modem->m_txInvert; - LogMessage(LOG_CAL, "Tx Invert: %s", m_setup->m_modem->m_txInvert ? "on" : "off"); + LogInfoEx(LOG_CAL, "Tx Invert: %s", m_setup->m_modem->m_txInvert ? "on" : "off"); m_setup->writeConfig(); } }); m_toggleRxInvert.addCallback("toggled", this, [&]() { if (!m_setup->m_isHotspot) { m_setup->m_modem->m_rxInvert = !m_setup->m_modem->m_rxInvert; - LogMessage(LOG_CAL, "Rx Invert: %s", m_setup->m_modem->m_rxInvert ? "on" : "off"); + LogInfoEx(LOG_CAL, "Rx Invert: %s", m_setup->m_modem->m_rxInvert ? "on" : "off"); m_setup->writeConfig(); } }); m_togglePTTInvert.addCallback("toggled", this, [&]() { if (!m_setup->m_isHotspot) { m_setup->m_modem->m_pttInvert = !m_setup->m_modem->m_pttInvert; - LogMessage(LOG_CAL, "PTT Invert: %s", m_setup->m_modem->m_pttInvert ? "on" : "off"); + LogInfoEx(LOG_CAL, "PTT Invert: %s", m_setup->m_modem->m_pttInvert ? "on" : "off"); m_setup->writeConfig(); } }); m_toggleDCBlocker.addCallback("toggled", this, [&]() { if (!m_setup->m_isHotspot) { m_setup->m_modem->m_dcBlocker = !m_setup->m_modem->m_dcBlocker; - LogMessage(LOG_CAL, "DC Blocker: %s", m_setup->m_modem->m_dcBlocker ? "on" : "off"); + LogInfoEx(LOG_CAL, "DC Blocker: %s", m_setup->m_modem->m_dcBlocker ? "on" : "off"); m_setup->writeConfig(); } }); m_toggleDuplex.addCallback("toggled", this, [&]() { if (m_setup->m_isHotspot && m_setup->m_isConnected) { m_setup->m_duplex = !m_setup->m_duplex; - LogMessage(LOG_CAL, "Hotspot Rx: %s", m_setup->m_duplex ? "Rx Antenna" : "Tx Antenna"); + LogInfoEx(LOG_CAL, "Hotspot Rx: %s", m_setup->m_duplex ? "Rx Antenna" : "Tx Antenna"); m_setup->writeConfig(); } }); diff --git a/src/host/win32/resource.rc b/src/host/win32/resource.rc index d1eb8f1b8..4df7014b0 100644 Binary files a/src/host/win32/resource.rc and b/src/host/win32/resource.rc differ diff --git a/src/monitor/Defines.h b/src/monitor/Defines.h index 44e3f9ab1..46f2d81b6 100644 --- a/src/monitor/Defines.h +++ b/src/monitor/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/monitor/InhibitSubscriberWnd.h b/src/monitor/InhibitSubscriberWnd.h index e9c2a55ae..76204faf9 100644 --- a/src/monitor/InhibitSubscriberWnd.h +++ b/src/monitor/InhibitSubscriberWnd.h @@ -137,7 +137,7 @@ class HOST_SW_API InhibitSubscriberWnd final : public TransmitWndBase { // callback REST API int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port()); } } diff --git a/src/monitor/MonitorMainWnd.h b/src/monitor/MonitorMainWnd.h index d72a0ef0e..72d990fa8 100644 --- a/src/monitor/MonitorMainWnd.h +++ b/src/monitor/MonitorMainWnd.h @@ -58,7 +58,7 @@ class HOST_SW_API MonitorMainWnd final : public finalcut::FWidget { */ explicit MonitorMainWnd(FWidget* widget = nullptr) : FWidget{widget} { - __InternalOutputStream(m_logWnd); + log_internal::SetInternalOutputStream(m_logWnd); // file menu m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X diff --git a/src/monitor/NodeStatusWnd.h b/src/monitor/NodeStatusWnd.h index 9aeea5f41..a4884ca11 100644 --- a/src/monitor/NodeStatusWnd.h +++ b/src/monitor/NodeStatusWnd.h @@ -15,7 +15,7 @@ #define __NODE_STATUS_WND_H__ #include "common/lookups/AffiliationLookup.h" -#include "host/network/RESTDefines.h" +#include "host/restapi/RESTDefines.h" #include "host/modem/Modem.h" #include "remote/RESTClient.h" @@ -376,7 +376,7 @@ class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog { int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(), HTTP_GET, GET_STATUS, req, rsp, m_chData.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to get status for %s:%u, chNo = %u", m_chData.address().c_str(), m_chData.port(), m_channelNo); ++m_failCnt; if (m_failCnt > NODE_UPDATE_FAIL_CNT) { @@ -506,7 +506,7 @@ class HOST_SW_API NodeStatusWnd final : public finalcut::FDialog { json::object req = json::object(); int ret = RESTClient::send(m_chData.address(), m_chData.port(), m_chData.password(), HTTP_GET, GET_STATUS, req, m_chData.ssl(), g_debug); - if (ret == network::rest::http::HTTPPayload::StatusType::OK) { + if (ret == restapi::http::HTTPPayload::StatusType::OK) { m_failed = false; m_failCnt = 0U; m_tbText = std::string("UNKNOWN"); diff --git a/src/monitor/PageSubscriberWnd.h b/src/monitor/PageSubscriberWnd.h index 2ef3c5f8b..39ca03e15 100644 --- a/src/monitor/PageSubscriberWnd.h +++ b/src/monitor/PageSubscriberWnd.h @@ -137,7 +137,7 @@ class HOST_SW_API PageSubscriberWnd final : public TransmitWndBase { // callback REST API int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port()); } } diff --git a/src/monitor/RadioCheckSubscriberWnd.h b/src/monitor/RadioCheckSubscriberWnd.h index c3afe1f34..8114d06d1 100644 --- a/src/monitor/RadioCheckSubscriberWnd.h +++ b/src/monitor/RadioCheckSubscriberWnd.h @@ -137,7 +137,7 @@ class HOST_SW_API RadioCheckSubscriberWnd final : public TransmitWndBase { // callback REST API int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port()); } } diff --git a/src/monitor/TransmitWndBase.h b/src/monitor/TransmitWndBase.h index 8796f9e92..4aeca58b5 100644 --- a/src/monitor/TransmitWndBase.h +++ b/src/monitor/TransmitWndBase.h @@ -15,7 +15,7 @@ #define __TRANSMIT_WND_BASE_H__ #include "common/lookups/AffiliationLookup.h" -#include "host/network/RESTDefines.h" +#include "host/restapi/RESTDefines.h" #include "host/modem/Modem.h" #include "remote/RESTClient.h" #include "MonitorMain.h" @@ -107,7 +107,7 @@ class HOST_SW_API TransmitWndBase : public FDblDialog { int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), HTTP_GET, GET_STATUS, req, rsp, m_selectedCh.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to get status for %s:%u", m_selectedCh.address().c_str(), m_selectedCh.port()); } diff --git a/src/monitor/UninhibitSubscriberWnd.h b/src/monitor/UninhibitSubscriberWnd.h index 012bdb745..ea4851378 100644 --- a/src/monitor/UninhibitSubscriberWnd.h +++ b/src/monitor/UninhibitSubscriberWnd.h @@ -137,7 +137,7 @@ class HOST_SW_API UninhibitSubscriberWnd final : public TransmitWndBase { // callback REST API int ret = RESTClient::send(m_selectedCh.address(), m_selectedCh.port(), m_selectedCh.password(), HTTP_PUT, method, req, m_selectedCh.ssl(), g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "failed to send request %s to %s:%u", method.c_str(), m_selectedCh.address().c_str(), m_selectedCh.port()); } } diff --git a/src/patch/ActivityLog.cpp b/src/patch/ActivityLog.cpp index 2680141fd..cb1693a68 100644 --- a/src/patch/ActivityLog.cpp +++ b/src/patch/ActivityLog.cpp @@ -11,12 +11,6 @@ #include "common/network/BaseNetwork.h" #include "common/Log.h" // for CurrentLogFileLevel() and LogGetNetwork() -#if defined(_WIN32) -#include "common/Clock.h" -#else -#include -#endif // defined(_WIN32) - #if defined(CATCH2_TEST_COMPILATION) #include #endif @@ -39,12 +33,12 @@ const uint32_t ACT_LOG_BUFFER_LEN = 501U; // Global Variables // --------------------------------------------------------------------------- -static std::string m_actFilePath; -static std::string m_actFileRoot; +static std::string g_actFilePath; +static std::string g_actFileRoot; -static FILE* m_actFpLog = nullptr; +static FILE* g_actFpLog = nullptr; -static struct tm m_actTm; +static struct tm g_actTm; // --------------------------------------------------------------------------- // Global Functions @@ -62,22 +56,22 @@ static bool ActivityLogOpen() 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) + if (tm->tm_mday == g_actTm.tm_mday && tm->tm_mon == g_actTm.tm_mon && tm->tm_year == g_actTm.tm_year) { + if (g_actFpLog != nullptr) return true; } else { - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_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; + g_actFpLog = ::fopen(filename, "a+t"); + g_actTm = *tm; - return m_actFpLog != nullptr; + return g_actFpLog != nullptr; } /* Initializes the activity log. */ @@ -87,8 +81,8 @@ bool ActivityLogInitialise(const std::string& filePath, const std::string& fileR #if defined(CATCH2_TEST_COMPILATION) return true; #endif - m_actFilePath = filePath; - m_actFileRoot = fileRoot; + g_actFilePath = filePath; + g_actFileRoot = fileRoot; return ::ActivityLogOpen(); } @@ -100,56 +94,34 @@ void ActivityLogFinalise() #if defined(CATCH2_TEST_COMPILATION) return; #endif - if (m_actFpLog != nullptr) - ::fclose(m_actFpLog); + if (g_actFpLog != nullptr) + ::fclose(g_actFpLog); } /* Writes a new entry to the activity log. */ -void ActivityLog(const char* msg, ...) +void log_internal::ActivityLogInternal(const std::string& log) { #if defined(CATCH2_TEST_COMPILATION) return; #endif - assert(msg != nullptr); - - char buffer[ACT_LOG_BUFFER_LEN]; - time_t now; - ::time(&now); - struct tm* tm = ::localtime(&now); - - struct timeval nowMillis; - ::gettimeofday(&nowMillis, NULL); - - ::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, nowMillis.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 (LogGetNetwork() != nullptr) { + network::BaseNetwork* network = (network::BaseNetwork*)LogGetNetwork(); + network->writeActLog(log.c_str()); + } + + ::fprintf(g_actFpLog, "%s\n", log.c_str()); + ::fflush(g_actFpLog); if (2U >= g_logDisplayLevel && g_logDisplayLevel != 0U) { - ::fprintf(stdout, "%s" EOL, buffer); + ::fprintf(stdout, "%s" EOL, log.c_str()); ::fflush(stdout); } } diff --git a/src/patch/ActivityLog.h b/src/patch/ActivityLog.h index 8f9ffec81..b577a7320 100644 --- a/src/patch/ActivityLog.h +++ b/src/patch/ActivityLog.h @@ -18,12 +18,28 @@ #include "Defines.h" +#if defined(_WIN32) +#include "common/Clock.h" +#else +#include +#endif // defined(_WIN32) + #include // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- +namespace log_internal +{ + /** + * @brief Writes a new entry to the diagnostics log. + * @param level Log level for entry. + * @param log Fully formatted log message. + */ + extern HOST_SW_API void ActivityLogInternal(const std::string& log); +} // namespace log_internal + /** * @brief Initializes the activity log. * @param filePath File path for the log file. @@ -34,12 +50,45 @@ extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const * @brief Finalizes the activity log. */ extern HOST_SW_API void ActivityLogFinalise(); + /** - * @brief Writes a new entry to the activity log. - * @param msg String format. + * @brief Writes a new entry to the diagnostics log. + * @param fmt String format. * - * This is a variable argument function. + * This is a variable argument function. This shouldn't be called directly, utilize the LogXXXX macros above, instead. */ -extern HOST_SW_API void ActivityLog(const char* msg, ...); +template +HOST_SW_API void ActivityLog(const std::string& fmt, Args... args) +{ + using namespace log_internal; + + int size_s = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + + int prefixLen = 0; + char prefixBuf[256]; + + time_t now; + ::time(&now); + struct tm* tm = ::localtime(&now); + + struct timeval nowMillis; + ::gettimeofday(&nowMillis, NULL); + + prefixLen = ::sprintf(prefixBuf, "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, nowMillis.tv_usec / 1000U); + + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + + std::snprintf(buf.get(), size, fmt.c_str(), args ...); + + std::string prefix = std::string(prefixBuf, prefixBuf + prefixLen); + std::string msg = std::string(buf.get(), buf.get() + size - 1); + + ActivityLogInternal(std::string(prefix + msg)); +} #endif // __ACTIVITY_LOG_H__ diff --git a/src/patch/Defines.h b/src/patch/Defines.h index 394c328c8..35ac458af 100644 --- a/src/patch/Defines.h +++ b/src/patch/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/patch/HostPatch.cpp b/src/patch/HostPatch.cpp index f9d12bfcc..563d49d8a 100644 --- a/src/patch/HostPatch.cpp +++ b/src/patch/HostPatch.cpp @@ -49,6 +49,7 @@ using namespace network::udp; // Constants // --------------------------------------------------------------------------- +#define TEK_DES "des" #define TEK_AES "aes" #define TEK_ARC4 "arc4" @@ -56,7 +57,7 @@ using namespace network::udp; // Static Class Members // --------------------------------------------------------------------------- -std::mutex HostPatch::m_networkMutex; +std::mutex HostPatch::s_networkMutex; // --------------------------------------------------------------------------- // Public Class Members @@ -89,8 +90,10 @@ HostPatch::HostPatch(const std::string& confFile) : m_callAlgoId(P25DEF::ALGO_UNENCRYPT), m_rxStartTime(0U), m_rxStreamId(0U), + m_tekSrcEnable(false), m_tekSrcAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekSrcKeyId(0U), + m_tekDstEnable(false), m_tekDstAlgoId(P25DEF::ALGO_UNENCRYPT), m_tekDstKeyId(0U), m_requestedSrcTek(false), @@ -247,12 +250,12 @@ int HostPatch::run() // ------------------------------------------------------ if (m_network != nullptr) { - std::lock_guard lock(HostPatch::m_networkMutex); + std::lock_guard lock(HostPatch::s_networkMutex); m_network->clock(ms); } if (m_mmdvmP25Reflector) { - std::lock_guard lock(HostPatch::m_networkMutex); + std::lock_guard lock(HostPatch::s_networkMutex); m_mmdvmP25Net->clock(ms); } @@ -351,6 +354,7 @@ bool HostPatch::createNetwork() m_dstSlot = (uint8_t)networkConf["destinationSlot"].as(1U); // source TEK parameters + m_tekSrcEnable = false; yaml::Node srcTekConf = networkConf["srcTek"]; bool tekSrcEnable = srcTekConf["enable"].as(false); std::string tekSrcAlgo = srcTekConf["tekAlgo"].as(); @@ -361,8 +365,10 @@ bool HostPatch::createNetwork() m_tekSrcAlgoId = P25DEF::ALGO_AES_256; else if (tekSrcAlgo == TEK_ARC4) m_tekSrcAlgoId = P25DEF::ALGO_ARC4; + else if (tekSrcAlgo == TEK_DES) + m_tekSrcAlgoId = P25DEF::ALGO_DES; else { - ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"arc4\"."); m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekSrcKeyId = 0U; } @@ -372,9 +378,12 @@ bool HostPatch::createNetwork() m_tekSrcAlgoId = P25DEF::ALGO_UNENCRYPT; if (m_tekSrcAlgoId == P25DEF::ALGO_UNENCRYPT) m_tekSrcKeyId = 0U; + if (m_tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT) + m_tekSrcEnable = true; // destination TEK parameters - yaml::Node dstTekConf = networkConf["srcTek"]; + m_tekDstEnable = false; + yaml::Node dstTekConf = networkConf["dstTek"]; bool tekDstEnable = dstTekConf["enable"].as(false); std::string tekDstAlgo = dstTekConf["tekAlgo"].as(); std::transform(tekDstAlgo.begin(), tekDstAlgo.end(), tekDstAlgo.begin(), ::tolower); @@ -384,8 +393,10 @@ bool HostPatch::createNetwork() m_tekDstAlgoId = P25DEF::ALGO_AES_256; else if (tekDstAlgo == TEK_ARC4) m_tekDstAlgoId = P25DEF::ALGO_ARC4; + else if (tekDstAlgo == TEK_DES) + m_tekDstAlgoId = P25DEF::ALGO_DES; else { - ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"adp\"."); + ::LogError(LOG_HOST, "Invalid TEK algorithm specified, must be \"aes\" or \"arc4\"."); m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; m_tekDstKeyId = 0U; } @@ -395,6 +406,8 @@ bool HostPatch::createNetwork() m_tekDstAlgoId = P25DEF::ALGO_UNENCRYPT; if (m_tekDstAlgoId == P25DEF::ALGO_UNENCRYPT) m_tekDstKeyId = 0U; + if (m_tekDstAlgoId != P25DEF::ALGO_UNENCRYPT) + m_tekDstEnable = true; m_twoWayPatch = networkConf["twoWay"].as(false); @@ -681,7 +694,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; - LogMessage(LOG_HOST, "DMR, call start, srcId = %u, dstId = %u, slot = %u", srcId, dstId, slotNo); + LogInfoEx(LOG_HOST, "DMR, call start, srcId = %u, dstId = %u, slot = %u", srcId, dstId, slotNo); } if (dataSync && (dataType == DataType::TERMINATOR_WITH_LC)) { @@ -710,7 +723,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; - LogMessage(LOG_HOST, "DMR, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "DMR, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_callInProgress = false; @@ -729,7 +742,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) lc::FullLC fullLC = lc::FullLC(); lc = *fullLC.decode(data.get(), DataType::VOICE_LC_HEADER); - LogMessage(LOG_HOST, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X", m_srcSlot, + LogInfoEx(LOG_HOST, DMR_DT_VOICE_LC_HEADER ", slot = %u, srcId = %u, dstId = %u, FLCO = $%02X", m_srcSlot, lc.getSrcId(), lc.getDstId(), flco); // send DMR voice header @@ -778,7 +791,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) lc::FullLC fullLC = lc::FullLC(); lc = *fullLC.decodePI(data.get()); - LogMessage(LOG_HOST, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_srcSlot, + LogInfoEx(LOG_HOST, DMR_DT_VOICE_PI_HEADER ", slot = %u, algId = %u, kId = %u, dstId = %u", m_srcSlot, lc.getAlgId(), lc.getKId(), lc.getDstId()); // send DMR voice header @@ -842,7 +855,7 @@ void HostPatch::processDMRNetwork(uint8_t* buffer, uint32_t length) emb.encode(buffer); } - LogMessage(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_srcSlot, seqNo); + LogInfoEx(LOG_HOST, DMR_DT_VOICE ", srcId = %u, dstId = %u, slot = %u, seqNo = %u", srcId, dstId, m_srcSlot, seqNo); // generate DMR network frame data::NetData dmrData; @@ -947,6 +960,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) return; bool reverseEncrypt = false; + bool tekEnable = m_tekSrcEnable; uint32_t actualDstId = m_srcTGId; uint8_t tekAlgoId = m_tekSrcAlgoId; uint16_t tekKeyId = m_tekSrcKeyId; @@ -956,6 +970,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) if (m_twoWayPatch) { if (dstId == m_dstTGId) { actualDstId = m_srcTGId; + tekEnable = m_tekDstEnable; tekAlgoId = m_tekDstAlgoId; tekKeyId = m_tekDstKeyId; reverseEncrypt = true; @@ -975,7 +990,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) uint8_t frameType = buffer[180U]; if (frameType == FrameType::HDU_VALID) { m_callAlgoId = buffer[181U]; - if (m_callAlgoId != ALGO_UNENCRYPT) { + if (tekEnable && m_callAlgoId != ALGO_UNENCRYPT) { callKID = GET_UINT16(buffer, 182U); if (m_callAlgoId != tekAlgoId && callKID != tekKeyId) { @@ -1005,7 +1020,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_rxStartTime = now; - LogMessage(LOG_HOST, "P25, call start, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_HOST, "P25, call start, srcId = %u, dstId = %u", srcId, dstId); if (m_grantDemand) { p25::lc::LC lc = p25::lc::LC(); @@ -1034,7 +1049,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, P25_TDU_STR); + LogInfoEx(LOG_HOST, P25_TDU_STR); if (m_mmdvmP25Reflector) { m_mmdvmP25Net->writeTDU(); @@ -1048,7 +1063,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - m_rxStartTime; - LogMessage(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); + LogInfoEx(LOG_HOST, "P25, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U); } m_rxStartTime = 0U; @@ -1119,9 +1134,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) dfsiLC.decodeLDU1(data.get() + count, netLDU + 204U); count += DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; - LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_NET, P25_LDU1_STR " audio, srcId = %u, dstId = %u", srcId, dstId); - if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + if (tekEnable && tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { cryptP25AudioFrame(netLDU, reverseEncrypt, 1U); } @@ -1149,7 +1164,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) } // the previous is nice and all -- but if we're cross-encrypting, we need to use the TEK - if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + if (tekEnable && tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { control.setAlgId(tekAlgoId); control.setKId(tekKeyId); @@ -1219,9 +1234,9 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) dfsiLC.decodeLDU2(data.get() + count, netLDU + 204U); count += DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; - LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); + LogInfoEx(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X", dfsiLC.control()->getAlgId(), dfsiLC.control()->getKId()); - if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + if (tekEnable && tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { cryptP25AudioFrame(netLDU, reverseEncrypt, 2U); } @@ -1231,7 +1246,7 @@ void HostPatch::processP25Network(uint8_t* buffer, uint32_t length) control.setDstId(actualDstId); // set the algo ID and key ID - if (tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { + if (tekEnable && tekAlgoId != ALGO_UNENCRYPT && tekKeyId != 0U) { control.setAlgId(tekAlgoId); control.setKId(tekKeyId); @@ -1336,6 +1351,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 case P25DEF::ALGO_ARC4: m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25SrcCrypto->cryptDES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; @@ -1349,6 +1367,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 case P25DEF::ALGO_ARC4: m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25DstCrypto->cryptDES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; @@ -1367,6 +1388,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 case P25DEF::ALGO_ARC4: m_p25DstCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25DstCrypto->cryptDES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekDstAlgoId); break; @@ -1380,6 +1404,9 @@ void HostPatch::cryptP25AudioFrame(uint8_t* ldu, bool reverseEncrypt, uint8_t p2 case P25DEF::ALGO_ARC4: m_p25SrcCrypto->cryptARC4_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); break; + case P25DEF::ALGO_DES: + m_p25SrcCrypto->cryptDES_IMBE(imbe, (p25N == 1U) ? DUID::LDU1 : DUID::LDU2); + break; default: LogError(LOG_HOST, "Unsupported TEK algorithm, tekAlgoId = $%02X", tekSrcAlgoId); break; @@ -1398,7 +1425,7 @@ void HostPatch::processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t return; if (algId == m_tekSrcAlgoId && ki->kId() == m_tekSrcKeyId) { - LogMessage(LOG_HOST, "Source TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + LogInfoEx(LOG_HOST, "Source TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); UInt8Array tek = std::make_unique(keyLength); ki->getKey(tek.get()); @@ -1408,7 +1435,7 @@ void HostPatch::processTEKResponse(p25::kmm::KeyItem* ki, uint8_t algId, uint8_t } if (algId == m_tekDstAlgoId && ki->kId() == m_tekDstKeyId) { - LogMessage(LOG_HOST, "Destination TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); + LogInfoEx(LOG_HOST, "Destination TEK loaded, algId = $%02X, kId = $%04X, sln = $%04X", algId, ki->kId(), ki->sln()); UInt8Array tek = std::make_unique(keyLength); ki->getKey(tek.get()); @@ -1453,7 +1480,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) uint32_t dstId = GET_UINT24(m_netLDU1, 76U); uint32_t srcId = GET_UINT24(m_netLDU1, 101U); - LogMessage(LOG_HOST, "MMDVM P25, call start, srcId = %u, dstId = %u", srcId, dstId); + LogInfoEx(LOG_HOST, "MMDVM P25, call start, srcId = %u, dstId = %u", srcId, dstId); lc::LC lc = lc::LC(); m_netLC = lc; @@ -1479,7 +1506,7 @@ void HostPatch::writeNet_LDU1(bool toFNE) lsd.setLSD1(m_netLDU1[201U]); lsd.setLSD2(m_netLDU1[202U]); - LogMessage(LOG_NET, "MMDVM " P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); + LogInfoEx(LOG_NET, "MMDVM " P25_LDU1_STR " audio, srcId = %u, dstId = %u", m_netLC.getSrcId(), m_netLC.getDstId()); if (m_debug) Utils::dump(1U, "P25, HostPatch::writeNet_LDU1(), MMDVM -> DVM LDU1", m_netLDU1, 9U * 25U); @@ -1535,7 +1562,7 @@ void HostPatch::writeNet_LDU2(bool toFNE) lsd.setLSD1(m_netLDU2[201U]); lsd.setLSD2(m_netLDU2[202U]); - LogMessage(LOG_NET, "MMDVM " P25_LDU2_STR " audio"); + LogInfoEx(LOG_NET, "MMDVM " P25_LDU2_STR " audio"); if (m_debug) Utils::dump(1U, "P25, HostPatch::writeNet_LDU2(), MMDVM -> DVM LDU2", m_netLDU2, 9U * 25U); @@ -1585,7 +1612,7 @@ void* HostPatch::threadNetworkProcess(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -1601,7 +1628,7 @@ void* HostPatch::threadNetworkProcess(void* arg) if (patch->m_tekSrcAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekSrcKeyId > 0U) { if (patch->m_p25SrcCrypto->getTEKLength() == 0U && !patch->m_requestedSrcTek) { patch->m_requestedSrcTek = true; - LogMessage(LOG_HOST, "Patch source TGID encryption enabled, requesting TEK from network."); + LogInfoEx(LOG_HOST, "Patch source TGID encryption enabled, requesting TEK from network."); patch->m_network->writeKeyReq(patch->m_tekSrcKeyId, patch->m_tekSrcAlgoId); } } @@ -1610,7 +1637,7 @@ void* HostPatch::threadNetworkProcess(void* arg) if (patch->m_tekDstAlgoId != P25DEF::ALGO_UNENCRYPT && patch->m_tekDstKeyId > 0U) { if (patch->m_p25DstCrypto->getTEKLength() == 0U && !patch->m_requestedDstTek) { patch->m_requestedDstTek = true; - LogMessage(LOG_HOST, "Patch destination TGID encryption enabled, requesting TEK from network."); + LogInfoEx(LOG_HOST, "Patch destination TGID encryption enabled, requesting TEK from network."); patch->m_network->writeKeyReq(patch->m_tekDstKeyId, patch->m_tekDstAlgoId); } } @@ -1619,7 +1646,7 @@ void* HostPatch::threadNetworkProcess(void* arg) uint32_t length = 0U; bool netReadRet = false; if (patch->m_digiMode == TX_MODE_DMR) { - std::lock_guard lock(HostPatch::m_networkMutex); + std::lock_guard lock(HostPatch::s_networkMutex); UInt8Array dmrBuffer = patch->m_network->readDMR(netReadRet, length); if (netReadRet) { patch->processDMRNetwork(dmrBuffer.get(), length); @@ -1627,7 +1654,7 @@ void* HostPatch::threadNetworkProcess(void* arg) } if (patch->m_digiMode == TX_MODE_P25) { - std::lock_guard lock(HostPatch::m_networkMutex); + std::lock_guard lock(HostPatch::s_networkMutex); UInt8Array p25Buffer = patch->m_network->readP25(netReadRet, length); if (netReadRet) { patch->processP25Network(p25Buffer.get(), length); @@ -1637,7 +1664,7 @@ void* HostPatch::threadNetworkProcess(void* arg) Thread::sleep(1U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } @@ -1672,7 +1699,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -1692,7 +1719,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) stopWatch.start(); if (patch->m_digiMode == TX_MODE_P25) { - std::lock_guard lock(HostPatch::m_networkMutex); + std::lock_guard lock(HostPatch::s_networkMutex); DECLARE_UINT8_ARRAY(buffer, 100U); uint32_t len = patch->m_mmdvmP25Net->read(buffer, 100U); @@ -1775,7 +1802,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); - LogMessage(LOG_HOST, "MMDVM " P25_TDU_STR); + LogInfoEx(LOG_HOST, "MMDVM " P25_TDU_STR); uint8_t controlByte = 0x00U; patch->m_network->writeP25TDU(patch->m_netLC, lsd, controlByte); @@ -1784,7 +1811,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); uint64_t diff = now - patch->m_rxStartTime; - LogMessage(LOG_HOST, "MMDVM P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); + LogInfoEx(LOG_HOST, "MMDVM P25, call end, srcId = %u, dstId = %u, dur = %us", patch->m_netLC.getSrcId(), patch->m_netLC.getDstId(), diff / 1000U); } patch->m_rxStartTime = 0U; @@ -1812,7 +1839,7 @@ void* HostPatch::threadMMDVMProcess(void* arg) Thread::sleep(5U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/patch/HostPatch.h b/src/patch/HostPatch.h index 2bc363741..9c3bf2371 100644 --- a/src/patch/HostPatch.h +++ b/src/patch/HostPatch.h @@ -99,8 +99,10 @@ class HOST_SW_API HostPatch { uint64_t m_rxStartTime; uint32_t m_rxStreamId; + bool m_tekSrcEnable; uint8_t m_tekSrcAlgoId; uint16_t m_tekSrcKeyId; + bool m_tekDstEnable; uint8_t m_tekDstAlgoId; uint16_t m_tekDstKeyId; bool m_requestedSrcTek; @@ -116,7 +118,7 @@ class HOST_SW_API HostPatch { bool m_trace; bool m_debug; - static std::mutex m_networkMutex; + static std::mutex s_networkMutex; /** * @brief Reads basic configuration parameters from the INI. diff --git a/src/patch/PatchMain.cpp b/src/patch/PatchMain.cpp index f03ef9b7a..87c40afab 100644 --- a/src/patch/PatchMain.cpp +++ b/src/patch/PatchMain.cpp @@ -194,6 +194,8 @@ int main(int argc, char** argv) } } + log_stacktrace::SignalHandling sh(g_foreground); + ::signal(SIGINT, sigHandler); ::signal(SIGTERM, sigHandler); #if !defined(_WIN32) diff --git a/src/patch/mmdvm/P25Network.cpp b/src/patch/mmdvm/P25Network.cpp index 892cf6f14..131c4f4fe 100644 --- a/src/patch/mmdvm/P25Network.cpp +++ b/src/patch/mmdvm/P25Network.cpp @@ -451,7 +451,7 @@ void P25Network::clock(uint32_t ms) return; if (!Socket::match(m_addr, address)) { - LogMessage(LOG_NET, "MMDVM, packet received from an invalid source"); + LogInfoEx(LOG_NET, "MMDVM, packet received from an invalid source"); return; } @@ -480,7 +480,7 @@ bool P25Network::open() return false; } - LogMessage(LOG_NET, "MMDVM, Opening P25 network connection"); + LogInfoEx(LOG_NET, "MMDVM, Opening P25 network connection"); return m_socket.open(m_addr); } @@ -491,5 +491,5 @@ void P25Network::close() { m_socket.close(); - LogMessage(LOG_NET, "MMDVM, Closing P25 network connection"); + LogInfoEx(LOG_NET, "MMDVM, Closing P25 network connection"); } diff --git a/src/patch/network/PeerNetwork.cpp b/src/patch/network/PeerNetwork.cpp index 6bbe6ad54..497760f12 100644 --- a/src/patch/network/PeerNetwork.cpp +++ b/src/patch/network/PeerNetwork.cpp @@ -11,9 +11,9 @@ #include "common/dmr/data/EMB.h" #include "common/dmr/lc/FullLC.h" #include "common/dmr/SlotType.h" -#include "common/network/json/json.h" #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" +#include "common/json/json.h" #include "common/Utils.h" #include "network/PeerNetwork.h" diff --git a/src/patch/win32/resource.rc b/src/patch/win32/resource.rc index bb9ede682..f310e91ca 100644 Binary files a/src/patch/win32/resource.rc and b/src/patch/win32/resource.rc differ diff --git a/src/peered/Defines.h b/src/peered/Defines.h index 7ec07b5ef..9dbb20f51 100644 --- a/src/peered/Defines.h +++ b/src/peered/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/peered/PeerEdMain.cpp b/src/peered/PeerEdMain.cpp index a2ea5d60e..116ad27ce 100644 --- a/src/peered/PeerEdMain.cpp +++ b/src/peered/PeerEdMain.cpp @@ -210,7 +210,7 @@ int main(int argc, char** argv) g_pidLookups = new PeerListLookup(g_iniFile, 0U, false); g_pidLookups->read(); - LogMessage(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str()); + LogInfoEx(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str()); // show and start the application wnd.show(); diff --git a/src/peered/PeerEdMainWnd.h b/src/peered/PeerEdMainWnd.h index 90b49e73f..f0ebff911 100644 --- a/src/peered/PeerEdMainWnd.h +++ b/src/peered/PeerEdMainWnd.h @@ -60,7 +60,7 @@ class HOST_SW_API PeerEdMainWnd final : public finalcut::FWidget { */ explicit PeerEdMainWnd(FWidget* widget = nullptr) : FWidget{widget} { - __InternalOutputStream(m_logWnd); + log_internal::SetInternalOutputStream(m_logWnd); // file menu m_fileMenuSeparator1.setSeparator(); @@ -73,7 +73,7 @@ class HOST_SW_API PeerEdMainWnd final : public finalcut::FWidget { m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this); m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); - m_keyF5.addCallback("activate", this, [&]() { g_pidLookups->reload(); m_wnd->loadListView(); LogMessage(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str()); }); + m_keyF5.addCallback("activate", this, [&]() { g_pidLookups->reload(); m_wnd->loadListView(); LogInfoEx(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str()); }); m_backupOnSave.setChecked(); @@ -123,7 +123,7 @@ class HOST_SW_API PeerEdMainWnd final : public finalcut::FWidget { { if (m_backupOnSave.isChecked()) { std::string bakFile = g_iniFile + ".bak"; - LogMessage(LOG_HOST, "Backing up existing file %s to %s", g_iniFile.c_str(), bakFile.c_str()); + LogInfoEx(LOG_HOST, "Backing up existing file %s to %s", g_iniFile.c_str(), bakFile.c_str()); copyFile(g_iniFile.c_str(), bakFile.c_str()); } diff --git a/src/peered/PeerEditWnd.h b/src/peered/PeerEditWnd.h index 1dabf2873..6133b47ec 100644 --- a/src/peered/PeerEditWnd.h +++ b/src/peered/PeerEditWnd.h @@ -150,9 +150,10 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { FLineEdit m_peerPassword{&m_sourceGroup}; FButtonGroup m_configGroup{"Configuration", this}; - FCheckBox m_peerLinkEnabled{"Peer Link", &m_configGroup}; + FCheckBox m_peerReplicaEnabled{"Peer Replica", &m_configGroup}; FCheckBox m_canReqKeysEnabled{"Request Keys", &m_configGroup}; FCheckBox m_canInhibitEnabled{"Issue Inhibit", &m_configGroup}; + FCheckBox m_callPriorityEnabled{"Call Priority", &m_configGroup}; /** * @brief Initializes the window layout. @@ -263,12 +264,12 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { // configuration { - m_configGroup.setGeometry(FPoint(39, 5), FSize(23, 5)); + m_configGroup.setGeometry(FPoint(39, 5), FSize(23, 6)); - m_peerLinkEnabled.setGeometry(FPoint(2, 1), FSize(10, 1)); - m_peerLinkEnabled.setChecked(m_rule.peerLink()); - m_peerLinkEnabled.addCallback("toggled", [&]() { - m_rule.peerLink(m_peerLinkEnabled.isChecked()); + m_peerReplicaEnabled.setGeometry(FPoint(2, 1), FSize(10, 1)); + m_peerReplicaEnabled.setChecked(m_rule.peerReplica()); + m_peerReplicaEnabled.addCallback("toggled", [&]() { + m_rule.peerReplica(m_peerReplicaEnabled.isChecked()); }); m_canReqKeysEnabled.setGeometry(FPoint(2, 2), FSize(10, 1)); @@ -282,6 +283,12 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { m_canInhibitEnabled.addCallback("toggled", [&]() { m_rule.canIssueInhibit(m_canInhibitEnabled.isChecked()); }); + + m_callPriorityEnabled.setGeometry(FPoint(2, 4), FSize(10, 1)); + m_callPriorityEnabled.setChecked(m_rule.hasCallPriority()); + m_callPriorityEnabled.addCallback("toggled", [&]() { + m_rule.hasCallPriority(m_callPriorityEnabled.isChecked()); + }); } CloseWndBase::initControls(); @@ -294,11 +301,12 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { { std::string peerAlias = m_rule.peerAlias(); uint32_t peerId = m_rule.peerId(); - bool peerLink = m_rule.peerLink(); + bool peerReplica = m_rule.peerReplica(); bool canRequestKeys = m_rule.canRequestKeys(); bool canIssueInhibit = m_rule.canIssueInhibit(); + bool hasCallPriority = m_rule.hasCallPriority(); - ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u CAN ISSUE INHIBIT: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys, canIssueInhibit); + ::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u REPLICA: %u CAN REQUEST KEYS: %u CAN ISSUE INHIBIT: %u HAS CALL PRIORITY: %u", peerAlias.c_str(), peerId, peerReplica, canRequestKeys, canIssueInhibit, hasCallPriority); } /* @@ -357,18 +365,18 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { // update peer auto peers = g_pidLookups->tableAsList(); auto it = std::find_if(peers.begin(), peers.end(), - [&](lookups::PeerId x) - { + [&](lookups::PeerId& x) { return x.peerId() == m_origPeerId; }); if (it != peers.end()) { - LogMessage(LOG_HOST, "Updating peer %s (%u) to %s (%u)", it->peerAlias().c_str(), it->peerId(), m_rule.peerAlias().c_str(), m_rule.peerId()); + LogInfoEx(LOG_HOST, "Updating peer %s (%u) to %s (%u)", it->peerAlias().c_str(), it->peerId(), m_rule.peerAlias().c_str(), m_rule.peerId()); g_pidLookups->eraseEntry(m_origPeerId); lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); - entry.peerLink(m_rule.peerLink()); + entry.peerReplica(m_rule.peerReplica()); entry.canRequestKeys(m_rule.canRequestKeys()); entry.canIssueInhibit(m_rule.canIssueInhibit()); + entry.hasCallPriority(m_rule.hasCallPriority()); g_pidLookups->addEntry(m_rule.peerId(), entry); @@ -383,8 +391,7 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { auto peers = g_pidLookups->tableAsList(); auto it = std::find_if(peers.begin(), peers.end(), - [&](lookups::PeerId x) - { + [&](lookups::PeerId& x) { return x.peerId() == m_rule.peerId(); }); if (it != peers.end()) { @@ -397,15 +404,16 @@ class HOST_SW_API PeerEditWnd final : public CloseWndBase { // add new peer if (m_saveCopy.isChecked()) { - LogMessage(LOG_HOST, "Copying Peer. Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); + LogInfoEx(LOG_HOST, "Copying Peer. Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); } else { - LogMessage(LOG_HOST, "Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); + LogInfoEx(LOG_HOST, "Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId()); } lookups::PeerId entry = lookups::PeerId(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), false); - entry.peerLink(m_rule.peerLink()); + entry.peerReplica(m_rule.peerReplica()); entry.canRequestKeys(m_rule.canRequestKeys()); entry.canIssueInhibit(m_rule.canIssueInhibit()); + entry.hasCallPriority(m_rule.hasCallPriority()); g_pidLookups->addEntry(m_rule.peerId(), entry); diff --git a/src/peered/PeerListWnd.h b/src/peered/PeerListWnd.h index 7fa6fd14f..3b0e25b9c 100644 --- a/src/peered/PeerListWnd.h +++ b/src/peered/PeerListWnd.h @@ -98,52 +98,55 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { m_selected = PeerId(); m_selectedPeerId = 0U; - auto entry = g_pidLookups->tableAsList()[0U]; - m_selected = entry; - - // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position - /* - * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. - */ - int firstScrollLinePos = 0; - if (m_listView.getCount() > 0) { - firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); - } + if (g_pidLookups->tableAsList().size() > 0U) { + auto entry = g_pidLookups->tableAsList()[0U]; + m_selected = entry; + + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } - m_listView.clear(); - for (auto entry : g_pidLookups->tableAsList()) { - // pad peer ID properly - std::ostringstream oss; - oss << std::setw(7) << std::setfill('0') << entry.peerId(); - - bool masterPassword = (entry.peerPassword().size() == 0U); - - // build list view entry - const std::array columns = { - oss.str(), - (masterPassword) ? "X" : "", - (entry.peerLink()) ? "X" : "", - (entry.canRequestKeys()) ? "X" : "", - (entry.canIssueInhibit()) ? "X" : "", - entry.peerAlias() - }; - - const finalcut::FStringList line(columns.cbegin(), columns.cend()); - m_listView.insert(line); - } + m_listView.clear(); + for (auto entry : g_pidLookups->tableAsList()) { + // pad peer ID properly + std::ostringstream oss; + oss << std::setw(7) << std::setfill('0') << entry.peerId(); + + bool masterPassword = (entry.peerPassword().size() == 0U); + + // build list view entry + const std::array columns = { + oss.str(), + (masterPassword) ? "X" : "", + (entry.peerReplica()) ? "X" : "", + (entry.canRequestKeys()) ? "X" : "", + (entry.canIssueInhibit()) ? "X" : "", + (entry.hasCallPriority()) ? "X" : "", + entry.peerAlias() + }; + + const finalcut::FStringList line(columns.cbegin(), columns.cend()); + m_listView.insert(line); + } - // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position - /* - * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. - */ - if ((size_t)firstScrollLinePos > m_listView.getCount()) - firstScrollLinePos = 0; - if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { - (m_listView.*RTTIResult::ptr)(firstScrollLinePos); - (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + if ((size_t)firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + } } - // generate dialog title + // generate dialog title uint32_t len = g_pidLookups->tableAsList().size(); std::stringstream ss; ss << "Peer ID List (" << len << " Peers)"; @@ -210,9 +213,10 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { // configure list view columns m_listView.addColumn("Peer ID", 10); m_listView.addColumn("Master Password", 16); - m_listView.addColumn("Peer Link", 12); + m_listView.addColumn("Peer Replica", 12); m_listView.addColumn("Request Keys", 12); m_listView.addColumn("Can Inhibit", 12); + m_listView.addColumn("Call Priority", 12); m_listView.addColumn("Alias", 40); // set right alignment for peer ID @@ -220,7 +224,8 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { m_listView.setColumnAlignment(3, finalcut::Align::Center); m_listView.setColumnAlignment(4, finalcut::Align::Center); m_listView.setColumnAlignment(5, finalcut::Align::Center); - m_listView.setColumnAlignment(6, finalcut::Align::Left); + m_listView.setColumnAlignment(6, finalcut::Align::Center); + m_listView.setColumnAlignment(7, finalcut::Align::Left); // set type of sorting m_listView.setColumnSortType(1, finalcut::SortType::Name); @@ -306,7 +311,7 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { if (m_selected.peerDefault()) return; - LogMessage(LOG_HOST, "Deleting peer ID %s (%u)", m_selected.peerAlias().c_str(), m_selected.peerId()); + LogInfoEx(LOG_HOST, "Deleting peer ID %s (%u)", m_selected.peerAlias().c_str(), m_selected.peerId()); g_pidLookups->eraseEntry(m_selected.peerId()); // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position diff --git a/src/remote/Defines.h b/src/remote/Defines.h index 57959ab1f..002e7f7ee 100644 --- a/src/remote/Defines.h +++ b/src/remote/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/remote/RESTClient.cpp b/src/remote/RESTClient.cpp index 01fbe510f..4ec31f0cb 100644 --- a/src/remote/RESTClient.cpp +++ b/src/remote/RESTClient.cpp @@ -9,16 +9,16 @@ */ #include "Defines.h" #include "common/edac/SHA256.h" -#include "common/network/json/json.h" -#include "common/network/rest/http/HTTPClient.h" -#include "common/network/rest/http/SecureHTTPClient.h" -#include "common/network/rest/RequestDispatcher.h" +#include "common/json/json.h" +#include "common/restapi/http/HTTPClient.h" +#include "common/restapi/http/SecureHTTPClient.h" +#include "common/restapi/RequestDispatcher.h" #include "common/Thread.h" #include "common/Log.h" #include "remote/RESTClient.h" -using namespace network; -using namespace network::rest::http; +using namespace restapi; +using namespace restapi::http; #include #include @@ -44,12 +44,12 @@ using namespace network::rest::http; // Static Class Members // --------------------------------------------------------------------------- -bool RESTClient::m_responseAvailable = false; -HTTPPayload RESTClient::m_response; +bool RESTClient::s_responseAvailable = false; +HTTPPayload RESTClient::s_response; -bool RESTClient::m_console = false; -bool RESTClient::m_enableSSL = false; -bool RESTClient::m_debug = false; +bool RESTClient::s_console = false; +bool RESTClient::s_enableSSL = false; +bool RESTClient::s_debug = false; // --------------------------------------------------------------------------- // Global Functions @@ -94,9 +94,9 @@ RESTClient::RESTClient(const std::string& address, uint32_t port, const std::str assert(!address.empty()); assert(port > 0U); - m_console = true; - m_enableSSL = enableSSL; - m_debug = debug; + s_console = true; + s_enableSSL = enableSSL; + s_debug = debug; } /* Finalizes a instance of the RESTClient class. */ @@ -115,7 +115,7 @@ int RESTClient::send(const std::string method, const std::string endpoint, json: int RESTClient::send(const std::string method, const std::string endpoint, json::object payload, json::object& response) { - return send(m_address, m_port, m_password, method, endpoint, payload, response, m_enableSSL, m_debug); + return send(m_address, m_port, m_password, method, endpoint, payload, response, s_enableSSL, s_debug); } /* Sends remote control command to the specified modem. */ @@ -146,10 +146,10 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } int ret = EXIT_SUCCESS; - m_enableSSL = enableSSL; - m_debug = debug; + s_enableSSL = enableSSL; + s_debug = debug; - typedef network::rest::BasicRequestDispatcher RESTDispatcherType; + typedef restapi::BasicRequestDispatcher RESTDispatcherType; RESTDispatcherType m_dispatcher(RESTClient::responseHandler); HTTPClient* client = nullptr; #if defined(ENABLE_SSL) @@ -159,7 +159,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin try { // setup HTTP client for authentication payload #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient = new SecureHTTPClient(address, port); if (!sslClient->open()) { delete sslClient; @@ -208,7 +208,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin HTTPPayload httpPayload = HTTPPayload::requestPayload(HTTP_PUT, "/auth"); httpPayload.payload(request); #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->request(httpPayload); } else { #endif // ENABLE_SSL @@ -220,7 +220,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin // wait for response and parse if (wait()) { #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->close(); delete sslClient; } else { @@ -234,7 +234,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } json::object rsp = json::object(); - if (!parseResponseBody(m_response, rsp)) { + if (!parseResponseBody(s_response, rsp)) { return ERRNO_BAD_API_RESPONSE; } @@ -245,7 +245,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } else { #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->close(); delete sslClient; } else { @@ -259,7 +259,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->close(); delete sslClient; } else { @@ -272,7 +272,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin // reset the HTTP client and setup for actual payload request #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient = new SecureHTTPClient(address, port); if (!sslClient->open()) { delete sslClient; @@ -296,7 +296,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin httpPayload.headers.add("X-DVM-Auth-Token", token); httpPayload.payload(payload); #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->request(httpPayload); } else { #endif // ENABLE_SSL @@ -308,7 +308,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin // wait for response and parse if (wait()) { #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->close(); delete sslClient; } else { @@ -322,25 +322,25 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } response = json::object(); - if (!parseResponseBody(m_response, response)) { + if (!parseResponseBody(s_response, response)) { return ERRNO_BAD_API_RESPONSE; } ret = response["status"].get(); - if (m_console) { - fprintf(stdout, "%s\r\n", m_response.content.c_str()); + if (s_console) { + fprintf(stdout, "%s\r\n", s_response.content.c_str()); } else { - if (m_debug) { - if (m_response.content.size() < 4095) { - ::LogDebug(LOG_REST, "REST Response: %s", m_response.content.c_str()); + if (s_debug) { + if (s_response.content.size() < 4095) { + ::LogDebug(LOG_REST, "REST Response: %s", s_response.content.c_str()); } // bryanb: this will cause REST responses >4095 characters to simply not print... } } #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { sslClient->close(); delete sslClient; } else { @@ -353,7 +353,7 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin } catch (std::exception&) { #if defined(ENABLE_SSL) - if (m_enableSSL) { + if (s_enableSSL) { if (sslClient != nullptr) { delete sslClient; } @@ -379,18 +379,18 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin void RESTClient::responseHandler(const HTTPPayload& request, HTTPPayload& reply) { - m_responseAvailable = true; - m_response = request; + s_responseAvailable = true; + s_response = request; } /* Helper to wait for a HTTP response. */ bool RESTClient::wait(const int t) { - m_responseAvailable = false; + s_responseAvailable = false; int timeout = t; - while (!m_responseAvailable && timeout > 0) { + while (!s_responseAvailable && timeout > 0) { timeout--; Thread::sleep(1); } diff --git a/src/remote/RESTClient.h b/src/remote/RESTClient.h index 877064918..025af96e5 100644 --- a/src/remote/RESTClient.h +++ b/src/remote/RESTClient.h @@ -21,8 +21,8 @@ #define __REST_CLIENT_H__ #include "Defines.h" -#include "common/network/json/json.h" -#include "common/network/rest/http/HTTPPayload.h" +#include "common/json/json.h" +#include "common/restapi/http/HTTPPayload.h" #include @@ -104,7 +104,7 @@ class HOST_SW_API RESTClient const std::string endpoint, json::object payload, json::object& response, bool enableSSL, int timeout, bool debug = false); private: - typedef network::rest::http::HTTPPayload HTTPPayload; + typedef restapi::http::HTTPPayload HTTPPayload; /** * @brief HTTP response handler. * @param request HTTP request. @@ -122,13 +122,13 @@ class HOST_SW_API RESTClient uint32_t m_port; std::string m_password; - static bool m_console; + static bool s_console; - static bool m_responseAvailable; - static HTTPPayload m_response; + static bool s_responseAvailable; + static HTTPPayload s_response; - static bool m_enableSSL; - static bool m_debug; + static bool s_enableSSL; + static bool s_debug; }; #endif // __REMOTE_COMMAND_H__ diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index be168f2b4..01c49e797 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -4,12 +4,12 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL * */ #include "remote/RESTClient.h" -#include "host/network/RESTDefines.h" -#include "fne/network/RESTDefines.h" +#include "host/restapi/RESTDefines.h" +#include "fne/restapi/RESTDefines.h" #include "common/Thread.h" #include "common/Log.h" @@ -48,11 +48,14 @@ #define RCD_FNE_PUT_RESETPEER "fne-reset-peer" #define RCD_FNE_PUT_PEER_ACL_ADD "fne-peer-acl-add" #define RCD_FNE_PUT_PEER_ACL_DELETE "fne-peer-acl-del" +#define RCD_FNE_PUT_PEER_RESET_CONN "fne-peer-reset-conn" #define RCD_FNE_SAVE_RID_ACL "fne-rid-commit" #define RCD_FNE_SAVE_TGID_ACL "fne-tgid-commit" #define RCD_FNE_SAVE_PEER_ACL "fne-peer-commit" +#define RCD_FNE_GET_SPANNINGTREE "fne-spanning-tree" + #define RCD_MODE "mdm-mode" #define RCD_MODE_OPT_IDLE "idle" #define RCD_MODE_OPT_LCKOUT "lockout" @@ -215,11 +218,14 @@ void usage(const char* message, const char* arg) reply += " fne-reset-peer Forces the FNE to reset the connection of the given peer ID (Converged FNE only)\r\n"; reply += " fne-peer-acl-add Adds the specified peer ID to the FNE ACL tables (Converged FNE only)\r\n"; reply += " fne-peer-acl-del Removes the specified peer ID to the FNE ACL tables (Converged FNE only)\r\n"; + reply += " fne-peer-reset-conn Forces the FNE to reset a upstream peer connection of the given peer ID (Converged FNE only)\r\n"; reply += "\r\n"; reply += " fne-rid-commit Saves the current RID ACL to permenant storage (Converged FNE only)\r\n"; reply += " fne-tgid-commit Saves the current TGID ACL to permenant storage (Converged FNE only)\r\n"; reply += " fne-peer-commit Saves the current peer ACL to permenant storage (Converged FNE only)\r\n"; reply += "\r\n"; + reply += " fne-spanning-tree Retrieves the current FNE spanning tree (Converged FNE only)\r\n"; + reply += "\r\n"; reply += " mdm-mode Set current mode of host (idle, lockout, dmr, p25, nxdn)\r\n"; reply += " mdm-kill Causes the host to quit\r\n"; reply += " mdm-force-kill Causes the host to quit immediately\r\n"; @@ -893,6 +899,13 @@ int main(int argc, char** argv) retCode = client->send(HTTP_PUT, FNE_PUT_PEER_RESET, req, response); } + else if (rcom == RCD_FNE_PUT_PEER_RESET_CONN && argCnt >= 1U) { + uint32_t peerId = getArgUInt32(args, 0U); + json::object req = json::object(); + req["peerId"].set(peerId); + + retCode = client->send(HTTP_PUT, FNE_PUT_PEER_RESET_CONN, req, response); + } else if (rcom == RCD_FNE_PUT_PEER_ACL_ADD && argCnt >= 1U) { uint32_t peerId = getArgUInt32(args, 0U); json::object req = json::object(); @@ -916,6 +929,9 @@ int main(int argc, char** argv) else if (rcom == RCD_FNE_SAVE_PEER_ACL) { retCode = client->send(HTTP_GET, FNE_GET_PEER_COMMIT, json::object(), response); } + else if (rcom == RCD_FNE_GET_SPANNINGTREE) { + retCode = client->send(HTTP_GET, FNE_GET_SPANNING_TREE, json::object(), response); + } else { args.clear(); LogError(LOG_REST, BAD_CMD_STR " (\"%s\")", rcom.c_str()); diff --git a/src/remote/win32/resource.rc b/src/remote/win32/resource.rc index 1f7e53e24..bd508d504 100644 Binary files a/src/remote/win32/resource.rc and b/src/remote/win32/resource.rc differ diff --git a/src/sysview/AffListWnd.h b/src/sysview/AffListWnd.h index 93a4a580f..6a3cbb815 100644 --- a/src/sysview/AffListWnd.h +++ b/src/sysview/AffListWnd.h @@ -16,7 +16,7 @@ #include "common/lookups/AffiliationLookup.h" #include "common/Log.h" -#include "fne/network/RESTDefines.h" +#include "fne/restapi/RESTDefines.h" #include "remote/RESTClient.h" #include "FDblDialog.h" @@ -102,7 +102,7 @@ class HOST_SW_API AffListWnd final : public FDblDialog { int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword, HTTP_GET, FNE_GET_AFF_LIST, req, rsp, fneSSL, g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "[AFFVIEW] failed to get affiliations for %s:%u", fneRESTAddress.c_str(), fneRESTPort); } else { diff --git a/src/sysview/Defines.h b/src/sysview/Defines.h index 2aaff9655..0ed1a7d08 100644 --- a/src/sysview/Defines.h +++ b/src/sysview/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/sysview/HostWS.cpp b/src/sysview/HostWS.cpp index 7efcaeeed..1925955c7 100644 --- a/src/sysview/HostWS.cpp +++ b/src/sysview/HostWS.cpp @@ -14,7 +14,7 @@ #include "common/StopWatch.h" #include "common/Thread.h" #include "common/Utils.h" -#include "fne/network/RESTDefines.h" +#include "fne/restapi/RESTDefines.h" #include "remote/RESTClient.h" #include "network/PeerNetwork.h" #include "HostWS.h" @@ -260,7 +260,7 @@ int HostWS::run() g_logDisplayLevel = 0U; std::ostringstream logOutput; - __InternalOutputStream(logOutput); + log_internal::SetInternalOutputStream(logOutput); Timer peerListUpdate(1000U, 10U); peerListUpdate.start(); @@ -327,7 +327,7 @@ int HostWS::run() int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword, HTTP_GET, FNE_GET_PEER_QUERY, req, rsp, fneSSL, g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "[AFFVIEW] failed to query peers for %s:%u", fneRESTAddress.c_str(), fneRESTPort); } else { @@ -354,7 +354,7 @@ int HostWS::run() int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword, HTTP_GET, FNE_GET_AFF_LIST, req, rsp, fneSSL, g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "[AFFVIEW] failed to query peers for %s:%u", fneRESTAddress.c_str(), fneRESTPort); } else { @@ -533,7 +533,7 @@ void* HostWS::threadWebSocket(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -542,7 +542,7 @@ void* HostWS::threadWebSocket(void* arg) ws->m_wsServer.start_accept(); ws->m_wsServer.run(); - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/sysview/NodeStatusWnd.h b/src/sysview/NodeStatusWnd.h index ec94486bf..9ac1a9ae1 100644 --- a/src/sysview/NodeStatusWnd.h +++ b/src/sysview/NodeStatusWnd.h @@ -887,7 +887,7 @@ class HOST_SW_API NodeStatusWnd final : public FDblDialog { return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -909,7 +909,7 @@ class HOST_SW_API NodeStatusWnd final : public FDblDialog { } wnd->m_threadStopped = true; - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/sysview/PeerListWnd.h b/src/sysview/PeerListWnd.h index 6629cd62d..83e43bee8 100644 --- a/src/sysview/PeerListWnd.h +++ b/src/sysview/PeerListWnd.h @@ -16,7 +16,7 @@ #include "common/lookups/AffiliationLookup.h" #include "common/Log.h" -#include "fne/network/RESTDefines.h" +#include "fne/restapi/RESTDefines.h" #include "remote/RESTClient.h" #include "FDblDialog.h" @@ -102,7 +102,7 @@ class HOST_SW_API PeerListWnd final : public FDblDialog { int ret = RESTClient::send(fneRESTAddress, fneRESTPort, fnePassword, HTTP_GET, FNE_GET_PEER_QUERY, req, rsp, fneSSL, g_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { + if (ret != restapi::http::HTTPPayload::StatusType::OK) { ::LogError(LOG_HOST, "[AFFVIEW] failed to query peers for %s:%u", fneRESTAddress.c_str(), fneRESTPort); } else { diff --git a/src/sysview/SysViewMain.cpp b/src/sysview/SysViewMain.cpp index 21a3e11c3..1572e21bc 100644 --- a/src/sysview/SysViewMain.cpp +++ b/src/sysview/SysViewMain.cpp @@ -288,7 +288,7 @@ void* threadNetworkPump(void* arg) return nullptr; } - LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[ OK ] %s", threadName.c_str()); #ifdef _GNU_SOURCE ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE @@ -364,7 +364,7 @@ void* threadNetworkPump(void* arg) } RxStatus status; - auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair& x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it == g_dmrStatus.end()) { LogError(LOG_NET, "DMR, tried to end call for non-existent call in progress?, srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); @@ -375,10 +375,10 @@ void* threadNetworkPump(void* arg) uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != g_dmrStatus.end()) { + if (std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair& x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }) != g_dmrStatus.end()) { g_dmrStatus.erase(dstId); - LogMessage(LOG_NET, "DMR, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + LogInfoEx(LOG_NET, "DMR, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); } } @@ -390,7 +390,7 @@ void* threadNetworkPump(void* arg) srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } - auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); + auto it = std::find_if(g_dmrStatus.begin(), g_dmrStatus.end(), [&](StatusMapPair& x) { return (x.second.dstId == dstId && x.second.slotNo == slotNo); }); if (it == g_dmrStatus.end()) { // this is a new call stream RxStatus status = RxStatus(); @@ -400,7 +400,7 @@ void* threadNetworkPump(void* arg) status.slotNo = slotNo; g_dmrStatus[dstId] = status; // this *could* be an issue if a dstId appears on both slots somehow... - LogMessage(LOG_NET, "DMR, Call Start, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, "DMR, Call Start, srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } } @@ -428,7 +428,7 @@ void* threadNetworkPump(void* arg) { lc::csbk::CSBK_BROADCAST* osp = static_cast(csbk.get()); if (osp->getAnncType() == DMRDEF::BroadcastAnncType::ANN_WD_TSCC) { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", dmrData.getSlotNo(), csbk->toString().c_str(), + LogInfoEx(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", dmrData.getSlotNo(), csbk->toString().c_str(), osp->getSystemId(), osp->getLogicalCh1()); // generate a net event for this @@ -445,7 +445,7 @@ void* threadNetworkPump(void* arg) break; default: { - LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u (%s), dstId = %u (%s)", dmrData.getSlotNo(), csbk->toString().c_str(), + LogInfoEx(LOG_NET, "DMR Slot %u, DT_CSBK, %s, srcId = %u (%s), dstId = %u (%s)", dmrData.getSlotNo(), csbk->toString().c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -466,7 +466,7 @@ void* threadNetworkPump(void* arg) } if (g_debug) - LogMessage(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length); + LogInfoEx(LOG_NET, "DMR, slotNo = %u, seqNo = %u, flco = $%02X, srcId = %u, dstId = %u, len = %u", slotNo, seqNo, flco, srcId, dstId, length); } UInt8Array p25Buffer = g_network->readP25(netReadRet, length); @@ -517,10 +517,10 @@ void* threadNetworkPump(void* arg) RxStatus status = g_p25Status[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_p25Status.end()) { + if (std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }) != g_p25Status.end()) { g_p25Status.erase(dstId); - LogMessage(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X, duration = %u", + LogInfoEx(LOG_NET, "P25, Call End, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X, duration = %u", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId, duration / 1000); } } @@ -532,7 +532,7 @@ void* threadNetworkPump(void* arg) srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } - auto it = std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + auto it = std::find_if(g_p25Status.begin(), g_p25Status.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }); if (it == g_p25Status.end()) { // this is a new call stream RxStatus status = RxStatus(); @@ -541,7 +541,7 @@ void* threadNetworkPump(void* arg) status.dstId = dstId; g_p25Status[dstId] = status; - LogMessage(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", + LogInfoEx(LOG_NET, "P25, Call Start, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); } } @@ -551,7 +551,7 @@ void* threadNetworkPump(void* arg) case P25DEF::DUID::TDU: case P25DEF::DUID::TDULC: if (duid == P25DEF::DUID::TDU) { - LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TDU_STR ", srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } else { @@ -560,7 +560,7 @@ void* threadNetworkPump(void* arg) LogWarning(LOG_NET, P25_TDULC_STR ", undecodable TDULC"); } else { - LogMessage(LOG_NET, P25_TDULC_STR ", srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TDULC_STR ", srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } } @@ -585,7 +585,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_GRP_VCH: case P25DEF::TSBKO::IOSP_UU_VCH: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, emerg = %u, encrypt = %u, prio = %u, chNo = %u-%u, srcId = %u (%s), dstId = %u (%s), sysId = $%03X, netId = $%05X", tsbk->toString(true).c_str(), tsbk->getEmergency(), tsbk->getEncrypted(), tsbk->getPriority(), tsbk->getGrpVchId(), tsbk->getGrpVchNo(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), sysId, netId); @@ -617,7 +617,7 @@ void* threadNetworkPump(void* arg) { lc::tsbk::IOSP_UU_ANS* iosp = static_cast(tsbk.get()); if (iosp->getResponse() > 0U) { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, response = $%02X, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), iosp->getResponse(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); @@ -641,7 +641,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_STS_UPDT: { lc::tsbk::IOSP_STS_UPDT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, status = $%02X, srcId = %u (%s)", tsbk->toString(true).c_str(), iosp->getStatus(), srcId, resolveRID(srcId).c_str()); // generate a net event for this @@ -663,7 +663,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_MSG_UPDT: { lc::tsbk::IOSP_MSG_UPDT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, message = $%02X, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), iosp->getMessage(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); @@ -686,7 +686,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_RAD_MON: { //lc::tsbk::IOSP_RAD_MON* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -704,7 +704,7 @@ void* threadNetworkPump(void* arg) break; case P25DEF::TSBKO::IOSP_CALL_ALRT: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -723,7 +723,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_ACK_RSP: { lc::tsbk::IOSP_ACK_RSP* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, serviceType = $%02X, srcId = %u (%s), dstId = %u (%s)", tsbk->toString(true).c_str(), iosp->getAIV(), iosp->getService(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); @@ -746,7 +746,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_EXT_FNCT: { lc::tsbk::IOSP_EXT_FNCT* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, serviceType = $%02X, arg = %u, tgt = %u", tsbk->toString(true).c_str(), iosp->getService(), srcId, dstId); // generate a net event for this @@ -769,7 +769,7 @@ void* threadNetworkPump(void* arg) // non-emergency mode is a TSBKO::OSP_DENY_RSP if (!tsbk->getEmergency()) { lc::tsbk::OSP_DENY_RSP* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u (%s), dstId = %u (%s)", osp->toString().c_str(), osp->getAIV(), osp->getResponse(), osp->getSrcId(), resolveRID(osp->getSrcId()).c_str(), osp->getDstId(), resolveTGID(osp->getDstId()).c_str()); @@ -788,7 +788,7 @@ void* threadNetworkPump(void* arg) g_netDataEvent(netEvent); } } else { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -808,7 +808,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::IOSP_GRP_AFF: { lc::tsbk::OSP_GRP_AFF* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, anncId = %u (%s), srcId = %u (%s), dstId = %u (%s), response = $%02X", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, anncId = %u (%s), srcId = %u (%s), dstId = %u (%s), response = $%02X", tsbk->toString().c_str(), iosp->getAnnounceGroup(), resolveTGID(iosp->getAnnounceGroup()).c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), iosp->getResponse()); @@ -834,7 +834,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::OSP_U_DEREG_ACK: { //lc::tsbk::OSP_U_DEREG_ACK* iosp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s)", + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s)", tsbk->toString(true).c_str(), srcId, resolveRID(srcId).c_str()); // generate a net event for this @@ -850,7 +850,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::OSP_LOC_REG_RSP: { lc::tsbk::OSP_LOC_REG_RSP* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", osp->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", osp->toString().c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -869,7 +869,7 @@ void* threadNetworkPump(void* arg) case P25DEF::TSBKO::OSP_ADJ_STS_BCAST: { lc::tsbk::OSP_ADJ_STS_BCAST* osp = static_cast(tsbk.get()); - LogMessage(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, sysId = $%03X, rfss = $%02X, site = $%02X, chNo = %u-%u, svcClass = $%02X", tsbk->toString().c_str(), osp->getAdjSiteSysId(), osp->getAdjSiteRFSSId(), osp->getAdjSiteId(), osp->getAdjSiteChnId(), osp->getAdjSiteChnNo(), osp->getAdjSiteSvcClass()); // generate a net event for this @@ -893,7 +893,7 @@ void* threadNetworkPump(void* arg) break; default: { - LogMessage(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), + LogInfoEx(LOG_NET, P25_TSDU_STR ", %s, srcId = %u (%s), dstId = %u (%s)", tsbk->toString().c_str(), srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); // generate a net event for this @@ -915,7 +915,7 @@ void* threadNetworkPump(void* arg) } if (g_debug) - LogMessage(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); + LogInfoEx(LOG_NET, "P25, duid = $%02X, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", duid, lco, MFId, srcId, dstId, length); } UInt8Array nxdnBuffer = g_network->readNXDN(netReadRet, length); @@ -950,10 +950,10 @@ void* threadNetworkPump(void* arg) RxStatus status = g_nxdnStatus[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_nxdnStatus.end()) { + if (std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }) != g_nxdnStatus.end()) { g_nxdnStatus.erase(dstId); - LogMessage(LOG_NET, "NXDN, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + LogInfoEx(LOG_NET, "NXDN, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); } } @@ -965,7 +965,7 @@ void* threadNetworkPump(void* arg) srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } - auto it = std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + auto it = std::find_if(g_nxdnStatus.begin(), g_nxdnStatus.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }); if (it == g_nxdnStatus.end()) { // this is a new call stream RxStatus status = RxStatus(); @@ -974,14 +974,14 @@ void* threadNetworkPump(void* arg) status.dstId = dstId; g_nxdnStatus[dstId] = status; - LogMessage(LOG_NET, "NXDN, Call Start, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, "NXDN, Call Start, srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } } } if (g_debug) - LogMessage(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); + LogInfoEx(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length); } UInt8Array analogBuffer = g_network->readAnalog(netReadRet, length); @@ -1003,10 +1003,10 @@ void* threadNetworkPump(void* arg) RxStatus status = g_analogStatus[dstId]; uint64_t duration = hrc::diff(pktTime, status.callStartTime); - if (std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }) != g_analogStatus.end()) { + if (std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }) != g_analogStatus.end()) { g_analogStatus.erase(dstId); - LogMessage(LOG_NET, "Analog, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", + LogInfoEx(LOG_NET, "Analog, Call End, srcId = %u (%s), dstId = %u (%s), duration = %u", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str(), duration / 1000); } } @@ -1018,7 +1018,7 @@ void* threadNetworkPump(void* arg) srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } - auto it = std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair x) { return x.second.dstId == dstId; }); + auto it = std::find_if(g_analogStatus.begin(), g_analogStatus.end(), [&](StatusMapPair& x) { return x.second.dstId == dstId; }); if (it == g_analogStatus.end()) { // this is a new call stream RxStatus status = RxStatus(); @@ -1027,13 +1027,13 @@ void* threadNetworkPump(void* arg) status.dstId = dstId; g_analogStatus[dstId] = status; - LogMessage(LOG_NET, "Analog, Call Start, srcId = %u (%s), dstId = %u (%s)", + LogInfoEx(LOG_NET, "Analog, Call Start, srcId = %u (%s), dstId = %u (%s)", srcId, resolveRID(srcId).c_str(), dstId, resolveTGID(dstId).c_str()); } } if (g_debug) - LogMessage(LOG_NET, "Analog, frameType = $%02X, srcId = %u, dstId = %u, len = %u", frameType, srcId, dstId, length); + LogInfoEx(LOG_NET, "Analog, frameType = $%02X, srcId = %u, dstId = %u, len = %u", frameType, srcId, dstId, length); } } @@ -1041,7 +1041,7 @@ void* threadNetworkPump(void* arg) Thread::sleep(1U); } - LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + LogInfoEx(LOG_HOST, "[STOP] %s", threadName.c_str()); delete th; } diff --git a/src/sysview/SysViewMainWnd.h b/src/sysview/SysViewMainWnd.h index cc420b199..db5aadcea 100644 --- a/src/sysview/SysViewMainWnd.h +++ b/src/sysview/SysViewMainWnd.h @@ -64,7 +64,7 @@ class HOST_SW_API SysViewMainWnd final : public finalcut::FWidget { */ explicit SysViewMainWnd(FWidget* widget = nullptr) : FWidget{widget} { - __InternalOutputStream(m_logWnd); + log_internal::SetInternalOutputStream(m_logWnd); // file menu m_statusMenu.addCallback("clicked", this, [&]() { diff --git a/src/sysview/TransmitWndBase.h b/src/sysview/TransmitWndBase.h index 5c5394d47..98460be78 100644 --- a/src/sysview/TransmitWndBase.h +++ b/src/sysview/TransmitWndBase.h @@ -250,7 +250,7 @@ class HOST_SW_API TransmitWndBase : public FDblDialog { csbk->setSrcId(arg); csbk->setDstId(dstId); - LogMessage(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, "DMR Slot %u, CSBK, %s, op = $%02X, arg = %u, tgt = %u", slot, csbk->toString().c_str(), func, arg, dstId); write_CSBK(slot, csbk.get()); @@ -320,7 +320,7 @@ class HOST_SW_API TransmitWndBase : public FDblDialog { iosp->setMFId(MFG_MOT); } - LogMessage(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, mfId = $%02X, op = $%02X, arg = %u, tgt = %u", iosp->toString().c_str(), iosp->getMFId(), iosp->getExtendedFunction(), iosp->getSrcId(), iosp->getDstId()); write_TSDU(iosp.get()); diff --git a/src/sysview/network/PeerNetwork.cpp b/src/sysview/network/PeerNetwork.cpp index 5235c4a88..a1069f3c7 100644 --- a/src/sysview/network/PeerNetwork.cpp +++ b/src/sysview/network/PeerNetwork.cpp @@ -4,13 +4,13 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ #include "sysview/Defines.h" -#include "common/network/json/json.h" #include "common/p25/dfsi/DFSIDefines.h" #include "common/p25/dfsi/LC.h" +#include "common/json/json.h" #include "common/zlib/zlib.h" #include "common/Utils.h" #include "network/PeerNetwork.h" @@ -26,7 +26,7 @@ using namespace network; // Static Class Members // --------------------------------------------------------------------------- -std::mutex PeerNetwork::m_peerStatusMutex; +std::mutex PeerNetwork::s_peerStatusMutex; // --------------------------------------------------------------------------- // Public Class Members @@ -38,9 +38,9 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) : Network(address, port, localPort, peerId, password, duplex, debug, true, true, true, true, true, true, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup), peerStatus(), - m_peerLink(false), - m_tgidPkt(true, "Peer-Link, TGID List"), - m_ridPkt(true, "Peer-Link, RID List") + m_peerReplica(false), + m_tgidPkt(true, "Peer Replication, TGID List"), + m_ridPkt(true, "Peer Replication, RID List") { assert(!address.empty()); assert(port > 0U); @@ -77,7 +77,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco if (it != g_peerIdentityNameMap.end()) identity = g_peerIdentityNameMap[peerId]; - ::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, identity.c_str(), payload.c_str()); + ::Log(9999U, {nullptr, nullptr, 0U, nullptr}, "%.9u (%8s) %s", peerId, identity.c_str(), payload.c_str()); g_disableTimeDisplay = currState; } break; @@ -89,7 +89,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco std::string payload(rawPayload, rawPayload + (length - 11U)); if (g_debug) - LogMessage(LOG_NET, "Peer Status, peerId = %u", peerId); + LogInfoEx(LOG_NET, "Peer Status, peerId = %u", peerId); // parse JSON body json::value v; @@ -105,7 +105,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco json::object obj = v.get(); uint32_t actualPeerId = obj["peerId"].getDefault(peerId); - std::lock_guard lock(m_peerStatusMutex); + std::lock_guard lock(s_peerStatusMutex); peerStatus[actualPeerId] = obj; } break; @@ -116,10 +116,10 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_FUNC::PEER_LINK: + case NET_FUNC::REPL: { switch (opcode.second) { - case NET_SUBFUNC::PL_TALKGROUP_LIST: + case NET_SUBFUNC::REPL_TALKGROUP_LIST: { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -160,8 +160,8 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco m_tidLookup->filename(filename); m_tidLookup->reload(); - // flag this peer as Peer-Link enabled - m_peerLink = true; + // flag this peer as replica enabled + m_peerReplica = true; // cleanup temporary file ::remove(filename.c_str()); @@ -171,7 +171,7 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; - case NET_SUBFUNC::PL_RID_LIST: + case NET_SUBFUNC::REPL_RID_LIST: { uint32_t decompressedLen = 0U; uint8_t* decompressed = nullptr; @@ -212,8 +212,8 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco m_ridLookup->filename(filename); m_ridLookup->reload(); - // flag this peer as Peer-Link enabled - m_peerLink = true; + // flag this peer as replica enabled + m_peerReplica = true; // cleanup temporary file ::remove(filename.c_str()); @@ -280,6 +280,7 @@ bool PeerNetwork::writeConfig() // Flags bool external = true; config["externalPeer"].set(external); // External Peer Marker + config["masterPeerId"].set(m_peerId); // Master Peer ID bool convPeer = true; config["conventionalPeer"].set(convPeer); // Conventional Peer Marker bool sysView = true; diff --git a/src/sysview/network/PeerNetwork.h b/src/sysview/network/PeerNetwork.h index c4bb5daa7..8b5bef946 100644 --- a/src/sysview/network/PeerNetwork.h +++ b/src/sysview/network/PeerNetwork.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL * */ /** @@ -57,18 +57,18 @@ namespace network bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup); /** - * @brief Flag indicating whether or not SysView has received Peer-Link data transfers. + * @brief Flag indicating whether or not SysView has received peer replication data transfers. */ - bool hasPeerLink() const { return m_peerLink; } + bool hasPeerReplica() const { return m_peerReplica; } /** * @brief Helper to lock the peer status mutex. */ - void lockPeerStatus() { m_peerStatusMutex.lock(); } + void lockPeerStatus() { s_peerStatusMutex.lock(); } /** * @brief Helper to unlock the peer status mutex. */ - void unlockPeerStatus() { m_peerStatusMutex.unlock(); } + void unlockPeerStatus() { s_peerStatusMutex.unlock(); } /** * @brief Map of peer status. @@ -96,8 +96,8 @@ namespace network bool writeConfig() override; private: - static std::mutex m_peerStatusMutex; - bool m_peerLink; + static std::mutex s_peerStatusMutex; + bool m_peerReplica; PacketBuffer m_tgidPkt; PacketBuffer m_ridPkt; diff --git a/src/tged/Defines.h b/src/tged/Defines.h index cc780a48f..0d02f58f9 100644 --- a/src/tged/Defines.h +++ b/src/tged/Defines.h @@ -20,6 +20,7 @@ #define __DEFINES_H__ #include "common/Defines.h" +#include "common/GitHash.h" // --------------------------------------------------------------------------- // Constants diff --git a/src/tged/TGEdMain.cpp b/src/tged/TGEdMain.cpp index da216e836..d8fc5a089 100644 --- a/src/tged/TGEdMain.cpp +++ b/src/tged/TGEdMain.cpp @@ -210,7 +210,7 @@ int main(int argc, char** argv) g_tidLookups = new TalkgroupRulesLookup(g_iniFile, 0U, false); g_tidLookups->read(); - LogMessage(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str()); + LogInfoEx(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str()); // show and start the application wnd.show(); diff --git a/src/tged/TGEdMainWnd.h b/src/tged/TGEdMainWnd.h index 62b2ba448..43388f4af 100644 --- a/src/tged/TGEdMainWnd.h +++ b/src/tged/TGEdMainWnd.h @@ -61,7 +61,7 @@ class HOST_SW_API TGEdMainWnd final : public finalcut::FWidget { */ explicit TGEdMainWnd(FWidget* widget = nullptr) : FWidget{widget} { - __InternalOutputStream(m_logWnd); + log_internal::SetInternalOutputStream(m_logWnd); // file menu m_fileMenuSeparator1.setSeparator(); @@ -74,7 +74,7 @@ class HOST_SW_API TGEdMainWnd final : public finalcut::FWidget { m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this); m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this); - m_keyF5.addCallback("activate", this, [&]() { g_tidLookups->reload(); m_wnd->loadListView(); LogMessage(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str()); }); + m_keyF5.addCallback("activate", this, [&]() { g_tidLookups->reload(); m_wnd->loadListView(); LogInfoEx(LOG_HOST, "Loaded talkgroup rules file: %s", g_iniFile.c_str()); }); m_backupOnSave.setChecked(); @@ -138,7 +138,7 @@ class HOST_SW_API TGEdMainWnd final : public finalcut::FWidget { { if (m_backupOnSave.isChecked()) { std::string bakFile = g_iniFile + ".bak"; - LogMessage(LOG_HOST, "Backing up existing file %s to %s", g_iniFile.c_str(), bakFile.c_str()); + LogInfoEx(LOG_HOST, "Backing up existing file %s to %s", g_iniFile.c_str(), bakFile.c_str()); copyFile(g_iniFile.c_str(), bakFile.c_str()); } diff --git a/src/tged/TGEditPeerListWnd.h b/src/tged/TGEditPeerListWnd.h index 6b8314b42..a4c1ad46f 100644 --- a/src/tged/TGEditPeerListWnd.h +++ b/src/tged/TGEditPeerListWnd.h @@ -173,7 +173,7 @@ class HOST_SW_API TGEditPeerListWnd final : public CloseWndBase { m_entry.addCallback("return-pressed", [&]() { size_t curItem = m_listBox.currentItem(); auto item = m_listBox.getItem(curItem); - LogMessage(LOG_HOST, "Updating %s peer ID %s to %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_entry.getText().c_str(), + LogInfoEx(LOG_HOST, "Updating %s peer ID %s to %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_entry.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); item.setText(m_entry.getText()); @@ -219,7 +219,7 @@ class HOST_SW_API TGEditPeerListWnd final : public CloseWndBase { */ void addEntry() { - LogMessage(LOG_HOST, "Adding %s peer ID %s from TG %s (%u)", m_title.c_str(), m_entry.getText().c_str(), + LogInfoEx(LOG_HOST, "Adding %s peer ID %s from TG %s (%u)", m_title.c_str(), m_entry.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); if (m_entry.getText() == "") { @@ -249,7 +249,7 @@ class HOST_SW_API TGEditPeerListWnd final : public CloseWndBase { for (std::vector::iterator it = peerList.begin(); it != peerList.end(); it++) { auto entry = *it; if (entry == peerId) { - LogMessage(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), + LogInfoEx(LOG_HOST, "Removing %s peer ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); peerList.erase(it); break; @@ -301,7 +301,7 @@ class HOST_SW_API TGEditPeerListWnd final : public CloseWndBase { } for (auto entry : peerList) { - LogMessage(LOG_HOST, "%s peer ID %u for TG %s (%u)", m_title.c_str(), entry, + LogInfoEx(LOG_HOST, "%s peer ID %u for TG %s (%u)", m_title.c_str(), entry, m_rule.name().c_str(), m_rule.source().tgId()); } diff --git a/src/tged/TGEditRIDListWnd.h b/src/tged/TGEditRIDListWnd.h index 1bbc94107..871854222 100644 --- a/src/tged/TGEditRIDListWnd.h +++ b/src/tged/TGEditRIDListWnd.h @@ -173,7 +173,7 @@ class HOST_SW_API TGEditRIDListWnd final : public CloseWndBase { m_entry.addCallback("return-pressed", [&]() { size_t curItem = m_listBox.currentItem(); auto item = m_listBox.getItem(curItem); - LogMessage(LOG_HOST, "Updating %s radio ID %s to %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_entry.getText().c_str(), + LogInfoEx(LOG_HOST, "Updating %s radio ID %s to %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_entry.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); item.setText(m_entry.getText()); @@ -238,7 +238,7 @@ class HOST_SW_API TGEditRIDListWnd final : public CloseWndBase { size_t curItem = m_listBox.currentItem(); auto item = m_listBox.getItem(curItem); - LogMessage(LOG_HOST, "Removing %s radio ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), + LogInfoEx(LOG_HOST, "Removing %s radio ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); m_listBox.remove(curItem); @@ -288,7 +288,7 @@ class HOST_SW_API TGEditRIDListWnd final : public CloseWndBase { auto item = m_listBox.getItem(i + 1U); if (item.getText() != "") { uint32_t peerId = ::atoi(item.getText().c_str()); - LogMessage(LOG_HOST, "%s radio ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), + LogInfoEx(LOG_HOST, "%s radio ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_rule.name().c_str(), m_rule.source().tgId()); ridList.push_back(peerId); } diff --git a/src/tged/TGEditWnd.h b/src/tged/TGEditWnd.h index 398c60efa..d8e133584 100644 --- a/src/tged/TGEditWnd.h +++ b/src/tged/TGEditWnd.h @@ -301,7 +301,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto config = m_rule.config(); config.inclusion(wnd.peerList); m_rule.config(config); - LogMessage(LOG_HOST, "Updated %s (%u) peer inclusion list, %u inclusions.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().inclusionSize()); + LogInfoEx(LOG_HOST, "Updated %s (%u) peer inclusion list, %u inclusions.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().inclusionSize()); }); m_exclusionList.setGeometry(FPoint(20, 10), FSize(16, 1)); @@ -312,7 +312,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto config = m_rule.config(); config.exclusion(wnd.peerList); m_rule.config(config); - LogMessage(LOG_HOST, "Updated %s (%u) peer exclusion list, %u exclusions.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().exclusionSize()); + LogInfoEx(LOG_HOST, "Updated %s (%u) peer exclusion list, %u exclusions.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().exclusionSize()); }); m_alwaysList.setGeometry(FPoint(2, 12), FSize(16, 1)); @@ -323,7 +323,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto config = m_rule.config(); config.alwaysSend(wnd.peerList); m_rule.config(config); - LogMessage(LOG_HOST, "Updated %s (%u) peer always receiving list, %u always.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().alwaysSendSize()); + LogInfoEx(LOG_HOST, "Updated %s (%u) peer always receiving list, %u always.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().alwaysSendSize()); }); m_preferredList.setGeometry(FPoint(20, 12), FSize(16, 1)); @@ -334,7 +334,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto config = m_rule.config(); config.preferred(wnd.peerList); m_rule.config(config); - LogMessage(LOG_HOST, "Updated %s (%u) peer preference list, %u preferred.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().preferredSize()); + LogInfoEx(LOG_HOST, "Updated %s (%u) peer preference list, %u preferred.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().preferredSize()); }); m_rewriteList.setGeometry(FPoint(2, 14), FSize(16, 1)); @@ -351,7 +351,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto config = m_rule.config(); config.permittedRIDs(wnd.ridList); m_rule.config(config); - LogMessage(LOG_HOST, "Updated %s (%u) permitted radio list, %u permitted.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().permittedRIDsSize()); + LogInfoEx(LOG_HOST, "Updated %s (%u) permitted radio list, %u permitted.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().permittedRIDsSize()); }); CloseWndBase::initControls(); @@ -444,12 +444,11 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { // update TG auto groupVoice = g_tidLookups->groupVoice(); auto it = std::find_if(groupVoice.begin(), groupVoice.end(), - [&](lookups::TalkgroupRuleGroupVoice x) - { + [&](lookups::TalkgroupRuleGroupVoice& x) { return x.source().tgId() == m_origTgId && x.source().tgSlot() == m_origTgSlot; }); if (it != groupVoice.end()) { - LogMessage(LOG_HOST, "Updating TG %s (%u) to %s (%u)", it->name().c_str(), it->source().tgId(), m_rule.name().c_str(), m_rule.source().tgId()); + LogInfoEx(LOG_HOST, "Updating TG %s (%u) to %s (%u)", it->name().c_str(), it->source().tgId(), m_rule.name().c_str(), m_rule.source().tgId()); g_tidLookups->eraseEntry(m_origTgId, m_origTgSlot); g_tidLookups->addEntry(m_rule); @@ -464,8 +463,7 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { auto groupVoice = g_tidLookups->groupVoice(); auto it = std::find_if(groupVoice.begin(), groupVoice.end(), - [&](lookups::TalkgroupRuleGroupVoice x) - { + [&](lookups::TalkgroupRuleGroupVoice& x) { return x.source().tgId() == m_rule.source().tgId() && x.source().tgSlot() == m_rule.source().tgSlot(); }); if (it != groupVoice.end()) { @@ -478,9 +476,9 @@ class HOST_SW_API TGEditWnd final : public CloseWndBase { // add new TG if (m_saveCopy.isChecked()) { - LogMessage(LOG_HOST, "Copying TG. Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId()); + LogInfoEx(LOG_HOST, "Copying TG. Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId()); } else { - LogMessage(LOG_HOST, "Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId()); + LogInfoEx(LOG_HOST, "Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId()); } g_tidLookups->addEntry(m_rule); diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 42ec8184a..92696236d 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -99,51 +99,53 @@ class HOST_SW_API TGListWnd final : public FDblDialog { m_selected = TalkgroupRuleGroupVoice(); m_selectedTgId = 0U; - auto entry = g_tidLookups->groupVoice()[0U]; - m_selected = entry; - - // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position - /* - * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. - */ - int firstScrollLinePos = 0; - if (m_listView.getCount() > 0) { - firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); - } + if (g_tidLookups->groupVoice().size() > 0U) { + auto entry = g_tidLookups->groupVoice()[0U]; + m_selected = entry; + + // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + int firstScrollLinePos = 0; + if (m_listView.getCount() > 0) { + firstScrollLinePos = (m_listView.*RTTIResult::ptr).getPosition(); + } - m_listView.clear(); - for (auto entry : g_tidLookups->groupVoice()) { - // pad TGs properly - std::ostringstream oss; - oss << std::setw(5) << std::setfill('0') << entry.source().tgId(); - - // build list view entry - const std::array columns = { - entry.name(), entry.nameAlias(), oss.str(), std::to_string(entry.source().tgSlot()), - (entry.config().active()) ? "X" : "", - (entry.config().affiliated()) ? "X" : "", - std::to_string(entry.config().inclusionSize()), - std::to_string(entry.config().exclusionSize()), - std::to_string(entry.config().alwaysSendSize()), - std::to_string(entry.config().permittedRIDsSize()) - }; - - const finalcut::FStringList line(columns.cbegin(), columns.cend()); - m_listView.insert(line); - } + m_listView.clear(); + for (auto entry : g_tidLookups->groupVoice()) { + // pad TGs properly + std::ostringstream oss; + oss << std::setw(5) << std::setfill('0') << entry.source().tgId(); + + // build list view entry + const std::array columns = { + entry.name(), entry.nameAlias(), oss.str(), std::to_string(entry.source().tgSlot()), + (entry.config().active()) ? "X" : "", + (entry.config().affiliated()) ? "X" : "", + std::to_string(entry.config().inclusionSize()), + std::to_string(entry.config().exclusionSize()), + std::to_string(entry.config().alwaysSendSize()), + std::to_string(entry.config().permittedRIDsSize()) + }; + + const finalcut::FStringList line(columns.cbegin(), columns.cend()); + m_listView.insert(line); + } - // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position - /* - * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. - */ - if ((size_t)firstScrollLinePos > m_listView.getCount()) - firstScrollLinePos = 0; - if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { - (m_listView.*RTTIResult::ptr)(firstScrollLinePos); - (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + // bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position + /* + * This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence. + */ + if ((size_t)firstScrollLinePos > m_listView.getCount()) + firstScrollLinePos = 0; + if (firstScrollLinePos > 0 && m_listView.getCount() > 0) { + (m_listView.*RTTIResult::ptr)(firstScrollLinePos); + (m_listView.*RTTIResult::ptr)->setValue(firstScrollLinePos); + } } - // generate dialog title + // generate dialog title uint32_t len = g_tidLookups->groupVoice().size(); std::stringstream ss; ss << "Talkgroup List (" << len << " TGs)"; @@ -177,7 +179,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { std::vector inclusions = config.inclusion(); auto it = std::find_if(inclusions.begin(), inclusions.end(), [&](uint32_t x) { return x == wnd.peerId; }); if (it == inclusions.end()) { - LogMessage(LOG_HOST, "Updating TG %s (%u) adding inclusion peer %u", rule.name().c_str(), rule.source().tgId(), peerId); + LogInfoEx(LOG_HOST, "Updating TG %s (%u) adding inclusion peer %u", rule.name().c_str(), rule.source().tgId(), peerId); inclusions.push_back(peerId); } @@ -216,7 +218,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { std::vector inclusions = config.inclusion(); auto it = std::find_if(inclusions.begin(), inclusions.end(), [&](uint32_t x) { return x == wnd.peerId; }); if (it != inclusions.end()) { - LogMessage(LOG_HOST, "Updating TG %s (%u) removing inclusion peer %u", rule.name().c_str(), rule.source().tgId(), peerId); + LogInfoEx(LOG_HOST, "Updating TG %s (%u) removing inclusion peer %u", rule.name().c_str(), rule.source().tgId(), peerId); inclusions.erase(it); } @@ -259,7 +261,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { std::vector alwaysSend = config.alwaysSend(); auto it = std::find_if(alwaysSend.begin(), alwaysSend.end(), [&](uint32_t x) { return x == wnd.peerId; }); if (it == alwaysSend.end()) { - LogMessage(LOG_HOST, "Updating TG %s (%u) adding always peer %u", rule.name().c_str(), rule.source().tgId(), peerId); + LogInfoEx(LOG_HOST, "Updating TG %s (%u) adding always peer %u", rule.name().c_str(), rule.source().tgId(), peerId); alwaysSend.push_back(peerId); } @@ -302,7 +304,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { std::vector alwaysSend = config.alwaysSend(); auto it = std::find_if(alwaysSend.begin(), alwaysSend.end(), [&](uint32_t x) { return x == wnd.peerId; }); if (it != alwaysSend.end()) { - LogMessage(LOG_HOST, "Updating TG %s (%u) removing always peer %u", rule.name().c_str(), rule.source().tgId(), peerId); + LogInfoEx(LOG_HOST, "Updating TG %s (%u) removing always peer %u", rule.name().c_str(), rule.source().tgId(), peerId); alwaysSend.erase(it); } @@ -414,7 +416,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { m_selected = entry; /* if (m_selectedTgId != tgid) - LogMessage(LOG_HOST, "Selected TG %s (%u) for editing", m_selected.name().c_str(), m_selected.source().tgId()); + LogInfoEx(LOG_HOST, "Selected TG %s (%u) for editing", m_selected.name().c_str(), m_selected.source().tgId()); */ m_selectedTgId = tgid; @@ -482,7 +484,7 @@ class HOST_SW_API TGListWnd final : public FDblDialog { if (m_selected.isInvalid()) return; - LogMessage(LOG_HOST, "Deleting TG %s (%u)", m_selected.name().c_str(), m_selected.source().tgId()); + LogInfoEx(LOG_HOST, "Deleting TG %s (%u)", m_selected.name().c_str(), m_selected.source().tgId()); g_tidLookups->eraseEntry(m_selected.source().tgId(), m_selected.source().tgSlot()); // bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position diff --git a/src/vocoder/MBEDecoder.h b/src/vocoder/MBEDecoder.h index 1aef85c65..e55eedefb 100644 --- a/src/vocoder/MBEDecoder.h +++ b/src/vocoder/MBEDecoder.h @@ -63,8 +63,8 @@ namespace vocoder * @brief Vocoder Decoding Mode */ enum MBE_DECODER_MODE { - DECODE_DMR_AMBE, //! DMR AMBE - DECODE_88BIT_IMBE //! 88-bit IMBE (P25) + DECODE_DMR_AMBE, //!< DMR AMBE + DECODE_88BIT_IMBE //!< 88-bit IMBE (P25) }; // --------------------------------------------------------------------------- diff --git a/src/vocoder/MBEEncoder.cpp b/src/vocoder/MBEEncoder.cpp index 3bf86e0bf..de5152ff8 100644 --- a/src/vocoder/MBEEncoder.cpp +++ b/src/vocoder/MBEEncoder.cpp @@ -593,7 +593,6 @@ void MBEEncoder::encodeBits(uint8_t* bits, uint8_t* codeword) assert(bits != nullptr); assert(codeword != nullptr); - int32_t errs = 0; float samples[160U]; ::memset(samples, 0x00U, 160U * sizeof(float)); @@ -702,7 +701,7 @@ void MBEEncoder::encode(int16_t* samples, uint8_t* codeword) uint8_t rawAmbe[9U]; ::memset(rawAmbe, 0x00U, 9U); - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 7; ++i) { for (int j = 0; j < 8; ++j) { rawAmbe[i] |= (bits[(i * 8) + j] << (7 - j)); } diff --git a/src/vocoder/MBEEncoder.h b/src/vocoder/MBEEncoder.h index c4fbb061d..c1612b0e2 100644 --- a/src/vocoder/MBEEncoder.h +++ b/src/vocoder/MBEEncoder.h @@ -33,8 +33,8 @@ namespace vocoder * @brief Vocoder Encoding Mode */ enum MBE_ENCODER_MODE { - ENCODE_DMR_AMBE, //! DMR AMBE - ENCODE_88BIT_IMBE, //! 88-bit IMBE (P25) + ENCODE_DMR_AMBE, //!< DMR AMBE + ENCODE_88BIT_IMBE, //!< 88-bit IMBE (P25) }; // --------------------------------------------------------------------------- diff --git a/tests/crypto/AES_Crypto_Test.cpp b/tests/crypto/AES_Crypto_Test.cpp index 4a5a8393c..b02c45a1d 100644 --- a/tests/crypto/AES_Crypto_Test.cpp +++ b/tests/crypto/AES_Crypto_Test.cpp @@ -56,7 +56,7 @@ TEST_CASE("AES", "[Crypto Test]") { for (uint32_t i = 0; i < 48U; i++) { if (decrypted[i] != message[i]) { - ::LogDebug("T", "AES_Crypto_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "AES_Crypto_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/crypto/AES_LLA_AM1_Test.cpp b/tests/crypto/AES_LLA_AM1_Test.cpp index 4195ca3fa..83c9de08c 100644 --- a/tests/crypto/AES_LLA_AM1_Test.cpp +++ b/tests/crypto/AES_LLA_AM1_Test.cpp @@ -18,7 +18,7 @@ using namespace crypto; #include #include -TEST_CASE("AES", "[LLA AM1 Test]") { +TEST_CASE("AES_LLA", "[LLA AM1 Test]") { SECTION("LLA_AM1_Test") { bool failed = false; @@ -70,7 +70,7 @@ TEST_CASE("AES", "[LLA AM1 Test]") { for (uint32_t i = 0; i < 16U; i++) { if (KS[i] != resultKS[i]) { - ::LogDebug("T", "LLA_AM1_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "LLA_AM1_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/crypto/AES_LLA_AM2_Test.cpp b/tests/crypto/AES_LLA_AM2_Test.cpp index 45c810bf5..e336e151e 100644 --- a/tests/crypto/AES_LLA_AM2_Test.cpp +++ b/tests/crypto/AES_LLA_AM2_Test.cpp @@ -18,7 +18,7 @@ using namespace crypto; #include #include -TEST_CASE("AES", "[LLA AM2 Test]") { +TEST_CASE("AES_LLA", "[LLA AM2 Test]") { SECTION("LLA_AM2_Test") { bool failed = false; @@ -72,7 +72,7 @@ TEST_CASE("AES", "[LLA AM2 Test]") { for (uint32_t i = 0; i < 4U; i++) { if (RES1[i] != resultRES1[i]) { - ::LogDebug("T", "LLA_AM2_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "LLA_AM2_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/crypto/AES_LLA_AM3_Test.cpp b/tests/crypto/AES_LLA_AM3_Test.cpp index c6e465b44..15aae7cab 100644 --- a/tests/crypto/AES_LLA_AM3_Test.cpp +++ b/tests/crypto/AES_LLA_AM3_Test.cpp @@ -18,7 +18,7 @@ using namespace crypto; #include #include -TEST_CASE("AES", "[LLA AM3 Test]") { +TEST_CASE("AES_LLA", "[LLA AM3 Test]") { SECTION("LLA_AM3_Test") { bool failed = false; @@ -77,7 +77,7 @@ TEST_CASE("AES", "[LLA AM3 Test]") { for (uint32_t i = 0; i < 16U; i++) { if (KS[i] != resultKS[i]) { - ::LogDebug("T", "LLA_AM3_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "LLA_AM3_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/crypto/AES_LLA_AM4_Test.cpp b/tests/crypto/AES_LLA_AM4_Test.cpp index 0428a2fc6..17b45a818 100644 --- a/tests/crypto/AES_LLA_AM4_Test.cpp +++ b/tests/crypto/AES_LLA_AM4_Test.cpp @@ -18,7 +18,7 @@ using namespace crypto; #include #include -TEST_CASE("AES", "[LLA AM4 Test]") { +TEST_CASE("AES_LLA", "[LLA AM4 Test]") { SECTION("LLA_AM4_Test") { bool failed = false; @@ -72,7 +72,7 @@ TEST_CASE("AES", "[LLA AM4 Test]") { for (uint32_t i = 0; i < 4U; i++) { if (RES2[i] != resultRES2[i]) { - ::LogDebug("T", "LLA_AM4_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "LLA_AM4_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/crypto/P25_KEK_Crypto_Test.cpp b/tests/crypto/P25_KEK_Crypto_Test.cpp new file mode 100644 index 000000000..40e42a790 --- /dev/null +++ b/tests/crypto/P25_KEK_Crypto_Test.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/Crypto.h" +#include "common/Log.h" +#include "common/Utils.h" + +#include +#include +#include + +TEST_CASE("AES_KEK", "[KEK Crypto Test]") { + SECTION("P25_AES_KEK_Crypto_Test") { + bool failed = false; + + INFO("P25 AES256 KEK Crypto Test"); + + srand((unsigned int)time(NULL)); + + // example data taken from TIA-102.AACA-C-2023 Section 14.3.3 + + // Encrypted Key Frame + uint8_t testWrappedKeyFrame[40U] = + { + 0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, + 0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, + 0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80 + }; + + // key (K) + uint8_t K[32U] = + { + 0x49, 0x40, 0x02, 0xBF, 0x16, 0x31, 0x32, 0xA4, 0x21, 0xFB, 0xEF, 0x11, 0x7F, 0x98, 0x5A, 0x0C, + 0xAA, 0xDD, 0xC2, 0x50, 0xA4, 0xC2, 0x19, 0x47, 0xD5, 0x93, 0xE6, 0xC0, 0x67, 0xDE, 0x40, 0x2C + }; + + // message + uint8_t message[32U] = + { + 0x2A, 0x19, 0x38, 0xCD, 0x0B, 0x6B, 0x6B, 0xD0, 0xB7, 0x74, 0x56, 0x92, 0xFE, 0x19, 0x14, 0xF0, + 0x38, 0x76, 0x61, 0x2F, 0xC2, 0x9D, 0x57, 0x77, 0x89, 0xA6, 0x2F, 0x65, 0xFA, 0x05, 0xEF, 0x83 + }; + + Utils::dump(2U, "KEK_Crypto_Test, Key", K, 32); + Utils::dump(2U, "KEK_Crypto_Test, Message", message, 32); + + p25::crypto::P25Crypto crypto; + + UInt8Array wrappedKey = crypto.cryptAES_TEK(K, message, 32U); + + Utils::dump(2U, "KEK_Crypto_Test, Wrapped", wrappedKey.get(), 40); + + for (uint32_t i = 0; i < 40U; i++) { + if (wrappedKey[i] != testWrappedKeyFrame[i]) { + ::LogDebug("T", "P25_AES_KEK_Crypto_Test, WRAPPED INVALID AT IDX %d", i); + failed = true; + } + } + + UInt8Array unwrappedKey = crypto.decryptAES_TEK(K, wrappedKey.get(), 40U); + + Utils::dump(2U, "KEK_Crypto_Test, Unwrapped", unwrappedKey.get(), 40); + + for (uint32_t i = 0; i < 32U; i++) { + if (unwrappedKey[i] != message[i]) { + ::LogError("T", "P25_AES_KEK_Crypto_Test, UNWRAPPED INVALID AT IDX %d", i); + failed = true; + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/crypto/P25_MAC_CBC_Test.cpp b/tests/crypto/P25_MAC_CBC_Test.cpp new file mode 100644 index 000000000..66ba252e1 --- /dev/null +++ b/tests/crypto/P25_MAC_CBC_Test.cpp @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/Crypto.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25::defines; + +#include +#include +#include + +TEST_CASE("AES_MAC_CBC", "[AES256 MAC CBC-MAC Test]") { + SECTION("P25_MAC_CBC_Crypto_Test") { + bool failed = false; + + INFO("P25 AES256 MAC CBC-MAC Test"); + + srand((unsigned int)time(NULL)); + + // example data taken from TIA-102.AACA-C-2023 Section 14.3.4 + + // MAC TEK + uint8_t macTek[] = + { + 0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1, + 0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F + }; + + // expected CBC key + uint8_t expectedCBC[] = + { + 0x09, 0xE7, 0x11, 0x7B, 0x4E, 0x42, 0x06, 0xDE, 0xD3, 0x66, 0xEA, 0x5D, 0x69, 0x33, 0x01, 0xCA, + 0x83, 0x21, 0xBC, 0xC2, 0x0F, 0xA5, 0x05, 0xDF, 0x12, 0x67, 0xDC, 0x2A, 0xE4, 0x58, 0xA0, 0x57 + }; + + // data block + uint8_t dataBlock[] = + { + 0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC, + 0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35, + 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56, + 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23, + 0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40 + }; + + uint8_t expectedMAC[8U]; + + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, TEK", macTek, 32U); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, DataBlock", dataBlock, 80U); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected CBC-MAC Key", expectedCBC, 32U); + + uint16_t fullLength = 0U; + uint16_t messageLength = GET_UINT16(dataBlock, 1U); + fullLength = messageLength + 3U; + bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U; + uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U; + + ::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType); + + switch (macType) { + case KMM_MAC::DES_MAC: + { + uint8_t macLength = 4U; + ::memset(expectedMAC, 0x00U, macLength); + + uint8_t macAlgId = dataBlock[fullLength - 4U]; + uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U); + uint8_t macFormat = dataBlock[fullLength - 1U]; + + ::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength); + + ::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength); + } + break; + + case KMM_MAC::ENH_MAC: + { + uint8_t macLength = 8U; + ::memset(expectedMAC, 0x00U, macLength); + + uint8_t macAlgId = dataBlock[fullLength - 4U]; + uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U); + uint8_t macFormat = dataBlock[fullLength - 1U]; + + ::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength); + + ::LogInfoEx("T", "P25_MAC_CBC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, Expected MAC", expectedMAC, macLength); + } + break; + + case KMM_MAC::NO_MAC: + break; + + default: + ::LogError(LOG_P25, "P25_MAC_CBC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType); + break; + } + + p25::crypto::P25Crypto crypto; + + UInt8Array macKey = crypto.cryptAES_KMM_CBC_KDF(macTek, dataBlock, fullLength); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, CBC MAC Key", macKey.get(), 32U); + + for (uint32_t i = 0; i < 32U; i++) { + if (macKey[i] != expectedCBC[i]) { + ::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + UInt8Array mac = crypto.cryptAES_KMM_CBC(macKey.get(), dataBlock, fullLength); + Utils::dump(2U, "P25_MAC_CBC_Crypto_Test, MAC", mac.get(), 8U); + + for (uint32_t i = 0; i < 8U; i++) { + if (mac[i] != expectedMAC[i]) { + ::LogError("T", "P25_MAC_CBC_Crypto_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/crypto/P25_MAC_CMAC_Test.cpp b/tests/crypto/P25_MAC_CMAC_Test.cpp new file mode 100644 index 000000000..5173251ce --- /dev/null +++ b/tests/crypto/P25_MAC_CMAC_Test.cpp @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/Crypto.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25::defines; + +#include +#include +#include + +TEST_CASE("AES_MAC_CMAC", "[AES256 MAC CMAC Test]") { + SECTION("P25_MAC_CMAC_Crypto_Test") { + bool failed = false; + + INFO("P25 AES256 MAC CMAC Test"); + + srand((unsigned int)time(NULL)); + + // example data taken from TIA-102.AACA-C-2023 Section 14.3.5.1 + + // MAC TEK + uint8_t macTek[] = + { + 0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1, + 0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F + }; + + // expected CMAC key + uint8_t expectedCMAC[] = + { + 0x5F, 0xB2, 0x91, 0xD0, 0x9E, 0xE3, 0x99, 0x1E, 0x13, 0x1A, 0x04, 0xB0, 0xE3, 0xA0, 0xBF, 0x58, + 0xB4, 0xA1, 0xCE, 0x46, 0x10, 0x48, 0xEB, 0x14, 0xB4, 0x97, 0xAE, 0x95, 0x22, 0xD0, 0x0D, 0x31 + }; + + // data block + uint8_t dataBlock[] = + { + 0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC, + 0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35, + 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56, + 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23, + 0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41 + }; + + uint8_t expectedMAC[8U]; + + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, TEK", macTek, 32U); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, DataBlock", dataBlock, 80U); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected CMAC Key", expectedCMAC, 32U); + + uint16_t fullLength = 0U; + uint16_t messageLength = GET_UINT16(dataBlock, 1U); + fullLength = messageLength + 3U; + bool hasMN = ((dataBlock[3U] >> 4U) & 0x03U) == 0x02U; + uint8_t macType = (dataBlock[3U] >> 2U) & 0x03U; + + ::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, messageLength = %u, hasMN = %u, macType = $%02X", messageLength, hasMN, macType); + + switch (macType) { + case KMM_MAC::DES_MAC: + { + uint8_t macLength = 4U; + ::memset(expectedMAC, 0x00U, macLength); + + uint8_t macAlgId = dataBlock[fullLength - 4U]; + uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U); + uint8_t macFormat = dataBlock[fullLength - 1U]; + + ::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength); + + ::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength); + } + break; + + case KMM_MAC::ENH_MAC: + { + uint8_t macLength = 8U; + ::memset(expectedMAC, 0x00U, macLength); + + uint8_t macAlgId = dataBlock[fullLength - 4U]; + uint16_t macKId = GET_UINT16(dataBlock, fullLength - 3U); + uint8_t macFormat = dataBlock[fullLength - 1U]; + + ::memcpy(expectedMAC, dataBlock + fullLength - (macLength + 5U), macLength); + + ::LogInfoEx("T", "P25_MAC_CMAC_Crypto_Test, macAlgId = $%02X, macKId = $%04X, macFormat = $%02X", macAlgId, macKId, macFormat); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, Expected MAC", expectedMAC, macLength); + } + break; + + case KMM_MAC::NO_MAC: + break; + + default: + ::LogError(LOG_P25, "P25_MAC_CMAC_Crypto_Test, unknown KMM MAC inventory type value, macType = $%02X", macType); + break; + } + + p25::crypto::P25Crypto crypto; + + UInt8Array macKey = crypto.cryptAES_KMM_CMAC_KDF(macTek, dataBlock, fullLength, true); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, CMAC MAC Key", macKey.get(), 32U); + + for (uint32_t i = 0; i < 32U; i++) { + if (macKey[i] != expectedCMAC[i]) { + ::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + UInt8Array mac = crypto.cryptAES_KMM_CMAC(expectedCMAC/* macKey.get()*/, dataBlock, fullLength); + Utils::dump(2U, "P25_MAC_CMAC_Crypto_Test, MAC", mac.get(), 8U); + + for (uint32_t i = 0; i < 8U; i++) { + if (mac[i] != expectedMAC[i]) { + ::LogError("T", "P25_MAC_CMAC_Crypto_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/crypto/RC4_Crypto_Test.cpp b/tests/crypto/RC4_Crypto_Test.cpp index 2208f5c62..2df39bee3 100644 --- a/tests/crypto/RC4_Crypto_Test.cpp +++ b/tests/crypto/RC4_Crypto_Test.cpp @@ -38,7 +38,7 @@ TEST_CASE("RC4", "[Crypto Test]") { { 0x90, 0x56, 0x00, 0x00, 0x2D, 0x75, 0xE6, 0x8D, 0x00, 0x89, 0x69, 0xCF, 0x00, 0xFE, 0x00, 0x04, 0x4F, 0xC7, 0x60, 0xFF, 0x30, 0x3E, 0x2B, 0xAD, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x08, - 0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x52, 0x50, 0x54, 0x4C, 0x00, 0x89, 0x69, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // perform crypto @@ -54,7 +54,7 @@ TEST_CASE("RC4", "[Crypto Test]") { for (uint32_t i = 0; i < 48U; i++) { if (decrypted[i] != message[i]) { - ::LogDebug("T", "RC4_Crypto_Test, INVALID AT IDX %d\n", i); + ::LogError("T", "RC4_Crypto_Test, INVALID AT IDX %d", i); failed = true; } } diff --git a/tests/edac/CRC_12_Test.cpp b/tests/edac/CRC_12_Test.cpp index c1c2fe7bf..8458d2799 100644 --- a/tests/edac/CRC_12_Test.cpp +++ b/tests/edac/CRC_12_Test.cpp @@ -37,13 +37,13 @@ TEST_CASE("CRC", "[12-bit Test]") { CRC::addCRC12(random, lenBits); uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCRC12(), crc = $%04X", inCrc); + ::LogInfoEx("T", "CRC::checkCRC12(), crc = $%04X", inCrc); Utils::dump(2U, "12_Sanity_Test CRC", random, len); bool ret = CRC::checkCRC12(random, lenBits); if (!ret) { - ::LogDebug("T", "12_Sanity_Test, failed CRC12 check"); + ::LogError("T", "12_Sanity_Test, failed CRC12 check"); failed = true; goto cleanup; } @@ -53,13 +53,13 @@ TEST_CASE("CRC", "[12-bit Test]") { ret = CRC::checkCRC12(random, lenBits); if (ret) { - ::LogDebug("T", "12_Sanity_Test, failed CRC12 error check"); + ::LogError("T", "12_Sanity_Test, failed CRC12 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_15_Test.cpp b/tests/edac/CRC_15_Test.cpp index 03ed8699b..cf48095d7 100644 --- a/tests/edac/CRC_15_Test.cpp +++ b/tests/edac/CRC_15_Test.cpp @@ -37,13 +37,13 @@ TEST_CASE("CRC", "[15-bit Test]") { CRC::addCRC15(random, lenBits); uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCRC15(), crc = $%04X", inCrc); + ::LogInfoEx("T", "CRC::checkCRC15(), crc = $%04X", inCrc); Utils::dump(2U, "15_Sanity_Test CRC", random, len); bool ret = CRC::checkCRC15(random, lenBits); if (!ret) { - ::LogDebug("T", "15_Sanity_Test, failed CRC15 check"); + ::LogError("T", "15_Sanity_Test, failed CRC15 check"); failed = true; goto cleanup; } @@ -53,13 +53,13 @@ TEST_CASE("CRC", "[15-bit Test]") { ret = CRC::checkCRC15(random, lenBits); if (ret) { - ::LogDebug("T", "15_Sanity_Test, failed CRC15 error check"); + ::LogError("T", "15_Sanity_Test, failed CRC15 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_16_Test.cpp b/tests/edac/CRC_16_Test.cpp index 33014d3c6..0503d91de 100644 --- a/tests/edac/CRC_16_Test.cpp +++ b/tests/edac/CRC_16_Test.cpp @@ -37,13 +37,13 @@ TEST_CASE("CRC", "[16-bit Test]") { CRC::addCRC16(random, lenBits); uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCRC16(), crc = $%04X", inCrc); + ::LogInfoEx("T", "CRC::checkCRC16(), crc = $%04X", inCrc); Utils::dump(2U, "16_Sanity_Test CRC", random, len); bool ret = CRC::checkCRC16(random, lenBits); if (!ret) { - ::LogDebug("T", "16_Sanity_Test, failed CRC16 check"); + ::LogError("T", "16_Sanity_Test, failed CRC16 check"); failed = true; goto cleanup; } @@ -53,13 +53,13 @@ TEST_CASE("CRC", "[16-bit Test]") { ret = CRC::checkCRC16(random, lenBits); if (ret) { - ::LogDebug("T", "16_Sanity_Test, failed CRC16 error check"); + ::LogError("T", "16_Sanity_Test, failed CRC16 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_32_Test.cpp b/tests/edac/CRC_32_Test.cpp index 32520a7fb..6411e0ab4 100644 --- a/tests/edac/CRC_32_Test.cpp +++ b/tests/edac/CRC_32_Test.cpp @@ -36,13 +36,13 @@ TEST_CASE("CRC", "[32-bit Test]") { CRC::addCRC32(random, len); uint32_t inCrc = (random[len - 4U] << 24) | (random[len - 3U] << 16) | (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCRC32(), crc = $%08X", inCrc); + ::LogInfoEx("T", "CRC::checkCRC32(), crc = $%08X", inCrc); Utils::dump(2U, "32_Sanity_Test CRC", random, len); bool ret = CRC::checkCRC32(random, len); if (!ret) { - ::LogDebug("T", "32_Sanity_Test, failed CRC32 check"); + ::LogError("T", "32_Sanity_Test, failed CRC32 check"); failed = true; goto cleanup; } @@ -52,13 +52,13 @@ TEST_CASE("CRC", "[32-bit Test]") { ret = CRC::checkCRC32(random, len); if (ret) { - ::LogDebug("T", "32_Sanity_Test, failed CRC32 error check"); + ::LogError("T", "32_Sanity_Test, failed CRC32 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_6_Test.cpp b/tests/edac/CRC_6_Test.cpp index 5462a22ed..f31d05a4d 100644 --- a/tests/edac/CRC_6_Test.cpp +++ b/tests/edac/CRC_6_Test.cpp @@ -37,13 +37,13 @@ TEST_CASE("CRC", "[6-bit Test]") { CRC::addCRC6(random, lenBits); uint32_t inCrc = (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCRC6(), crc = $%02X", inCrc); + ::LogInfoEx("T", "CRC::checkCRC6(), crc = $%02X", inCrc); Utils::dump(2U, "6_Sanity_Test CRC", random, len); bool ret = CRC::checkCRC6(random, lenBits); if (!ret) { - ::LogDebug("T", "6_Sanity_Test, failed CRC6 check"); + ::LogError("T", "6_Sanity_Test, failed CRC6 check"); failed = true; goto cleanup; } @@ -53,13 +53,13 @@ TEST_CASE("CRC", "[6-bit Test]") { ret = CRC::checkCRC6(random, lenBits); if (ret) { - ::LogDebug("T", "6_Sanity_Test, failed CRC6 error check"); + ::LogError("T", "6_Sanity_Test, failed CRC6 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_8_Test.cpp b/tests/edac/CRC_8_Test.cpp index 2a387be70..fd72c7b77 100644 --- a/tests/edac/CRC_8_Test.cpp +++ b/tests/edac/CRC_8_Test.cpp @@ -34,7 +34,7 @@ TEST_CASE("CRC", "[8-bit Test]") { } uint8_t crc = CRC::crc8(random, len); - ::LogDebug("T", "crc = %02X", crc); + ::LogInfoEx("T", "crc = %02X", crc); Utils::dump(2U, "8_Sanity_Test CRC", random, len); @@ -42,15 +42,15 @@ TEST_CASE("CRC", "[8-bit Test]") { random[11U] >>= 8; uint8_t calc = CRC::crc8(random, len); - ::LogDebug("T", "calc = %02X", calc); + ::LogInfoEx("T", "calc = %02X", calc); if (crc == calc) { - ::LogDebug("T", "8_Sanity_Test, failed CRC8 error check"); + ::LogError("T", "8_Sanity_Test, failed CRC8 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_9_Test.cpp b/tests/edac/CRC_9_Test.cpp index 55b4fbe74..975a16373 100644 --- a/tests/edac/CRC_9_Test.cpp +++ b/tests/edac/CRC_9_Test.cpp @@ -37,7 +37,7 @@ TEST_CASE("CRC", "[9-bit Test]") { random[1U] = 0; uint16_t crc = edac::CRC::createCRC9(random, 144U); - ::LogDebug("T", "crc = %04X", crc); + ::LogInfoEx("T", "crc = %04X", crc); random[0U] = random[0U] + ((crc >> 8) & 0x01U); random[1U] = (crc & 0xFFU); @@ -49,13 +49,13 @@ TEST_CASE("CRC", "[9-bit Test]") { uint16_t calculated = edac::CRC::createCRC9(random, 144U); if (((crc ^ calculated) == 0)/*|| ((crc ^ calculated) == 0x1FFU)*/) { - ::LogDebug("T", "9_Sanity_Test, failed CRC9 error check"); + ::LogError("T", "9_Sanity_Test, failed CRC9 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_CCITT_161_Test.cpp b/tests/edac/CRC_CCITT_161_Test.cpp index f49d42077..b320ca65e 100644 --- a/tests/edac/CRC_CCITT_161_Test.cpp +++ b/tests/edac/CRC_CCITT_161_Test.cpp @@ -36,13 +36,13 @@ TEST_CASE("CRC", "[16-bit CCITT-161 Test]") { CRC::addCCITT161(random, len); uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCCITT161(), crc = $%04X", inCrc); + ::LogInfoEx("T", "CRC::checkCCITT161(), crc = $%04X", inCrc); Utils::dump(2U, "CCITT-161_Sanity_Test CRC", random, len); bool ret = CRC::checkCCITT161(random, len); if (!ret) { - ::LogDebug("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 check"); + ::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 check"); failed = true; goto cleanup; } @@ -52,13 +52,13 @@ TEST_CASE("CRC", "[16-bit CCITT-161 Test]") { ret = CRC::checkCCITT161(random, len); if (ret) { - ::LogDebug("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 error check"); + ::LogError("T", "CCITT-161_Sanity_Test, failed CRC CCITT-162 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/edac/CRC_CCITT_162_Test.cpp b/tests/edac/CRC_CCITT_162_Test.cpp index 83fe65bd2..402b85879 100644 --- a/tests/edac/CRC_CCITT_162_Test.cpp +++ b/tests/edac/CRC_CCITT_162_Test.cpp @@ -36,13 +36,13 @@ TEST_CASE("CRC", "[16-bit CCITT-162 Test]") { CRC::addCCITT162(random, len); uint16_t inCrc = (random[len - 2U] << 8) | (random[len - 1U] << 0); - ::LogDebug("T", "CRC::checkCCITT162(), crc = $%04X", inCrc); + ::LogInfoEx("T", "CRC::checkCCITT162(), crc = $%04X", inCrc); Utils::dump(2U, "CCITT-162_Sanity_Test CRC", random, len); bool ret = CRC::checkCCITT162(random, len); if (!ret) { - ::LogDebug("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 check"); + ::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 check"); failed = true; goto cleanup; } @@ -52,13 +52,13 @@ TEST_CASE("CRC", "[16-bit CCITT-162 Test]") { ret = CRC::checkCCITT162(random, len); if (ret) { - ::LogDebug("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 error check"); + ::LogError("T", "CCITT-162_Sanity_Test, failed CRC CCITT-162 error check"); failed = true; goto cleanup; } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/p25/HDU_RS_Test.cpp b/tests/p25/HDU_RS_Test.cpp index 372ef45fd..78d8604b8 100644 --- a/tests/p25/HDU_RS_Test.cpp +++ b/tests/p25/HDU_RS_Test.cpp @@ -64,7 +64,7 @@ TEST_CASE("HDU", "[Reed-Soloman 36,20,17 Test]") { try { bool ret = m_rs.decode362017(rs); if (!ret) { - ::LogDebug("T", "LC::decodeHDU(), failed to decode RS (36,20,17) FEC\n"); + ::LogError("T", "LC::decodeHDU(), failed to decode RS (36,20,17) FEC"); failed = true; goto cleanup; } @@ -80,20 +80,20 @@ TEST_CASE("HDU", "[Reed-Soloman 36,20,17 Test]") { for (uint32_t i = 0; i < 15U; i++) { if (i == 14U) { if (rs[i] != 0xF0U) { - ::LogDebug("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i); failed = true; } } else { if (rs[i] != random[i]) { - ::LogDebug("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeHDU(), UNCORRECTABLE AT IDX %d", i); failed = true; } } } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/p25/KMM_Rekey_CBC_Test.cpp b/tests/p25/KMM_Rekey_CBC_Test.cpp new file mode 100644 index 000000000..a8b3ae6c9 --- /dev/null +++ b/tests/p25/KMM_Rekey_CBC_Test.cpp @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/Crypto.h" +#include "common/p25/kmm/KMMRekeyCommand.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::crypto; +using namespace p25::defines; +using namespace p25::kmm; + +#include +#include +#include + +TEST_CASE("KMM_ReKey_CBC", "[P25 KMM Rekey Command CBC Test]") { + SECTION("P25_KMM_ReKey_CBC_Test") { + bool failed = false; + + INFO("P25 KMM ReKey Test"); + + srand((unsigned int)time(NULL)); + + // MAC TEK + uint8_t macTek[] = + { + 0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1, + 0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F + }; + + // data block + uint8_t dataBlock[] = + { + 0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC, + 0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35, + 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56, + 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23, + 0x72, 0xFB, 0x80, 0x42, 0xA0, 0x91, 0x56, 0xF0, 0xD4, 0x72, 0x1C, 0x08, 0x84, 0x2F, 0x62, 0x40 + }; + + // Encrypted Key Frame + uint8_t testWrappedKeyFrame[40U] = + { + 0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, + 0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, + 0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80 + }; + + uint8_t encryptMI[] = + { + 0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67 + }; + + // final encrypted block + uint8_t encryptedBlock[] = + { + 0x67, 0x75, 0xB1, 0xD1, 0x8A, 0xBD, 0xCF, 0x86, 0x08, 0x54, 0xDF, 0x09, 0x8E, 0xA3, 0x41, 0x29, + 0x13, 0x2A, 0x0E, 0x48, 0x4C, 0xCC, 0x5C, 0xAE, 0x80, 0x08, 0x0B, 0x19, 0xF7, 0x08, 0xAE, 0x8F, + 0xB8, 0x40, 0xAA, 0x2E, 0x3E, 0x5E, 0xCD, 0x03, 0x73, 0x52, 0x75, 0xFE, 0xE2, 0x88, 0x0E, 0x6D, + 0xDD, 0x00, 0xC1, 0x11, 0x42, 0x8F, 0xEE, 0x39, 0xC6, 0x2B, 0xF3, 0xC1, 0xD2, 0xEE, 0x3B, 0xEB, + 0xBB, 0x7C, 0x44, 0xA5, 0xE3, 0xC9, 0x30, 0x8C, 0x5D, 0xE9, 0x17, 0x84, 0x7C, 0x17, 0xAF, 0x23 + }; + + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, MAC TEK", macTek, 32U); + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, OFB MI", encryptMI, 8U); + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, DataBlock", dataBlock, 80U); + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedBlock", encryptedBlock, 80U); + + KMMRekeyCommand outKmm = KMMRekeyCommand(); + + outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + outKmm.setSrcLLId(0x712B1DU); + outKmm.setDstLLId(0x643BA8U); + + outKmm.setMACType(KMM_MAC::ENH_MAC); + outKmm.setMACAlgId(ALGO_AES_256); + outKmm.setMACKId(0x2F62U); + outKmm.setMACFormat(KMM_MAC_FORMAT_CBC); + + outKmm.setMessageNumber(0x1772U); + + outKmm.setAlgId(ALGO_AES_256); + outKmm.setKId(0x50BCU); + + KeysetItem ks; + ks.keysetId(1U); + ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys + ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(0U); + ki.sln(0U); + ki.kId(0x4983U); + + ki.setKey(testWrappedKeyFrame, 40U); + ks.push_back(ki); + + std::vector keysets; + keysets.push_back(ks); + + outKmm.setKeysets(keysets); + + UInt8Array kmmFrame = std::make_unique(outKmm.fullLength()); + outKmm.encode(kmmFrame.get()); + outKmm.generateMAC(macTek, kmmFrame.get()); + + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength()); + + for (uint32_t i = 0; i < outKmm.fullLength(); i++) { + if (kmmFrame.get()[i] != dataBlock[i]) { + ::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + P25Crypto crypto; + crypto.setMI(encryptMI); + crypto.setTEKAlgoId(ALGO_AES_256); + crypto.setKey(macTek, 32U); + crypto.generateKeystream(); + + crypto.cryptAES_PDU(kmmFrame.get(), outKmm.fullLength()); + + Utils::dump(2U, "P25_KMM_ReKey_CBC_Test, EncryptedDataBlock", kmmFrame.get(), outKmm.fullLength()); + + for (uint32_t i = 0; i < outKmm.fullLength(); i++) { + if (kmmFrame.get()[i] != encryptedBlock[i]) { + ::LogError("T", "P25_KMM_ReKey_CBC_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/KMM_Rekey_CMAC_Test.cpp b/tests/p25/KMM_Rekey_CMAC_Test.cpp new file mode 100644 index 000000000..2a54e9a4f --- /dev/null +++ b/tests/p25/KMM_Rekey_CMAC_Test.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/kmm/KMMRekeyCommand.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::kmm; + +#include +#include +#include + +TEST_CASE("KMM_ReKey_CMAC", "[P25 KMM Rekey Command CMAC Test]") { + SECTION("P25_KMM_ReKey_CMAC_Test") { + bool failed = false; + + INFO("P25 KMM ReKey Test"); + + srand((unsigned int)time(NULL)); + + // MAC TEK + uint8_t macTek[] = + { + 0x16, 0x85, 0x62, 0x45, 0x3B, 0x3E, 0x7F, 0x61, 0x8D, 0x68, 0xB3, 0x87, 0xE0, 0xB9, 0x97, 0xE1, + 0xFB, 0x0F, 0x26, 0x4F, 0xA8, 0x3B, 0x74, 0xE4, 0x3B, 0x17, 0x29, 0x17, 0xBD, 0x39, 0x33, 0x9F + }; + + // data block + uint8_t dataBlock[] = + { + 0x1E, 0x00, 0x4D, 0xA8, 0x64, 0x3B, 0xA8, 0x71, 0x2B, 0x1D, 0x17, 0x72, 0x00, 0x84, 0x50, 0xBC, + 0x01, 0x00, 0x01, 0x84, 0x28, 0x01, 0x00, 0x00, 0x00, 0x49, 0x83, 0x80, 0x28, 0x9C, 0xF6, 0x35, + 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, 0xE0, 0x5C, 0xAE, 0x47, 0x56, + 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, 0x84, 0x09, 0x45, 0x37, 0x23, + 0x72, 0xFB, 0x80, 0x21, 0x85, 0x22, 0x33, 0x41, 0xD9, 0x8A, 0x97, 0x08, 0x84, 0x2F, 0x62, 0x41 + }; + + // Encrypted Key Frame + uint8_t testWrappedKeyFrame[40U] = + { + 0x80, 0x28, 0x9C, 0xF6, 0x35, 0xFB, 0x68, 0xD3, 0x45, 0xD3, 0x4F, 0x62, 0xEF, 0x06, 0x3B, 0xA4, + 0xE0, 0x5C, 0xAE, 0x47, 0x56, 0xE7, 0xD3, 0x04, 0x46, 0xD1, 0xF0, 0x7C, 0x6E, 0xB4, 0xE9, 0xE0, + 0x84, 0x09, 0x45, 0x37, 0x23, 0x72, 0xFB, 0x80 + }; + + Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, DataBlock", dataBlock, 80U); + + KMMRekeyCommand outKmm = KMMRekeyCommand(); + + outKmm.setDecryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE); + outKmm.setSrcLLId(0x712B1DU); + outKmm.setDstLLId(0x643BA8U); + + outKmm.setMACType(KMM_MAC::ENH_MAC); + outKmm.setMACAlgId(ALGO_AES_256); + outKmm.setMACKId(0x2F62U); + outKmm.setMACFormat(KMM_MAC_FORMAT_CMAC); + + outKmm.setMessageNumber(0x1772U); + + outKmm.setAlgId(ALGO_AES_256); + outKmm.setKId(0x50BCU); + + KeysetItem ks; + ks.keysetId(1U); + ks.algId(ALGO_AES_256); // we currently can only OTAR AES256 keys + ks.keyLength(P25DEF::MAX_WRAPPED_ENC_KEY_LENGTH_BYTES); + + p25::kmm::KeyItem ki = p25::kmm::KeyItem(); + ki.keyFormat(0U); + ki.sln(0U); + ki.kId(0x4983U); + + ki.setKey(testWrappedKeyFrame, 40U); + ks.push_back(ki); + + std::vector keysets; + keysets.push_back(ks); + + outKmm.setKeysets(keysets); + + UInt8Array kmmFrame = std::make_unique(outKmm.fullLength()); + outKmm.encode(kmmFrame.get()); + outKmm.generateMAC(macTek, kmmFrame.get()); + + Utils::dump(2U, "P25_KMM_ReKey_CMAC_Test, GeneratedDataBlock", kmmFrame.get(), outKmm.fullLength()); + + for (uint32_t i = 0; i < outKmm.fullLength(); i++) { + if (kmmFrame.get()[i] != dataBlock[i]) { + ::LogError("T", "P25_KMM_ReKey_CMAC_Test, INVALID AT IDX %d", i); + failed = true; + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/LDU1_RS_Test.cpp b/tests/p25/LDU1_RS_Test.cpp index dcede1ae3..58fc7c458 100644 --- a/tests/p25/LDU1_RS_Test.cpp +++ b/tests/p25/LDU1_RS_Test.cpp @@ -62,7 +62,7 @@ TEST_CASE("LDU1", "[Reed-Soloman 24,12,13 Test]") { try { bool ret = m_rs.decode241213(rs); if (!ret) { - ::LogDebug("T", "LC::decodeLDU1(), failed to decode RS (24,12,13) FEC\n"); + ::LogError("T", "LC::decodeLDU1(), failed to decode RS (24,12,13) FEC"); failed = true; goto cleanup; } @@ -78,20 +78,20 @@ TEST_CASE("LDU1", "[Reed-Soloman 24,12,13 Test]") { for (uint32_t i = 0; i < 9U; i++) { if (i == 8U) { if (rs[i] != 0xF0U) { - ::LogDebug("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i); failed = true; } } else { if (rs[i] != random[i]) { - ::LogDebug("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeLDU1(), UNCORRECTABLE AT IDX %d", i); failed = true; } } } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/p25/LDU2_RS_Test.cpp b/tests/p25/LDU2_RS_Test.cpp index 9251107a1..9a0de303f 100644 --- a/tests/p25/LDU2_RS_Test.cpp +++ b/tests/p25/LDU2_RS_Test.cpp @@ -61,7 +61,7 @@ TEST_CASE("LDU2", "[Reed-Soloman 24,16,9 Test]") { try { bool ret = m_rs.decode24169(rs); if (!ret) { - ::LogDebug("T", "LC::decodeLDU2(), failed to decode RS (24,16,9) FEC\n"); + ::LogError("T", "LC::decodeLDU2(), failed to decode RS (24,16,9) FEC"); failed = true; goto cleanup; } @@ -77,20 +77,20 @@ TEST_CASE("LDU2", "[Reed-Soloman 24,16,9 Test]") { for (uint32_t i = 0; i < 12U; i++) { if (i == 11U) { if (rs[i] != 0xF0U) { - ::LogDebug("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i); failed = true; } } else { if (rs[i] != random[i]) { - ::LogDebug("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d\n", i); + ::LogError("T", "LC::decodeLDU2(), UNCORRECTABLE AT IDX %d", i); failed = true; } } } cleanup: - delete random; + free(random); REQUIRE(failed==false); } } diff --git a/tests/p25/PDU_Confirmed_AuxES_Test.cpp b/tests/p25/PDU_Confirmed_AuxES_Test.cpp new file mode 100644 index 000000000..f65878a48 --- /dev/null +++ b/tests/p25/PDU_Confirmed_AuxES_Test.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Confirmed_AuxES_Test", "[P25 PDU Confirmed Aux ES Test]") { + SECTION("P25_PDU_Confirmed_AuxES_Test") { + bool failed = false; + + INFO("P25 PDU Confirmed Aux ES Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 30U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + uint8_t encryptMI[] = + { + 0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67 + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Test Source", testPDUSource, 30U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::CONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::ENC_USER_DATA); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.setEXSAP(PDUSAP::USER_DATA); + + dataHeader.setMI(encryptMI); + dataHeader.setAlgId(ALGO_AES_256); + dataHeader.setKId(0x2F62U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Confirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "P25_PDU_Confirmed_AuxES_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Confirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Confirmed_AuxES_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Confirmed_ConvReg_Test.cpp b/tests/p25/PDU_Confirmed_ConvReg_Test.cpp new file mode 100644 index 000000000..64a403746 --- /dev/null +++ b/tests/p25/PDU_Confirmed_ConvReg_Test.cpp @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Confirmed_ConvReg_Test", "[P25 PDU Confirmed Conv Reg Test]") { + SECTION("P25_PDU_Confirmed_ConvReg_Test") { + bool failed = false; + + INFO("P25 PDU Confirmed Conv Reg Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // data block + uint8_t dataBlock[] = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0x1C, + 0x2A, 0x6E, 0x12, 0x2A, 0x20, 0x67, 0x0F, 0x79, 0x29, 0x2C, 0x70, 0x9E, 0x0B, 0x32, 0x21, 0x23, + 0x3D, 0x22, 0xED, 0x8C, 0x29, 0x26, 0x50, + + 0x26, 0xE0, 0xB2, 0x22, 0x22, 0xB0, 0x72, 0x20, 0xE2, 0x22, 0x22, 0x59, 0x11, 0xE3, 0x92, 0x22, + 0x22, 0x92, 0x73, 0x21, 0x52, 0x22, 0x22, 0x1F, 0x30 + }; + + // expected PDU user data + uint8_t expectedUserData[] = + { + 0x00, 0x54, 0x36, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC9, 0x9D, 0x42, 0x56 + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + uint8_t pduUserData[P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(pduUserData, 0x00U, P25_MAX_PDU_BLOCKS * P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + /* + ** self-sanity check the assembler chain + */ + + DataHeader rspHeader = DataHeader(); + rspHeader.setFormat(PDUFormatType::CONFIRMED); + rspHeader.setMFId(assembler.dataHeader.getMFId()); + rspHeader.setAckNeeded(true); + rspHeader.setOutbound(true); + rspHeader.setSAP(PDUSAP::CONV_DATA_REG); + rspHeader.setSynchronize(true); + rspHeader.setLLId(0x12345U); + rspHeader.setBlocksToFollow(1U); + + uint32_t regType = PDURegType::ACCEPT; + uint32_t llId = 0x12345U; + uint32_t ipAddr = 0x7F000001; + + pduUserData[0U] = ((regType & 0x0FU) << 4); // Registration Type & Options + pduUserData[1U] = (llId >> 16) & 0xFFU; // Logical Link ID + pduUserData[2U] = (llId >> 8) & 0xFFU; + pduUserData[3U] = (llId >> 0) & 0xFFU; + if (regType == PDURegType::ACCEPT) { + pduUserData[8U] = (ipAddr >> 24) & 0xFFU; // IP Address + pduUserData[9U] = (ipAddr >> 16) & 0xFFU; + pduUserData[10U] = (ipAddr >> 8) & 0xFFU; + pduUserData[11U] = (ipAddr >> 0) & 0xFFU; + } + + Utils::dump(2U, "P25, PDU Registration Response", pduUserData, 12U); + + rspHeader.calculateLength(12U); + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(rspHeader, false, false, pduUserData, &bitLength); + + LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Confirmed_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U; + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength; i++) { + if (pduUserData2[i] != pduUserData[i]) { + ::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + /* + ** test disassembly against the static test data block + */ + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BYTES; i < 64U; i += P25_PDU_FEC_LENGTH_BYTES) { + LogInfoEx("T", "i = %u", i); + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + ::memcpy(buffer, dataBlock + i, P25_PDU_FEC_LENGTH_BYTES); + + Utils::dump(2U, "P25_PDU_Confirmed_Test, Block", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Confirmed_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength() - 4U; + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength; i++) { + if (pduUserData2[i] != expectedUserData[i]) { + ::LogError("T", "P25_PDU_Confirmed_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Confirmed_ExtAddr_Test.cpp b/tests/p25/PDU_Confirmed_ExtAddr_Test.cpp new file mode 100644 index 000000000..669e173d7 --- /dev/null +++ b/tests/p25/PDU_Confirmed_ExtAddr_Test.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Confirmed_ExtAddr_Test", "[P25 PDU Confirmed Ext Addr Test]") { + SECTION("P25_PDU_Confirmed_ExtAddr_Test") { + bool failed = false; + + INFO("P25 PDU Confirmed Ext Addr Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 30U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Test Source", testPDUSource, 30U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::CONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::EXT_ADDR); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.setEXSAP(PDUSAP::USER_DATA); + dataHeader.setSrcLLId(0x54321U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Confirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "P25_PDU_Confirmed_ExtAddr_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Confirmed_ExtAddr_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Confirmed_Large_Test.cpp b/tests/p25/PDU_Confirmed_Large_Test.cpp new file mode 100644 index 000000000..0cf58c967 --- /dev/null +++ b/tests/p25/PDU_Confirmed_Large_Test.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Confirmed_Large_Test", "[P25 PDU Confirmed Large Test]") { + SECTION("P25_PDU_Confirmed_Large_Test") { + bool failed = false; + + INFO("P25 PDU Confirmed Large Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 120U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, + 0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21 + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Test Source", testPDUSource, 120U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::CONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(false); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::USER_DATA); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Confirmed_Large_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Confirmed_Large_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Confirmed_Large_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Confirmed_Small_Test.cpp b/tests/p25/PDU_Confirmed_Small_Test.cpp new file mode 100644 index 000000000..8b54e0ce4 --- /dev/null +++ b/tests/p25/PDU_Confirmed_Small_Test.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Confirmed_Small_Test", "[P25 PDU Confirmed Small Test]") { + SECTION("P25_PDU_Confirmed_Small_Test") { + bool failed = false; + + INFO("P25 PDU Confirmed Small Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 30U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "PDU_Confirmed_Small_Test, Test Source", testPDUSource, 30U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::CONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::USER_DATA); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength); + + LogInfoEx("T", "PDU_Confirmed_Small_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "PDU_Confirmed_Small_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "PDU_Confirmed_Small_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "PDU_Confirmed_Small_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "PDU_Confirmed_Small_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Unconfirmed_AuxES_Test.cpp b/tests/p25/PDU_Unconfirmed_AuxES_Test.cpp new file mode 100644 index 000000000..f4bac2a9a --- /dev/null +++ b/tests/p25/PDU_Unconfirmed_AuxES_Test.cpp @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Unconfirmed_AuxES_Test", "[P25 PDU Unconfirmed Aux ES Test]") { + SECTION("P25_PDU_Unconfirmed_AuxES_Test") { + bool failed = false; + + INFO("P25 PDU Unconfirmed Aux ES Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 30U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + uint8_t encryptMI[] = + { + 0x70, 0x30, 0xF1, 0xF7, 0x65, 0x69, 0x26, 0x67 + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Test Source", testPDUSource, 30U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::UNCONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::ENC_USER_DATA); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.setEXSAP(PDUSAP::USER_DATA); + + dataHeader.setMI(encryptMI); + dataHeader.setAlgId(ALGO_AES_256); + dataHeader.setKId(0x2F62U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, false, true, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Unconfirmed_AuxES_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "P25_PDU_Unconfirmed_AuxES_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Unconfirmed_AuxES_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Unconfirmed_ExtAddr_Test.cpp b/tests/p25/PDU_Unconfirmed_ExtAddr_Test.cpp new file mode 100644 index 000000000..341859c2b --- /dev/null +++ b/tests/p25/PDU_Unconfirmed_ExtAddr_Test.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Unconfirmed_ExtAddr_Test", "[P25 PDU Unconfirmed Ext Addr Test]") { + SECTION("P25_PDU_Unconfirmed_ExtAddr_Test") { + bool failed = false; + + INFO("P25 PDU Unconfirmed Ext Addr Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 30U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Test Source", testPDUSource, 30U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::UNCONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(true); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::EXT_ADDR); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.setEXSAP(PDUSAP::USER_DATA); + dataHeader.setSrcLLId(0x54321U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, true, false, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Unconfirmed_ExtAddr_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + LogInfoEx("T", "P25_PDU_Unconfirmed_ExtAddr_Test, i = %u", i); + Utils::dump(2U, "buffer", buffer, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Unconfirmed_ExtAddr_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tests/p25/PDU_Unconfirmed_Test.cpp b/tests/p25/PDU_Unconfirmed_Test.cpp new file mode 100644 index 000000000..7fddb4e07 --- /dev/null +++ b/tests/p25/PDU_Unconfirmed_Test.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Test Suite + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "host/Defines.h" +#include "common/p25/P25Defines.h" +#include "common/p25/data/Assembler.h" +#include "common/Log.h" +#include "common/Utils.h" + +using namespace p25; +using namespace p25::defines; +using namespace p25::data; + +#include +#include +#include + +TEST_CASE("PDU_Unconfirmed_Test", "[P25 PDU Unconfirmed Test]") { + SECTION("P25_PDU_Unconfirmed_Test") { + bool failed = false; + + INFO("P25 PDU Unconfirmed Test"); + + srand((unsigned int)time(NULL)); + + g_logDisplayLevel = 1U; + + // test PDU data + uint32_t testLength = 120U; + uint8_t testPDUSource[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, + 0x20, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x54, 0x45, 0x53, 0x54, 0x20, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21 + }; + + data::Assembler::setVerbose(true); + data::Assembler::setDumpPDUData(true); + + Assembler assembler = Assembler(); + + Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Test Source", testPDUSource, 120U); + + DataHeader dataHeader = DataHeader(); + dataHeader.setFormat(PDUFormatType::UNCONFIRMED); + dataHeader.setMFId(MFG_STANDARD); + dataHeader.setAckNeeded(false); + dataHeader.setOutbound(true); + dataHeader.setSAP(PDUSAP::USER_DATA); + dataHeader.setLLId(0x12345U); + dataHeader.setFullMessage(true); + dataHeader.setBlocksToFollow(1U); + + dataHeader.calculateLength(testLength); + + /* + ** self-sanity check the assembler chain + */ + + uint32_t bitLength = 0U; + UInt8Array ret = assembler.assemble(dataHeader, false, false, testPDUSource, &bitLength); + + LogInfoEx("T", "P25_PDU_Confirmed_Large_Test, Assembled Bit Length = %u (%u)", bitLength, bitLength / 8); + Utils::dump(2U, "P25_PDU_Unconfirmed_Test, Assembled PDU", ret.get(), bitLength / 8); + + if (ret == nullptr) + failed = true; + + if (!failed) { + uint8_t buffer[P25_PDU_FRAME_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FRAME_LENGTH_BYTES); + + // for the purposes of our test we strip the pad bit length from the bit length + bitLength -= dataHeader.getPadLength() * 8U; + + uint32_t blockCnt = 0U; + for (uint32_t i = P25_PREAMBLE_LENGTH_BITS; i < bitLength; i += P25_PDU_FEC_LENGTH_BITS) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(ret.get(), buffer, i, P25_PDU_FEC_LENGTH_BITS); + + bool ret = false; + if (blockCnt == 0U) + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES, true); + else + ret = assembler.disassemble(buffer, P25_PDU_FEC_LENGTH_BYTES); + if (!ret) { + failed = true; + ::LogError("T", "P25_PDU_Unconfirmed_Test, PDU Disassemble, block %u", blockCnt); + } + + blockCnt++; + } + + if (assembler.getComplete()) { + uint8_t pduUserData2[P25_MAX_PDU_BLOCKS * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + uint32_t pduUserDataLength = assembler.getUserDataLength(); + assembler.getUserData(pduUserData2); + + for (uint32_t i = 0; i < pduUserDataLength - 4U; i++) { + if (pduUserData2[i] != testPDUSource[i]) { + ::LogError("T", "P25_PDU_Unconfirmed_Test, INVALID AT IDX %d", i); + failed = true; + } + } + } + } + + REQUIRE(failed==false); + } +} diff --git a/tools/colorize-fne.sh b/tools/colorize-fne.sh index bee978b3b..b0bbb54bc 100755 --- a/tools/colorize-fne.sh +++ b/tools/colorize-fne.sh @@ -23,14 +23,25 @@ #* along with this program; if not, write to the Free Software #* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #*/ -LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&#; s#E:#\x1b[0m\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;" +CLEAR_ID="s/ID: /ID /g;" +EOL_CLEAR="s/$/\x1b[0m/;" +LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&\x1b[0m#; s#E:#\x1b[0m\x1b[1m\x1b[31m&\x1b[0m#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&\x1b[0m#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;" DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" NXDN_COLOR="s#VCALL#\x1b[36m&#; s#TX_REL#\x1b[0m\x1b[32m&#" AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" +NAK_COLOR="s#NAK#\x1b[0m\x1b[1m\x1b[31m&\x1b[0m#;" + +HOST_HIGHLIGHT="s#(HOST)#\x1b[1m\x1b[37m&\x1b[0m#;" + RF_HIGHLIGHT="s#(RF)#\x1b[1m\x1b[34m&\x1b[0m#;" NET_HIGHLIGHT="s#(NET)#\x1b[1m\x1b[36m&\x1b[0m#;" -sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}" \ No newline at end of file +MASTER_HIGHLIGHT="s#(MSTR)#\x1b[1m\x1b[34m&\x1b[0m#;" +PEER_HIGHLIGHT="s#(PEER)#\x1b[1m\x1b[36m&\x1b[0m#;" +STP_HIGHLIGHT="s#(STP)#\x1b[1m\x1b[32m&\x1b[0m#;" +REPL_HIGHLIGHT="s#(REPL)#\x1b[1m\x1b[33m&\x1b[0m#;" + +sed "${CLEAR_ID}; ${LOG_COLOR}; ${NAK_COLOR}; ${HOST_HIGHLIGHT}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${MASTER_HIGHLIGHT}; ${PEER_HIGHLIGHT}; ${STP_HIGHLIGHT}; ${REPL_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}; ${EOL_COLOR};" diff --git a/tools/colorize-host.sh b/tools/colorize-host.sh index f51668769..951c123ac 100755 --- a/tools/colorize-host.sh +++ b/tools/colorize-host.sh @@ -23,7 +23,8 @@ #* along with this program; if not, write to the Free Software #* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #*/ -LOG_COLOR="s#W:#\x1b[1m\x1b[33m&#; s#E:#\x1b[1m\x1b[31m&#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&\x1b[0m\x1b[1m#;" +EOL_CLEAR="s/$/\x1b[0m/;" +LOG_COLOR="s#W:#\x1b[0m\x1b[1m\x1b[33m&\x1b[0m#; s#E:#\x1b[0m\x1b[1m\x1b[31m&\x1b[0m#; s#M:#\x1b[0m&#; s#I:#\x1b[0m&#; s#D:#\x1b[1m\x1b[34m&\x1b[0m#; s#U:#\x1b[44m\x1b[1m\x1b[33m&#;" DMR_COLOR="s#VOICE#\x1b[36m&#; s#TERMINATOR_WITH_LC#\x1b[0m\x1b[32m&#; s#CSBK#\x1b[0m\x1b[35m&#" P25_COLOR="s#LDU#\x1b[36m&#; s#TDU#\x1b[0m\x1b[32m&#; s#HDU#\x1b[0m\x1b[32m&#; s#TSDU#\x1b[0m\x1b[35m&#" @@ -33,4 +34,4 @@ AFF_COLOR="s#Affiliations#\x1b[1m\x1b[36m&#; s#Affiliation#\x1b[1m\x1b[36m&#;" RF_HIGHLIGHT="s#RF#\x1b[1m\x1b[35m&\x1b[0m#;" NET_HIGHLIGHT="s#NET#\x1b[1m\x1b[36m&\x1b[0m#;" -sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}" +sed "${LOG_COLOR}; ${RF_HIGHLIGHT}; ${NET_HIGHLIGHT}; ${DMR_COLOR}; ${P25_COLOR}; ${NXDN_COLOR}; ${AFF_COLOR}; ${EOL_COLOR};"