commit 9cc117d30cf4605c11f0fc70e00a081bc7969160 Author: Bryan Biedenkapp Date: Fri Apr 17 12:16:46 2020 -0400 inital commit from private repo to public; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..176a458f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a3d54310 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Ignore thumbnails created by windows +Thumbs.db + +# Ignore files build by Visual Studio +*.obj +*.pdb +*.mdb # Mono debug file +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +[Oo]bj*/ +[Rr]elease*/ +[R]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +*.sdf +*.opensdf +*.userprefs +build.mk +*.prv.xml +*.pub.xml +build/ +.vscode/ +package/ +*.ini + +# Compiled binary files +*.exe +*.dll + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Visual Studio +.vs diff --git a/DVMHost.sln b/DVMHost.sln new file mode 100644 index 00000000..98211087 --- /dev/null +++ b/DVMHost.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28729.10 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dvmhost", "DVMHost.vcxproj", "{1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|Any CPU.Build.0 = Debug|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|x64.ActiveCfg = Debug|x64 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|x64.Build.0 = Debug|x64 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|x86.ActiveCfg = Debug|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Debug|x86.Build.0 = Debug|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|Any CPU.ActiveCfg = Release|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|Any CPU.Build.0 = Release|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|x64.ActiveCfg = Release|x64 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|x64.Build.0 = Release|x64 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|x86.ActiveCfg = Release|Win32 + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection +EndGlobal diff --git a/DVMHost.vcxproj b/DVMHost.vcxproj new file mode 100644 index 00000000..6764ea0a --- /dev/null +++ b/DVMHost.vcxproj @@ -0,0 +1,320 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {1D34E8C1-CFA5-4D60-B509-9DB58DC4AE92} + Win32Proj + DVMHost + dvmhost + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir);$(IncludePath) + + + true + $(ProjectDir);$(IncludePath) + + + false + $(ProjectDir);$(IncludePath) + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + $(IntDir)%(RelativeDir) + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Console + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + $(IntDir)%(RelativeDir) + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Console + true + true + true + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Document + + + Document + + + + + + + Document + + + Document + + + Document + + + + + + \ No newline at end of file diff --git a/DVMHost.vcxproj.filters b/DVMHost.vcxproj.filters new file mode 100644 index 00000000..f39f91b1 --- /dev/null +++ b/DVMHost.vcxproj.filters @@ -0,0 +1,540 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {d76c896d-81a1-4571-a979-85bbe05f6ff2} + + + {b48c6e95-0721-492e-b1ea-7ec159b0ad47} + + + {6af8b815-9965-4986-b628-1dc3f551ec6a} + + + {9cbfad41-aaef-4b33-bfa5-1c6dd6c245c9} + + + {0db9bf97-4035-4390-8e6e-2aac5d51dbc4} + + + {8e26b0a3-a3ad-4d67-bc50-c725212e96a1} + + + {1e5fc186-1e05-48e3-a775-dc2b28e6b554} + + + {916a1400-af49-4141-912a-eb51ee88e24a} + + + {11b92d52-caf4-4a9e-86dc-6188084d91d4} + + + {364613af-689a-40ff-9eb0-9ed75ec264d0} + + + {7c629c3c-e84b-48f4-9dde-061d737bf36d} + + + {4ea80282-86d8-4253-8fe1-ea332ba13e09} + + + {9e7cd2a6-f295-456f-aa3a-4328994acad2} + + + {0405cae8-d22a-4120-b228-3c2250948346} + + + {f490b092-2827-4999-b3ee-bf978975894a} + + + {87267b26-6d3b-4b44-9c34-93860af6bff2} + + + {ac49360e-4207-433b-a3db-1974710de8b8} + + + {6de4c69f-fa55-405e-a92f-3fdec05414c5} + + + {ee5bbcfb-d1bf-47fb-88b4-1b76100d1c0f} + + + {77d81332-702f-487a-b155-dcc38f2831f0} + + + {ac8d97b8-a9bd-48e3-9fb9-08e997d055d4} + + + {94f78cbc-78b5-454e-972d-89ff595eca17} + + + {bae82c0a-4986-4f77-a85e-e865b77993b3} + + + {453ece82-3382-481b-b8a6-560f085e1d0d} + + + {d742b2cf-71b2-45a1-afbd-753128c38f66} + + + {07a6e6a8-699e-41a9-9e0f-0a8a064b2331} + + + {d602c3e6-4a28-4d6f-a0f6-49b54978ce71} + + + {89dd7bc2-bdf5-4ae8-ba5b-f44abdaba5e8} + + + {a085e2ea-d75f-4eef-b7f1-1cb100cd6323} + + + {3f236928-1cc8-405c-a085-8a22404b5887} + + + {5a6c9478-41b0-41e3-8511-4d58e7edc38f} + + + {f4690a68-efe2-4d42-a449-3b25d20d18a1} + + + {f8c0460a-c82a-430c-8ad7-eac5ad528ca7} + + + {3a6ae793-a482-46fe-9fd3-93476ccb591d} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\edac + + + Header Files\modem + + + Header Files\modem + + + Header Files\network + + + Header Files\network + + + Header Files\edac + + + Header Files\modem + + + Header Files\network + + + Header Files\lookups + + + Header Files\lookups + + + Header Files\lookups + + + Header Files\lookups + + + Header Files\lookups + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\dmr\acl + + + Header Files\dmr\data + + + Header Files\dmr\data + + + Header Files\dmr\data + + + Header Files\dmr\data + + + Header Files\dmr\edac + + + Header Files\dmr\lc + + + Header Files\dmr\lc + + + Header Files\dmr\lc + + + Header Files\dmr\lc + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\dmr + + + Header Files\p25\acl + + + Header Files\p25\data + + + Header Files\p25\data + + + Header Files\p25\data + + + Header Files\p25\edac + + + Header Files\p25\lc + + + Header Files\p25\lc + + + Header Files\p25\lc + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\p25 + + + Header Files\yaml + + + Header Files\host\calibrate + + + Header Files\host\calibrate + + + Header Files\host + + + Header Files + + + Header Files\network + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\edac + + + Source Files\network + + + Source Files\network + + + Source Files\modem + + + Source Files\modem + + + Source Files\edac + + + Source Files\modem + + + Source Files\network + + + Source Files\lookups + + + Source Files\lookups + + + Source Files\lookups + + + Source Files\lookups + + + Source Files\dmr\acl + + + Source Files\dmr\data + + + Source Files\dmr\data + + + Source Files\dmr\data + + + Source Files\dmr\data + + + Source Files\dmr\edac + + + Source Files\dmr\lc + + + Source Files\dmr\lc + + + Source Files\dmr\lc + + + Source Files\dmr\lc + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\dmr + + + Source Files\p25\acl + + + Source Files\p25\data + + + Source Files\p25\data + + + Source Files\p25\data + + + Source Files\p25\edac + + + Source Files\p25\lc + + + Source Files\p25\lc + + + Source Files\p25\lc + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\p25 + + + Source Files\yaml + + + Source Files\host + + + Source Files\host\calibrate + + + Source Files\host\calibrate + + + Source Files + + + Source Files\network + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Defines.h b/Defines.h new file mode 100644 index 00000000..3d2240b3 --- /dev/null +++ b/Defines.h @@ -0,0 +1,239 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2018-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DEFINES_H__) +#define __DEFINES_H__ + +#include + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +#ifndef _INT8_T_DECLARED +#ifndef __INT8_TYPE__ +typedef signed char int8_t; +#endif // __INT8_TYPE__ +#endif // _INT8_T_DECLARED +#ifndef _INT16_T_DECLARED +#ifndef __INT16_TYPE__ +typedef short int16_t; +#endif // __INT16_TYPE__ +#endif // _INT16_T_DECLARED +#ifndef _INT32_T_DECLARED +#ifndef __INT32_TYPE__ +typedef int int32_t; +#endif // __INT32_TYPE__ +#endif // _INT32_T_DECLARED +#ifndef _INT64_T_DECLARED +#ifndef __INT64_TYPE__ +typedef long long int64_t; +#endif // __INT64_TYPE__ +#endif // _INT64_T_DECLARED +#ifndef _UINT8_T_DECLARED +#ifndef __UINT8_TYPE__ +typedef unsigned char uint8_t; +#endif // __UINT8_TYPE__ +#endif // _UINT8_T_DECLARED +#ifndef _UINT16_T_DECLARED +#ifndef __UINT16_TYPE__ +typedef unsigned short uint16_t; +#endif // __UINT16_TYPE__ +#endif // _UINT16_T_DECLARED +#ifndef _UINT32_T_DECLARED +#ifndef __UINT32_TYPE__ +typedef unsigned int uint32_t; +#endif // __UINT32_TYPE__ +#endif // _UINT32_T_DECLARED +#ifndef _UINT64_T_DECLARED +#ifndef __UINT64_TYPE__ +typedef unsigned long long uint64_t; +#endif // __UINT64_TYPE__ +#endif // _UINT64_T_DECLARED + +#ifndef __LONG64_TYPE__ +typedef long long long64_t; +#endif // __LONG64_TYPE__ +#ifndef __ULONG64_TYPE__ +typedef unsigned long long ulong64_t; +#endif // __ULONG64_TYPE__ + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define __PROG_NAME__ "Digital Voice Modem Host" +#define __EXE_NAME__ "dvmhost" +#define __VER__ "R01.00.00" +#define __BUILD__ __DATE__ " " __TIME__ + +#define HOST_SW_API + +#if defined(_WIN32) || defined(_WIN64) +#define DEFAULT_CONF_FILE "config.yml" +#else +#define DEFAULT_CONF_FILE "/opt/DVM/bin/config.yml" +#endif // defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) || defined(_WIN64) +#define DEFAULT_LOCK_FILE "dvm.lock" +#else +#define DEFAULT_LOCK_FILE "/tmp/dvm.lock" +#endif // defined(_WIN32) || defined(_WIN64) + +#if defined(__GNUC__) || defined(__GNUG__) +#define __forceinline __attribute__((always_inline)) +#endif + +const uint32_t TRAFFIC_DEFAULT_PORT = 62031; +const uint32_t RCON_DEFAULT_PORT = 9990; + +const uint8_t BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +enum HOST_STATE { + HOST_STATE_LOCKOUT = 250U, + HOST_STATE_ERROR = 254U, + HOST_STATE_QUIT = 255U, +}; + +const uint8_t TAG_HEADER = 0x00U; +const uint8_t TAG_DATA = 0x01U; +const uint8_t TAG_LOST = 0x02U; +const uint8_t TAG_EOT = 0x03U; + +enum RPT_RF_STATE { + RS_RF_LISTENING, + RS_RF_LATE_ENTRY, + RS_RF_AUDIO, + RS_RF_DATA, + RS_RF_REJECTED, + RS_RF_INVALID +}; + +enum RPT_NET_STATE { + RS_NET_IDLE, + RS_NET_AUDIO, + RS_NET_DATA +}; + +const uint8_t UDP_COMPRESS_NONE = 0x00U; + +const uint8_t IP_COMPRESS_NONE = 0x00U; +const uint8_t IP_COMPRESS_RFC1144_COMPRESS = 0x01U; +const uint8_t IP_COMPRESS_RFC1144_UNCOMPRESS = 0x02U; + +// --------------------------------------------------------------------------- +// Inlines +// --------------------------------------------------------------------------- + +inline std::string __BOOL_STR(const bool& value) { + std::stringstream ss; + ss << std::boolalpha << value; + return ss.str(); +} + +inline std::string __INT_STR(const int& value) { + std::stringstream ss; + ss << value; + return ss.str(); +} + +inline std::string __FLOAT_STR(const float& value) { + std::stringstream ss; + ss << value; + return ss.str(); +} + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#define __FLOAT_ADDR(x) (*(uint32_t*)& x) +#define __DOUBLE_ADDR(x) (*(uint64_t*)& x) + +#define WRITE_BIT(p, i, b) p[(i) >> 3] = (b) ? (p[(i) >> 3] | BIT_MASK_TABLE[(i) & 7]) : (p[(i) >> 3] & ~BIT_MASK_TABLE[(i) & 7]) +#define READ_BIT(p, i) (p[(i) >> 3] & BIT_MASK_TABLE[(i) & 7]) + +#define __SET_UINT32(val, buffer, offset) \ + buffer[0U + offset] = val >> 24; \ + buffer[1U + offset] = val >> 16; \ + buffer[2U + offset] = val >> 8; \ + buffer[3U + offset] = val >> 0; +#define __GET_UINT32(buffer, offset) \ + (buffer[offset + 0U] << 24) | \ + (buffer[offset + 1U] << 16) | \ + (buffer[offset + 2U] << 8) | \ + (buffer[offset + 3U] << 0); +#define __SET_UINT16(val, buffer, offset) \ + buffer[0U + offset] = val >> 16; \ + buffer[1U + offset] = val >> 8; \ + buffer[2U + offset] = val >> 0; +#define __GET_UINT16(buffer, offset) \ + (buffer[offset + 0U] << 16) | \ + (buffer[offset + 1U] << 8) | \ + (buffer[offset + 2U] << 0); + +/** + * Property Creation + * These macros should always be used LAST in the "public" section of a class definition. + */ +/// Creates a read-only get property. +#define __READONLY_PROPERTY(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type get##propName(void) const { return m_##variableName; } +/// Creates a read-only get property, does not use "get". +#define __READONLY_PROPERTY_PLAIN(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type propName(void) const { return m_##variableName; } +/// Creates a read-only get property by reference. +#define __READONLY_PROPERTY_BYREF(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type& get##propName(void) const { return m_##variableName; } + +/// Creates a get and set property. +#define __PROPERTY(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type get##propName(void) const { return m_##variableName; } \ + __forceinline void set##propName(type val) { m_##variableName = val; } +/// Creates a get and set property, does not use "get"/"set". +#define __PROPERTY_PLAIN(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type propName(void) const { return m_##variableName; } \ + __forceinline void propName(type val) { m_##variableName = val; } +/// Creates a get and set property by reference. +#define __PROPERTY_BYREF(type, variableName, propName) \ + private: type m_##variableName; \ + public: __forceinline type& get##propName(void) const { return m_##variableName; } \ + __forceinline void set##propName(type& val) { m_##variableName = val; } + +#endif // __DEFINES_H__ diff --git a/HostMain.cpp b/HostMain.cpp new file mode 100644 index 00000000..894e9e07 --- /dev/null +++ b/HostMain.cpp @@ -0,0 +1,236 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "HostMain.h" +#include "host/Host.h" +#include "host/calibrate/HostCal.h" +#include "Log.h" + +using namespace network; +using namespace modem; +using namespace lookups; + +#include +#include +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- +#define IS(s) (::strcmp(argv[i], s) == 0) + +// --------------------------------------------------------------------------- +// Global Variables +// --------------------------------------------------------------------------- + +int g_signal = 0; +bool g_calibrate = false; +std::string g_progExe = std::string(__EXE_NAME__); +std::string g_iniFile = std::string(DEFAULT_CONF_FILE); +std::string g_lockFile = std::string(DEFAULT_LOCK_FILE); + +bool g_foreground = false; +bool g_killed = false; + +bool g_fireDMRBeacon = false; +bool g_fireP25Control = false; +bool g_interruptP25Control = false; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +#if !defined(_WIN32) && !defined(_WIN64) +static void sigHandler(int signum) +{ + g_killed = true; + g_signal = signum; +} +#endif + +void fatal(const char* msg, ...) +{ + char buffer[400U]; + + va_list vl; + va_start(vl, msg); + + ::vsprintf(buffer + ::strlen(buffer), msg, vl); + + va_end(vl); + + ::fprintf(stderr, "%s: %s\n", g_progExe.c_str(), buffer); + exit(EXIT_FAILURE); +} + +void usage(const char* message, const char* arg) +{ + ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); + if (message != NULL) { + ::fprintf(stderr, "%s: ", g_progExe.c_str()); + ::fprintf(stderr, message, arg); + ::fprintf(stderr, "\n\n"); + } + + ::fprintf(stdout, "usage: %s [-v] [-f] [--cal] [-c ]\n\n" + " -f foreground mode\n" + " --cal calibration mode\n" + "\n" + " -v show version information\n" + " -h show this screen\n" + " -- stop handling options\n", + g_progExe.c_str()); + exit(EXIT_FAILURE); +} + +int checkArgs(int argc, char* argv[]) +{ + int i, p = 0; + + // iterate through arguments + for (i = 1; i <= argc; i++) + { + if (argv[i] == NULL) { + break; + } + + if (*argv[i] != '-') { + continue; + } + else if (IS("--")) { + ++p; + break; + } + else if (IS("-f")) { + g_foreground = true; + } + else if (IS("--cal")) { + g_calibrate = true; + } + else if (IS("-c")) { + if (argc-- <= 0) + usage("error: %s", "must specify the configuration file to use"); + g_iniFile = std::string(argv[++i]); + + if (g_iniFile == "") + usage("error: %s", "configuration file cannot be blank!"); + + p += 2; + } + else if (IS("-v")) { + ::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__); + if (argc == 2) + exit(EXIT_SUCCESS); + } + else if (IS("-h")) { + usage(NULL, NULL); + if (argc == 2) + exit(EXIT_SUCCESS); + } + else { + usage("unrecognized option `%s'", argv[i]); + } + } + + if (p < 0 || p > argc) { + p = 0; + } + + return ++p; +} + +// --------------------------------------------------------------------------- +// Program Entry Point +// --------------------------------------------------------------------------- + +int main(int argc, char** argv) +{ + if (argv[0] != NULL && *argv[0] != 0) + g_progExe = std::string(argv[0]); + + if (argc > 1) { + // check arguments + int i = checkArgs(argc, argv); + if (i < argc) { + argc -= i; + argv += i; + } + else { + argc--; + argv++; + } + } + +#if !defined(_WIN32) && !defined(_WIN64) + ::signal(SIGINT, sigHandler); + ::signal(SIGTERM, sigHandler); + ::signal(SIGHUP, sigHandler); +#endif + + int ret = 0; + + do { + g_signal = 0; + + if (g_calibrate) { + HostCal* cal = new HostCal(g_iniFile); + ret = cal->run(); + delete cal; + } + else { + Host* host = new Host(g_iniFile); + ret = host->run(); + delete host; + } + + if (g_signal == 2) + ::LogInfoEx(LOG_HOST, "Exited on receipt of SIGINT"); + + if (g_signal == 15) + ::LogInfoEx(LOG_HOST, "Exited on receipt of SIGTERM"); + + if (g_signal == 1) + ::LogInfoEx(LOG_HOST, "Restarting on receipt of SIGHUP"); + } while (g_signal == 1); + + ::LogFinalise(); + ::ActivityLogFinalise(); + + return ret; +} diff --git a/HostMain.h b/HostMain.h new file mode 100644 index 00000000..50198a1e --- /dev/null +++ b/HostMain.h @@ -0,0 +1,56 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HOST_MAIN_H__) +#define __HOST_MAIN_H__ + +#include "Defines.h" + +#include + +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + +extern int g_signal; +extern std::string g_progExe; +extern std::string g_iniFile; +extern std::string g_lockFile; + +extern bool g_foreground; +extern bool g_killed; + +extern bool g_fireDMRBeacon; +extern bool g_fireP25Control; +extern bool g_interruptP25Control; + +extern HOST_SW_API void fatal(const char* msg, ...); + +#endif // __HOST_MAIN_H__ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..b017086e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,264 @@ +The GNU General Public License, Version 2, June 1991 (GPLv2) +============================================================ + +> Copyright (C) 1989, 1991 Free Software Foundation, Inc. +> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + + +Preamble +-------- + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to most +of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you can +do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a +fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show them +these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer +you this license which gives you legal permission to copy, distribute and/or +modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced by +others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish +to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's free +use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + +Terms And Conditions For Copying, Distribution And Modification +--------------------------------------------------------------- + +**0.** This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program or +work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included without +limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is not +restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the Program +a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +**2.** You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you also +meet all of these conditions: + +* **a)** You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + +* **b)** You must cause any work that you distribute or publish, that in whole + or in part contains or is derived from the Program or any part thereof, to + be licensed as a whole at no charge to all third parties under the terms of + this License. + +* **c)** If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the entire whole, +and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +**3.** You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and 2 +above provided that you also do one of the following: + +* **a)** Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above on + a medium customarily used for software interchange; or, + +* **b)** Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + +* **c)** Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only for + noncommercial distribution and only if you received the program in object + code or executable form with such an offer, in accord with Subsection b + above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all the +source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source code +from the same place counts as distribution of the source code, even though third +parties are not compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you do +not accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +**7.** If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution of +the Program by all those who receive copies directly or indirectly through you, +then the only way you could satisfy both it and this License would be to refrain +entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and the +section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +**9.** The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose any +version ever published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + + +No Warranty +----------- + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/Log.cpp b/Log.cpp new file mode 100644 index 00000000..9e269d4a --- /dev/null +++ b/Log.cpp @@ -0,0 +1,327 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Log.h" +#include "network/Network.h" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +#define EOL "\n" +#else +#define EOL "\r\n" +#endif + +// --------------------------------------------------------------------------- +// Global Variables +// --------------------------------------------------------------------------- + +static uint32_t m_fileLevel = 0U; +static std::string m_filePath; +static std::string m_actFilePath; +static std::string m_fileRoot; +static std::string m_actFileRoot; + +static network::Network* m_network; + +static FILE* m_fpLog = NULL; +static FILE* m_actFpLog = NULL; + +static uint32_t m_displayLevel = 2U; + +static struct tm m_tm; +static struct tm m_actTm; + +static char LEVELS[] = " DMIWEF"; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- +/// +/// Helper to open the detailed log file, file handle. +/// +/// True, if log file is opened, otherwise false. +static bool LogOpen() +{ + if (m_fileLevel == 0U) + return true; + + time_t now; + ::time(&now); + + struct tm* tm = ::gmtime(&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 != NULL) + return true; + } + else { + if (m_fpLog != NULL) + ::fclose(m_fpLog); + } + + char filename[100U]; +#if defined(_WIN32) || defined(_WIN64) + ::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); +#else + ::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); +#endif + m_fpLog = ::fopen(filename, "a+t"); + m_tm = *tm; + + return m_fpLog != NULL; +} + +/// +/// Helper to open the activity log file, file handle. +/// +/// True, if log file is opened, otherwise false. +static bool ActivityLogOpen() +{ + time_t now; + ::time(&now); + + struct tm* tm = ::gmtime(&now); + + if (tm->tm_mday == m_actTm.tm_mday && tm->tm_mon == m_actTm.tm_mon && tm->tm_year == m_actTm.tm_year) { + if (m_actFpLog != NULL) + return true; + } + else { + if (m_actFpLog != NULL) + ::fclose(m_actFpLog); + } + + char filename[100U]; +#if defined(_WIN32) || defined(_WIN64) + ::sprintf(filename, "%s\\%s-%04d-%02d-%02d.activity.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#else + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.activity.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#endif + m_actFpLog = ::fopen(filename, "a+t"); + m_actTm = *tm; + + return m_actFpLog != NULL; +} + +/// +/// Initializes the activity log. +/// +/// Full-path to the activity log file. +/// Prefix of the activity log file name. +bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot) +{ + m_actFilePath = filePath; + m_actFileRoot = fileRoot; + m_network = NULL; + + return ::ActivityLogOpen(); +} + +/// +/// Finalizes the activity log. +/// +void ActivityLogFinalise() +{ + if (m_actFpLog != NULL) + ::fclose(m_actFpLog); +} + +/// +/// Writes a new entry to the activity log. +/// +/// Digital mode (usually P25 or DMR). +/// Flag indicating that the entry was generated from an RF event. +/// Formatted string to write to activity log. +void ActivityLog(const char *mode, const bool sourceRf, const char* msg, ...) +{ + assert(mode != NULL); + assert(msg != NULL); + + char buffer[400U]; +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + ::GetSystemTime(&st); + + ::sprintf(buffer, "A: %04u-%02u-%02u %02u:%02u:%02u.%03u %s %s ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, mode, (sourceRf) ? "RF" : "Net"); +#else + struct timeval now; + ::gettimeofday(&now, NULL); + + struct tm* tm = ::gmtime(&now.tv_sec); + + ::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, now.tv_usec / 1000U, mode, (sourceRf) ? "RF" : "Net"); +#endif + + va_list vl; + va_start(vl, msg); + + ::vsprintf(buffer + ::strlen(buffer), msg, vl); + + va_end(vl); + + bool ret = ::ActivityLogOpen(); + if (!ret) + return; + + ::fprintf(m_actFpLog, "%s\n", buffer); + ::fflush(m_actFpLog); + + if (m_network != NULL) { + m_network->writeActLog(buffer); + } + + if (2U >= m_fileLevel && m_fileLevel != 0U) { + bool ret = ::LogOpen(); + if (!ret) + return; + + ::fprintf(m_fpLog, "%s\n", buffer); + ::fflush(m_fpLog); + } + + if (2U >= m_displayLevel && m_displayLevel != 0U) { + ::fprintf(stdout, "%s" EOL, buffer); + ::fflush(stdout); + } +} + +/// +/// Sets the instance of the Network class to transfer the activity log with. +/// +/// Instance of the Network class. +void ActivityLogSetNetwork(void* network) +{ + // 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::Network*)network; +} + +/// +/// Initializes the detailed log. +/// +/// Full-path to the detailed log file. +/// Prefix of the detailed log file name. +/// File logging level. +/// Console logging level. +bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel) +{ + m_filePath = filePath; + m_fileRoot = fileRoot; + m_fileLevel = fileLevel; + m_displayLevel = displayLevel; + return ::LogOpen(); +} + +/// +/// Finalizes the detailed log. +/// +void LogFinalise() +{ + if (m_fpLog != NULL) + ::fclose(m_fpLog); +} + +/// +/// Writes a new entry to the detailed log. +/// +/// Log level. +/// Module name the log entry was genearted from. +/// Formatted string to write to activity log. +void Log(uint32_t level, const char *module, const char* fmt, ...) +{ + assert(fmt != NULL); + + char buffer[300U]; +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + ::GetSystemTime(&st); + + if (module != NULL) { + ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u (%s) ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, module); + } + else { + ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + } +#else + struct timeval now; + ::gettimeofday(&now, NULL); + + struct tm* tm = ::gmtime(&now.tv_sec); + + if (module != NULL) { + ::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, now.tv_usec / 1000U, module); + } + 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, now.tv_usec / 1000U); + } +#endif + + va_list vl; + va_start(vl, fmt); + + ::vsprintf(buffer + ::strlen(buffer), fmt, vl); + + va_end(vl); + + if (level >= m_fileLevel && m_fileLevel != 0U) { + bool ret = ::LogOpen(); + if (!ret) + return; + + ::fprintf(m_fpLog, "%s\n", buffer); + ::fflush(m_fpLog); + } + + if (level >= m_displayLevel && m_displayLevel != 0U) { + ::fprintf(stdout, "%s" EOL, buffer); + ::fflush(stdout); + } + + if (level >= 6U) { // Fatal + ::fclose(m_fpLog); + exit(1); + } +} diff --git a/Log.h b/Log.h new file mode 100644 index 00000000..d6396ec2 --- /dev/null +++ b/Log.h @@ -0,0 +1,82 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__LOG_H__) +#define __LOG_H__ + +#include "Defines.h" + +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +#define LOG_HOST "HOST" +#define LOG_RCON "RCON" +#define LOG_MODEM "MODEM" +#define LOG_RF "RF" +#define LOG_NET "NET" +#define LOG_P25 "P25" +#define LOG_DMR "DMR" +#define LOG_CAL "CAL" + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#define LogDebug(_module, fmt, ...) Log(1U, _module, fmt, ##__VA_ARGS__) +#define LogMessage(_module, fmt, ...) Log(2U, _module, fmt, ##__VA_ARGS__) +#define LogInfo(fmt, ...) Log(3U, NULL, fmt, ##__VA_ARGS__) +#define LogInfoEx(_module, fmt, ...) Log(3U, _module, fmt, ##__VA_ARGS__) +#define LogWarning(_module, fmt, ...) Log(4U, _module, fmt, ##__VA_ARGS__) +#define LogError(_module, fmt, ...) Log(5U, _module, fmt, ##__VA_ARGS__) +#define LogFatal(_module, fmt, ...) Log(6U, _module, fmt, ##__VA_ARGS__) + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/// Initializes the activity log. +extern HOST_SW_API bool ActivityLogInitialise(const std::string& filePath, const std::string& fileRoot); +/// Finalizes the activity log. +extern HOST_SW_API void ActivityLogFinalise(); +/// Writes a new entry to the activity log. +extern HOST_SW_API void ActivityLog(const char* mode, const bool sourceRf, const char* msg, ...); +/// Sets the instance of the Network class to transfer the activity log with. +extern HOST_SW_API void ActivityLogSetNetwork(void* network); + +/// Initializes the detailed log. +extern HOST_SW_API bool LogInitialise(const std::string& filePath, const std::string& fileRoot, uint32_t fileLevel, uint32_t displayLevel); +/// Finalizes the detailed log. +extern HOST_SW_API void LogFinalise(); +/// Writes a new entry to the detailed log. +extern HOST_SW_API void Log(uint32_t level, const char* module, const char* fmt, ...); + +#endif // __LOG_H__ diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..659505b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +CC = gcc +CXX = g++ +CFLAGS = -g -O3 -Wall -std=c++0x -pthread -I. +LIBS = -lpthread +LDFLAGS = -g + +OBJECTS = \ + edac/AMBEFEC.o \ + edac/BCH.o \ + edac/BPTC19696.o \ + edac/CRC.o \ + edac/Golay2087.o \ + edac/Golay24128.o \ + edac/Hamming.o \ + edac/QR1676.o \ + edac/RS129.o \ + edac/RS634717.o \ + edac/SHA256.o \ + dmr/acl/AccessControl.o \ + dmr/data/Data.o \ + dmr/data/DataHeader.o \ + dmr/data/EMB.o \ + dmr/data/EmbeddedData.o \ + dmr/edac/Trellis.o \ + dmr/lc/CSBK.o \ + dmr/lc/FullLC.o \ + dmr/lc/LC.o \ + dmr/lc/ShortLC.o \ + dmr/Control.o \ + dmr/DataPacket.o \ + dmr/Slot.o \ + dmr/SlotType.o \ + dmr/Sync.o \ + dmr/VoicePacket.o \ + lookups/IdenTableLookup.o \ + lookups/RadioIdLookup.o \ + lookups/RSSIInterpolator.o \ + lookups/TalkgroupIdLookup.o \ + p25/acl/AccessControl.o \ + p25/data/DataBlock.o \ + p25/data/DataHeader.o \ + p25/data/LowSpeedData.o \ + p25/edac/Trellis.o \ + p25/lc/LC.o \ + p25/lc/TDULC.o \ + p25/lc/TSBK.o \ + p25/Audio.o \ + p25/Control.o \ + p25/DataPacket.o \ + p25/NID.o \ + p25/Sync.o \ + p25/TrunkPacket.o \ + p25/P25Utils.o \ + p25/VoicePacket.o \ + modem/SerialController.o \ + modem/Modem.o \ + modem/NullModem.o \ + network/UDPSocket.o \ + network/RemoteControl.o \ + network/BaseNetwork.o \ + network/Network.o \ + yaml/Yaml.o \ + host/calibrate/Console.o \ + host/calibrate/HostCal.o \ + host/Host.o \ + Log.o \ + Mutex.o \ + Thread.o \ + Timer.o \ + StopWatch.o \ + Utils.o \ + HostMain.o + +BIN = ../bin + +all: dvmhost + -cp -f dvmhost $(BIN) + if [ ! -e $(BIN)/config.yml ]; then cp -f config.yml $(BIN); fi + if [ ! -e $(BIN)/rid_acl.dat ]; then cp -f rid_acl.dat $(BIN); fi + if [ ! -e $(BIN)/tg_acl.dat ]; then cp -f tg_acl.dat $(BIN); fi + if [ ! -e $(BIN)/iden_table.dat ]; then cp -f iden_table.dat $(BIN); fi + if [ ! -e $(BIN)/RSSI.dat ]; then cp -f RSSI.dat $(BIN); fi + +dvmhost: $(OBJECTS) + $(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o dvmhost + +%.o: %.cpp + $(CXX) $(CFLAGS) -c -o $@ $< + +clean: + $(RM) dvmhost *.o *.d *.bak *~ edac/*.o dmr/*.o dmr/acl/*.o dmr/data/*.o dmr/edac/*.o dmr/lc/*.o p25/*.o p25/acl/*.o p25/data/*.o p25/edac/*.o p25/lc/*.o lookups/*.o modem/*.o network/*.o yaml/*.o host/*.o host/calibrate/*.o diff --git a/Makefile.arm b/Makefile.arm new file mode 100644 index 00000000..eb6789e8 --- /dev/null +++ b/Makefile.arm @@ -0,0 +1,91 @@ +CC = arm-linux-gnueabihf-gcc-4.9 +CXX = arm-linux-gnueabihf-g++-4.9 +CFLAGS = -g -O3 -Wall -std=c++0x -pthread -I. +LIBS = -lpthread +LDFLAGS = -g + +OBJECTS = \ + edac/AMBEFEC.o \ + edac/BCH.o \ + edac/BPTC19696.o \ + edac/CRC.o \ + edac/Golay2087.o \ + edac/Golay24128.o \ + edac/Hamming.o \ + edac/QR1676.o \ + edac/RS129.o \ + edac/RS634717.o \ + edac/SHA256.o \ + dmr/acl/AccessControl.o \ + dmr/data/Data.o \ + dmr/data/DataHeader.o \ + dmr/data/EMB.o \ + dmr/data/EmbeddedData.o \ + dmr/edac/Trellis.o \ + dmr/lc/CSBK.o \ + dmr/lc/FullLC.o \ + dmr/lc/LC.o \ + dmr/lc/ShortLC.o \ + dmr/Control.o \ + dmr/DataPacket.o \ + dmr/Slot.o \ + dmr/SlotType.o \ + dmr/Sync.o \ + dmr/VoicePacket.o \ + lookups/IdenTableLookup.o \ + lookups/RadioIdLookup.o \ + lookups/RSSIInterpolator.o \ + lookups/TalkgroupIdLookup.o \ + p25/acl/AccessControl.o \ + p25/data/DataBlock.o \ + p25/data/DataHeader.o \ + p25/data/LowSpeedData.o \ + p25/edac/Trellis.o \ + p25/lc/LC.o \ + p25/lc/TDULC.o \ + p25/lc/TSBK.o \ + p25/Audio.o \ + p25/Control.o \ + p25/DataPacket.o \ + p25/NID.o \ + p25/Sync.o \ + p25/TrunkPacket.o \ + p25/P25Utils.o \ + p25/VoicePacket.o \ + modem/SerialController.o \ + modem/Modem.o \ + modem/NullModem.o \ + network/UDPSocket.o \ + network/RemoteControl.o \ + network/BaseNetwork.o \ + network/Network.o \ + yaml/Yaml.o \ + host/calibrate/Console.o \ + host/calibrate/HostCal.o \ + host/Host.o \ + Log.o \ + Mutex.o \ + Thread.o \ + Timer.o \ + StopWatch.o \ + Utils.o \ + HostMain.o + +BIN = ../bin + +all: dvmhost + -cp -f dvmhost $(BIN) + if [ ! -e $(BIN)/config.yml ]; then cp -f config.yml $(BIN); fi + if [ ! -e $(BIN)/rid_acl.dat ]; then cp -f rid_acl.dat $(BIN); fi + if [ ! -e $(BIN)/tg_acl.dat ]; then cp -f tg_acl.dat $(BIN); fi + if [ ! -e $(BIN)/iden_table.dat ]; then cp -f iden_table.dat $(BIN); fi + if [ ! -e $(BIN)/RSSI.dat ]; then cp -f RSSI.dat $(BIN); fi + +dvmhost: $(OBJECTS) + $(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o dvmhost + +%.o: %.cpp + $(CXX) $(CFLAGS) -c -o $@ $< + +clean: + $(RM) dvmhost *.o *.d *.bak *~ edac/*.o dmr/*.o dmr/acl/*.o dmr/data/*.o dmr/edac/*.o dmr/lc/*.o p25/*.o p25/acl/*.o p25/data/*.o p25/edac/*.o p25/lc/*.o lookups/*.o modem/*.o network/*.o yaml/*.o host/*.o host/calibrate/*.o diff --git a/Makefile.rpi-arm b/Makefile.rpi-arm new file mode 100644 index 00000000..ecfec395 --- /dev/null +++ b/Makefile.rpi-arm @@ -0,0 +1,91 @@ +CC = /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc +CXX = /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ +CFLAGS = -g -O3 -Wall -std=c++0x -pthread -I. +LIBS = -lpthread +LDFLAGS = -g + +OBJECTS = \ + edac/AMBEFEC.o \ + edac/BCH.o \ + edac/BPTC19696.o \ + edac/CRC.o \ + edac/Golay2087.o \ + edac/Golay24128.o \ + edac/Hamming.o \ + edac/QR1676.o \ + edac/RS129.o \ + edac/RS634717.o \ + edac/SHA256.o \ + dmr/acl/AccessControl.o \ + dmr/data/Data.o \ + dmr/data/DataHeader.o \ + dmr/data/EMB.o \ + dmr/data/EmbeddedData.o \ + dmr/edac/Trellis.o \ + dmr/lc/CSBK.o \ + dmr/lc/FullLC.o \ + dmr/lc/LC.o \ + dmr/lc/ShortLC.o \ + dmr/Control.o \ + dmr/DataPacket.o \ + dmr/Slot.o \ + dmr/SlotType.o \ + dmr/Sync.o \ + dmr/VoicePacket.o \ + lookups/IdenTableLookup.o \ + lookups/RadioIdLookup.o \ + lookups/RSSIInterpolator.o \ + lookups/TalkgroupIdLookup.o \ + p25/acl/AccessControl.o \ + p25/data/DataBlock.o \ + p25/data/DataHeader.o \ + p25/data/LowSpeedData.o \ + p25/edac/Trellis.o \ + p25/lc/LC.o \ + p25/lc/TDULC.o \ + p25/lc/TSBK.o \ + p25/Audio.o \ + p25/Control.o \ + p25/DataPacket.o \ + p25/NID.o \ + p25/Sync.o \ + p25/TrunkPacket.o \ + p25/P25Utils.o \ + p25/VoicePacket.o \ + modem/SerialController.o \ + modem/Modem.o \ + modem/NullModem.o \ + network/UDPSocket.o \ + network/RemoteControl.o \ + network/BaseNetwork.o \ + network/Network.o \ + yaml/Yaml.o \ + host/calibrate/Console.o \ + host/calibrate/HostCal.o \ + host/Host.o \ + Log.o \ + Mutex.o \ + Thread.o \ + Timer.o \ + StopWatch.o \ + Utils.o \ + HostMain.o + +BIN = ../bin + +all: dvmhost + -cp -f dvmhost $(BIN) + if [ ! -e $(BIN)/config.yml ]; then cp -f config.yml $(BIN); fi + if [ ! -e $(BIN)/rid_acl.dat ]; then cp -f rid_acl.dat $(BIN); fi + if [ ! -e $(BIN)/tg_acl.dat ]; then cp -f tg_acl.dat $(BIN); fi + if [ ! -e $(BIN)/iden_table.dat ]; then cp -f iden_table.dat $(BIN); fi + if [ ! -e $(BIN)/RSSI.dat ]; then cp -f RSSI.dat $(BIN); fi + +dvmhost: $(OBJECTS) + $(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o dvmhost + +%.o: %.cpp + $(CXX) $(CFLAGS) -c -o $@ $< + +clean: + $(RM) dvmhost *.o *.d *.bak *~ edac/*.o dmr/*.o dmr/acl/*.o dmr/data/*.o dmr/edac/*.o dmr/lc/*.o p25/*.o p25/acl/*.o p25/data/*.o p25/edac/*.o p25/lc/*.o lookups/*.o modem/*.o network/*.o yaml/*.o host/*.o host/calibrate/*.o diff --git a/Mutex.cpp b/Mutex.cpp new file mode 100644 index 00000000..058b6ee9 --- /dev/null +++ b/Mutex.cpp @@ -0,0 +1,102 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Mutex.h" + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// Initializes a new instance of the Mutex class. +/// +Mutex::Mutex() : + m_handle() +{ + m_handle = ::CreateMutex(NULL, FALSE, NULL); +} + +/// +/// Finalizes a instance of the Mutex class. +/// +Mutex::~Mutex() +{ + ::CloseHandle(m_handle); +} + +/// +/// Locks the mutex. +/// +void Mutex::lock() +{ + ::WaitForSingleObject(m_handle, INFINITE); +} + +/// +/// Unlocks the mutex. +/// +void Mutex::unlock() +{ + ::ReleaseMutex(m_handle); +} +#else +/// +/// Initializes a new instance of the Mutex class. +/// +Mutex::Mutex() : + m_mutex(PTHREAD_MUTEX_INITIALIZER) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Mutex class. +/// +Mutex::~Mutex() +{ + /* stub */ +} + +/// +/// Locks the mutex. +/// +void Mutex::lock() +{ + ::pthread_mutex_lock(&m_mutex); +} + +/// +/// Unlocks the mutex. +/// +void Mutex::unlock() +{ + ::pthread_mutex_unlock(&m_mutex); +} +#endif diff --git a/Mutex.h b/Mutex.h new file mode 100644 index 00000000..28cc71b1 --- /dev/null +++ b/Mutex.h @@ -0,0 +1,66 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__MUTEX_H__) +#define __MUTEX_H__ + +#include "Defines.h" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a simple mutual exclusion locking mechanism. +// --------------------------------------------------------------------------- + +class HOST_SW_API Mutex { +public: + /// Initializes a new instance of the Mutex class. + Mutex(); + /// Finalizes a instance of the Mutex class. + ~Mutex(); + + /// Locks the mutex. + void lock(); + /// Unlocks the mutex. + void unlock(); + +private: +#if defined(_WIN32) || defined(_WIN64) + HANDLE m_handle; +#else + pthread_mutex_t m_mutex; +#endif +}; + +#endif // __MUTEX_H__ diff --git a/README.md b/README.md new file mode 100644 index 00000000..be51ea26 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Digital Voice Modem Host + +The DVM Host software provides the host computer implementation of a mixed-mode DMR/P25 or dedicated-mode DMR or P25 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. + +This project is a direct fork of the MMDVMHost (https://github.com/g4klx/MMDVMHost) project, and combines the MMDVMCal (https://github.com/g4klx/MMDVMCal) project into a single package. + +## Building + +Please see the various Makefile included in the project for more information. (All following information assumes familiarity with the standard Linux make system.) + +The DVM Host software does not have any specific library dependancies and is written to be as library-free as possible. A basic GCC install is usually all thats needed to compile. + +* Makefile - This makefile is used for building binaries for the native installed GCC. +* Makefile.arm - This makefile is used for cross-compiling for a ARM platform. + +Use the ```make``` command to build the software. + +## License + +This project is licensed under the GPLv2 License - see the [LICENSE.md](LICENSE.md) file for details + diff --git a/RSSI.dat b/RSSI.dat new file mode 100644 index 00000000..096ae875 --- /dev/null +++ b/RSSI.dat @@ -0,0 +1,11 @@ +# This file maps the raw RSSI values to dBm values to send to the DMR network. A number of data +# points should be entered and the software will use those to work out the in-between values. +# +# The format of the file is: +# Raw RSSI Value dBm Value +# +# For example +# 1134 -90 +# 1123 -100 +# 1000 -109 +# diff --git a/RingBuffer.h b/RingBuffer.h new file mode 100644 index 00000000..dd240e85 --- /dev/null +++ b/RingBuffer.h @@ -0,0 +1,223 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2006-2009,2012,2013,2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RING_BUFFER_H__) +#define __RING_BUFFER_H__ + +#include "Defines.h" +#include "Log.h" + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a circular buffer for storing data. +// --------------------------------------------------------------------------- + +template +class HOST_SW_API RingBuffer { +public: + /// Initializes a new instance of the RingBuffer class. + /// Length of ring buffer. + /// Name of buffer. + RingBuffer(uint32_t length, const char* name) : + m_length(length), + m_name(name), + m_buffer(NULL), + m_iPtr(0U), + m_oPtr(0U) + { + assert(length > 0U); + assert(name != NULL); + + m_buffer = new T[length]; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + /// Finalizes a instance of the RingBuffer class. + ~RingBuffer() + { + delete[] m_buffer; + } + + /// Adds data to the end of the ring buffer. + /// Data buffer. + /// Length of data in buffer. + /// True, if data is added to ring buffer, otherwise false. + bool addData(const T* buffer, uint32_t length) + { + if (length >= freeSpace()) { + LogError(LOG_HOST, "%s buffer overflow, clearing the buffer. (%u >= %u)", m_name, length, freeSpace()); + clear(); + return false; + } + + for (uint32_t i = 0U; i < length; i++) { + m_buffer[m_iPtr++] = buffer[i]; + + if (m_iPtr == m_length) + m_iPtr = 0U; + } + + return true; + } + + /// Gets data from the ring buffer. + /// Buffer to write data to be retrieved. + /// Length of data to retrieve. + /// True, if data is read from ring buffer, otherwise false. + bool getData(T* buffer, uint32_t length) + { + if (dataSize() < length) { + LogError(LOG_HOST, "**** Underflow in %s ring buffer, %u < %u", m_name, dataSize(), length); + return false; + } + + for (uint32_t i = 0U; i < length; i++) { + buffer[i] = m_buffer[m_oPtr++]; + + if (m_oPtr == m_length) + m_oPtr = 0U; + } + + return true; + } + + /// Gets data from ring buffer without moving buffer pointers. + /// Buffer to write data to be retrieved. + /// Length of data to retrieve. + /// True, if data is read from ring buffer, otherwise false. + bool peek(T* buffer, uint32_t length) + { + if (dataSize() < length) { + LogError(LOG_HOST, "**** Underflow peek in %s ring buffer, %u < %u", m_name, dataSize(), length); + return false; + } + + uint32_t ptr = m_oPtr; + for (uint32_t i = 0U; i < length; i++) { + buffer[i] = m_buffer[ptr++]; + + if (ptr == m_length) + ptr = 0U; + } + + return true; + } + + /// Clears ring buffer and resets data pointers. + void clear() + { + m_iPtr = 0U; + m_oPtr = 0U; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + /// Resizes the ring buffer to the specified length. + /// New length of the ring buffer. + void resize(uint32_t length) + { + clear(); + + delete[] m_buffer; + + m_length = length; + m_buffer = new T[length]; + + clear(); + } + + /// Returns the currently available space in the ring buffer. + /// Space free in the ring buffer. + uint32_t freeSpace() const + { + uint32_t len = m_length; + + if (m_oPtr > m_iPtr) + len = m_oPtr - m_iPtr; + else if (m_iPtr > m_oPtr) + len = m_length - (m_iPtr - m_oPtr); + + if (len > m_length) + len = 0U; + + return len; + } + + /// Returns the size of the data currently stored in the ring buffer. + /// Size of data stored in the ring buffer. + uint32_t dataSize() const + { + return m_length - freeSpace(); + } + + /// Gets the length of the ring buffer. + /// Length of ring buffer. + uint32_t length() const + { + return m_length; + } + + /// Helper to test if the given length of data would fit in the ring buffer. + /// True, if specified length will fit in buffer, otherwise false. + bool hasSpace(uint32_t length) const + { + return freeSpace() > length; + } + + /// Helper to return whether the ring buffer contains data. + /// True, if ring buffer contains data, otherwise false. + bool hasData() const + { + return m_oPtr != m_iPtr; + } + + /// Helper to return whether the ring buffer is empty or not. + /// True, if the ring buffer is empty, otherwise false. + bool isEmpty() const + { + return m_oPtr == m_iPtr; + } + +private: + uint32_t m_length; + + const char* m_name; + + T* m_buffer; + + uint32_t m_iPtr; + uint32_t m_oPtr; +}; + +#endif // __RING_BUFFER_H__ diff --git a/StopWatch.cpp b/StopWatch.cpp new file mode 100644 index 00000000..c68a6f7d --- /dev/null +++ b/StopWatch.cpp @@ -0,0 +1,157 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "StopWatch.h" + +#if !defined(_WIN32) || !defined(_WIN64) +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// Initializes a new instance of the StopWatch class. +/// +StopWatch::StopWatch() : + m_frequencyS(), + m_frequencyMS(), + m_start() +{ + ::QueryPerformanceFrequency(&m_frequencyS); + + m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL; +} + +/// +/// Finalizes a instance of the StopWatch class. +/// +StopWatch::~StopWatch() +{ + /* stub */ +} + +/// +/// Gets the current running time. +/// +/// +ulong64_t StopWatch::time() const +{ + LARGE_INTEGER now; + ::QueryPerformanceCounter(&now); + + return (ulong64_t)(now.QuadPart / m_frequencyMS.QuadPart); +} + +/// +/// Starts the stopwatch. +/// +/// +ulong64_t StopWatch::start() +{ + ::QueryPerformanceCounter(&m_start); + + return (ulong64_t)(m_start.QuadPart / m_frequencyS.QuadPart); +} + +/// +/// Gets the elpased time since the stopwatch started. +/// +/// +uint32_t StopWatch::elapsed() +{ + LARGE_INTEGER now; + ::QueryPerformanceCounter(&now); + + LARGE_INTEGER temp; + temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000; + + return (uint32_t)(temp.QuadPart / m_frequencyS.QuadPart); +} +#else +/// +/// Initializes a new instance of the StopWatch class. +/// +StopWatch::StopWatch() : + m_startMS(0ULL) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the StopWatch class. +/// +StopWatch::~StopWatch() +{ + /* stub */ +} + +/// +/// Gets the current running time. +/// +/// +ulong64_t StopWatch::time() const +{ + struct timeval now; + ::gettimeofday(&now, NULL); + + return now.tv_sec * 1000ULL + now.tv_usec / 1000ULL; +} + +/// +/// Starts the stopwatch. +/// +/// +ulong64_t StopWatch::start() +{ + struct timespec now; + ::clock_gettime(CLOCK_MONOTONIC, &now); + + m_startMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; + + return m_startMS; +} + +/// +/// Gets the elpased time since the stopwatch started. +/// +/// +uint32_t StopWatch::elapsed() +{ + struct timespec now; + ::clock_gettime(CLOCK_MONOTONIC, &now); + + ulong64_t nowMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; + + return nowMS - m_startMS; +} +#endif diff --git a/StopWatch.h b/StopWatch.h new file mode 100644 index 00000000..44d7d83a --- /dev/null +++ b/StopWatch.h @@ -0,0 +1,71 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__STOPWATCH_H__) +#define __STOPWATCH_H__ + +#include "Defines.h" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a stopwatch. +// --------------------------------------------------------------------------- + +class HOST_SW_API StopWatch { +public: + /// Initializes a new instance of the StopWatch class. + StopWatch(); + /// Finalizes a instance of the StopWatch class. + ~StopWatch(); + + /// Gets the current running time. + ulong64_t time() const; + + /// Starts the stopwatch. + ulong64_t start(); + /// Gets the elpased time since the stopwatch started. + uint32_t elapsed(); + +private: +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER m_frequencyS; + LARGE_INTEGER m_frequencyMS; + LARGE_INTEGER m_start; +#else + ulong64_t m_startMS; +#endif +}; + +#endif // __STOPWATCH_H__ diff --git a/Thread.cpp b/Thread.cpp new file mode 100644 index 00000000..64509dff --- /dev/null +++ b/Thread.cpp @@ -0,0 +1,164 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Thread.h" + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// Initializes a new instance of the Thread class. +/// +Thread::Thread() : + m_handle() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Thread class. +/// +Thread::~Thread() +{ + /* stub */ +} + +/// +/// Starts the thread execution. +/// +/// True, if thread started, otherwise false. +bool Thread::run() +{ + m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL); + + return m_handle != NULL; +} + +/// +/// +/// +void Thread::wait() +{ + ::WaitForSingleObject(m_handle, INFINITE); + + ::CloseHandle(m_handle); +} + +/// +/// +/// +/// +void Thread::sleep(uint32_t ms) +{ + ::Sleep(ms); +} +#else +/// +/// Initializes a new instance of the Thread class. +/// +Thread::Thread() : + m_thread() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Thread class. +/// +Thread::~Thread() +{ + /* stub */ +} + +/// +/// Starts the thread execution. +/// +/// True, if thread started, otherwise false. +bool Thread::run() +{ + return ::pthread_create(&m_thread, NULL, helper, this) == 0; +} + +/// +/// +/// +void Thread::wait() +{ + ::pthread_join(m_thread, NULL); +} + +/// +/// +/// +/// +void Thread::sleep(uint32_t ms) +{ + ::usleep(ms * 1000); +} +#endif + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// +/// +/// +/// +DWORD Thread::helper(LPVOID arg) +{ + Thread* p = (Thread*)arg; + + p->entry(); + + return 0UL; +} +#else +/// +/// +/// +/// +/// +void* Thread::helper(void* arg) +{ + Thread* p = (Thread*)arg; + + p->entry(); + + return NULL; +} +#endif diff --git a/Thread.h b/Thread.h new file mode 100644 index 00000000..09e6848e --- /dev/null +++ b/Thread.h @@ -0,0 +1,81 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__THREAD_H__) +#define __THREAD_H__ + +#include "Defines.h" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a simple threading mechanism. +// --------------------------------------------------------------------------- + +class HOST_SW_API Thread { +public: + /// Initializes a new instance of the Thread class. + Thread(); + /// Finalizes a instance of the Thread class. + virtual ~Thread(); + + /// Starts the thread execution. + virtual bool run(); + + /// User-defined function to run for the thread main. + virtual void entry() = 0; + + /// + virtual void wait(); + + /// + static void sleep(uint32_t ms); + +private: +#if defined(_WIN32) || defined(_WIN64) + HANDLE m_handle; +#else + pthread_t m_thread; +#endif + +#if defined(_WIN32) || defined(_WIN64) + /// + static DWORD __stdcall helper(LPVOID arg); +#else + /// + static void* helper(void* arg); +#endif +}; + +#endif // __THREAD_H__ diff --git a/Timer.cpp b/Timer.cpp new file mode 100644 index 00000000..0b0248d3 --- /dev/null +++ b/Timer.cpp @@ -0,0 +1,121 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009,2010,2015 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "Timer.h" + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Timer class. +/// +Timer::Timer() : + m_ticksPerSec(1000U), + m_timeout(0U), + m_timer(0U), + m_paused(false) +{ + /* stub */ +} + +/// +/// Initializes a new instance of the Timer class. +/// +/// +/// +/// +Timer::Timer(uint32_t ticksPerSec, uint32_t secs, uint32_t msecs) : + m_ticksPerSec(ticksPerSec), + m_timeout(0U), + m_timer(0U), + m_paused(false) +{ + assert(ticksPerSec > 0U); + + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + ulong64_t temp = (secs * 1000ULL + msecs) * m_ticksPerSec; + m_timeout = (uint32_t)(temp / 1000ULL + 1ULL); + } +} + +/// +/// Finalizes a instance of the Timer class. +/// +Timer::~Timer() +{ + /* stub */ +} + +/// +/// Sets the timeout for the timer. +/// +/// +/// +void Timer::setTimeout(uint32_t secs, uint32_t msecs) +{ + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + ulong64_t temp = (secs * 1000ULL + msecs) * m_ticksPerSec; + m_timeout = (uint32_t)(temp / 1000ULL + 1ULL); + } + else { + m_timeout = 0U; + m_timer = 0U; + } +} + +/// +/// Gets the timeout for the timer. +/// +/// +uint32_t Timer::getTimeout() const +{ + if (m_timeout == 0U) + return 0U; + + return (m_timeout - 1U) / m_ticksPerSec; +} + +/// +/// Gets the current time for the timer. +/// +/// +uint32_t Timer::getTimer() const +{ + if (m_timer == 0U) + return 0U; + + return (m_timer - 1U) / m_ticksPerSec; +} diff --git a/Timer.h b/Timer.h new file mode 100644 index 00000000..9dcb04e9 --- /dev/null +++ b/Timer.h @@ -0,0 +1,153 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009,2010,2011,2014 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__TIMER_H__) +#define __TIMER_H__ + +#include "Defines.h" + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements a timer. +// --------------------------------------------------------------------------- + +class HOST_SW_API Timer { +public: + /// Initializes a new instance of the Timer class. + Timer(); + /// Initializes a new instance of the Timer class. + Timer(uint32_t ticksPerSec, uint32_t secs = 0U, uint32_t msecs = 0U); + /// Finalizes a instance of the Timer class. + ~Timer(); + + /// Sets the timeout for the timer. + void setTimeout(uint32_t secs, uint32_t msecs = 0U); + + /// Gets the timeout for the timer. + uint32_t getTimeout() const; + /// Gets the current time for the timer. + uint32_t getTimer() const; + + /// Gets the currently remaining time for the timer. + /// Amount of time remaining before the timeout. + uint32_t getRemaining() + { + if (m_timeout == 0U || m_timer == 0U) + return 0U; + + if (m_timer >= m_timeout) + return 0U; + + return (m_timeout - m_timer) / m_ticksPerSec; + } + + /// Flag indicating whether the timer is running. + /// True, if the timer is still running, otherwise false. + bool isRunning() + { + return m_timer > 0U; + } + + /// Flag indicating whether the timer is paused. + /// True, if the timer is paused, otherwise false. + bool isPaused() + { + return m_paused; + } + + /// Starts the timer. + /// + /// + void start(uint32_t secs, uint32_t msecs = 0U) + { + setTimeout(secs, msecs); + + start(); + } + + /// Starts the timer. + void start() + { + if (m_timeout > 0U) + m_timer = 1U; + m_paused = false; + } + + /// Stops the timer. + void stop() + { + m_timer = 0U; + m_paused = false; + } + + /// Pauses the timer. + void pause() + { + m_paused = true; + } + + /// Resumes the timer. + void resume() + { + m_paused = false; + } + + /// Flag indicating whether or not the timer has reached timeout and expired. + /// True, if the timer is expired, otherwise false. + bool hasExpired() + { + if (m_timeout == 0U || m_timer == 0U) + return false; + + if (m_timer >= m_timeout) + return true; + + return false; + } + + /// Updates the timer by the passed number of ticks. + /// + void clock(uint32_t ticks = 1U) + { + if (m_paused) + return; + if (m_timer > 0U && m_timeout > 0U) + m_timer += ticks; + } + +private: + uint32_t m_ticksPerSec; + uint32_t m_timeout; + uint32_t m_timer; + + bool m_paused; +}; + +#endif // __TIMER_H__ diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 00000000..6a861953 --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,341 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009,2014,2015,2016 Jonathan Naylor, G4KLX +* Copyright (C) 2018-2020 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Utils.h" +#include "Log.h" + +#include +#include + +// --------------------------------------------------------------------------- +// Constants/Macros +// --------------------------------------------------------------------------- + +const uint8_t BITS_TABLE[] = { +# define B2(n) n, n+1, n+1, n+2 +# define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2) +# define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2) + B6(0), B6(1), B6(1), B6(2) +}; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- +/// +/// Displays the host version. +/// +void getHostVersion() +{ + LogInfo(__PROG_NAME__ " %s (built %s)", __VER__, __BUILD__); +} + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +/// +void Utils::dump(const std::string& title, const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + + dump(2U, title, data, length); +} + +/// +/// +/// +/// +/// +/// +/// +void Utils::dump(int level, const std::string& title, const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + + ::Log(level, "DUMP", "%s", title.c_str()); + + uint32_t offset = 0U; + + while (length > 0U) { + std::string output; + + uint32_t bytes = (length > 16U) ? 16U : length; + + for (unsigned i = 0U; i < bytes; i++) { + char temp[10U]; + ::sprintf(temp, "%02X ", data[offset + i]); + output += temp; + } + + for (uint32_t i = bytes; i < 16U; i++) + output += " "; + + output += " *"; + + for (unsigned i = 0U; i < bytes; i++) { + uint8_t c = data[offset + i]; + + if (::isprint(c)) + output += c; + else + output += '.'; + } + + output += '*'; + + ::Log(level, "DUMP", "%04X: %s", offset, output.c_str()); + + offset += 16U; + + if (length >= 16U) + length -= 16U; + else + length = 0U; + } +} + +/// +/// +/// +/// +/// +/// +void Utils::dump(const std::string& title, const bool* bits, uint32_t length) +{ + assert(bits != NULL); + + dump(2U, title, bits, length); +} + +/// +/// +/// +/// +/// +/// +/// +void Utils::dump(int level, const std::string& title, const bool* bits, uint32_t length) +{ + assert(bits != NULL); + + uint8_t bytes[100U]; + uint32_t nBytes = 0U; + for (uint32_t n = 0U; n < length; n += 8U, nBytes++) + bitsToByteBE(bits + n, bytes[nBytes]); + + dump(level, title, bytes, nBytes); +} + +/// +/// +/// +/// +/// +void Utils::byteToBitsBE(uint8_t byte, bool* bits) +{ + assert(bits != NULL); + + bits[0U] = (byte & 0x80U) == 0x80U; + bits[1U] = (byte & 0x40U) == 0x40U; + bits[2U] = (byte & 0x20U) == 0x20U; + bits[3U] = (byte & 0x10U) == 0x10U; + bits[4U] = (byte & 0x08U) == 0x08U; + bits[5U] = (byte & 0x04U) == 0x04U; + bits[6U] = (byte & 0x02U) == 0x02U; + bits[7U] = (byte & 0x01U) == 0x01U; +} + +/// +/// +/// +/// +/// +void Utils::byteToBitsLE(uint8_t byte, bool* bits) +{ + assert(bits != NULL); + + bits[0U] = (byte & 0x01U) == 0x01U; + bits[1U] = (byte & 0x02U) == 0x02U; + bits[2U] = (byte & 0x04U) == 0x04U; + bits[3U] = (byte & 0x08U) == 0x08U; + bits[4U] = (byte & 0x10U) == 0x10U; + bits[5U] = (byte & 0x20U) == 0x20U; + bits[6U] = (byte & 0x40U) == 0x40U; + bits[7U] = (byte & 0x80U) == 0x80U; +} + +/// +/// +/// +/// +/// +void Utils::bitsToByteBE(const bool* bits, uint8_t& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x80U : 0x00U; + byte |= bits[1U] ? 0x40U : 0x00U; + byte |= bits[2U] ? 0x20U : 0x00U; + byte |= bits[3U] ? 0x10U : 0x00U; + byte |= bits[4U] ? 0x08U : 0x00U; + byte |= bits[5U] ? 0x04U : 0x00U; + byte |= bits[6U] ? 0x02U : 0x00U; + byte |= bits[7U] ? 0x01U : 0x00U; +} + +/// +/// +/// +/// +/// +void Utils::bitsToByteLE(const bool* bits, uint8_t& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x01U : 0x00U; + byte |= bits[1U] ? 0x02U : 0x00U; + byte |= bits[2U] ? 0x04U : 0x00U; + byte |= bits[3U] ? 0x08U : 0x00U; + byte |= bits[4U] ? 0x10U : 0x00U; + byte |= bits[5U] ? 0x20U : 0x00U; + byte |= bits[6U] ? 0x40U : 0x00U; + byte |= bits[7U] ? 0x80U : 0x00U; +} + +/// +/// +/// +/// +/// +/// +/// +uint32_t Utils::getBits(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop) +{ + assert(in != NULL); + assert(out != NULL); + + uint32_t n = 0U; + for (uint32_t i = start; i < stop; i++, n++) { + bool b = READ_BIT(in, i); + WRITE_BIT(out, n, b); + } + + return n; +} + +/// +/// +/// +/// +/// +/// +/// +uint32_t Utils::getBitRange(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t length) +{ + return getBits(in, out, start, start + length); +} + +/// +/// +/// +/// +/// +/// +/// +uint32_t Utils::setBits(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop) +{ + assert(in != NULL); + assert(out != NULL); + + uint32_t n = 0U; + for (uint32_t i = start; i < stop; i++, n++) { + bool b = READ_BIT(in, n); + WRITE_BIT(out, i, b); + } + + return n; +} + +/// +/// +/// +/// +/// +/// +/// +uint32_t Utils::setBitRange(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t length) +{ + return setBits(in, out, start, start + length); +} + +/// +/// Returns the count of bits in the passed 8 byte value. +/// +/// +/// +uint8_t Utils::countBits8(uint8_t bits) +{ + return BITS_TABLE[bits]; +} + +/// +/// Returns the count of bits in the passed 32 byte value. +/// +/// +/// +uint8_t Utils::countBits32(uint32_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + n += BITS_TABLE[p[2U]]; + n += BITS_TABLE[p[3U]]; + return n; +} + +/// +/// Returns the count of bits in the passed 64 byte value. +/// +/// +/// +uint8_t Utils::countBits64(ulong64_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + n += BITS_TABLE[p[2U]]; + n += BITS_TABLE[p[3U]]; + n += BITS_TABLE[p[4U]]; + n += BITS_TABLE[p[5U]]; + n += BITS_TABLE[p[6U]]; + n += BITS_TABLE[p[7U]]; + return n; +} diff --git a/Utils.h b/Utils.h new file mode 100644 index 00000000..b627a452 --- /dev/null +++ b/Utils.h @@ -0,0 +1,86 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009,2014,2015 by Jonathan Naylor, G4KLX +* Copyright (C) 2018-2019 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#if !defined(__UTILS_H__) +#define __UTILS_H__ + +#include "Defines.h" + +#include + +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + +extern "C" { + /// Displays the host version. + HOST_SW_API void getHostVersion(); +} + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements various helper utilities. +// --------------------------------------------------------------------------- + +class HOST_SW_API Utils { +public: + /// + static void dump(const std::string& title, const uint8_t* data, uint32_t length); + /// + static void dump(int level, const std::string& title, const uint8_t* data, uint32_t length); + + /// + static void dump(const std::string& title, const bool* bits, uint32_t length); + /// + static void dump(int level, const std::string& title, const bool* bits, uint32_t length); + + /// + static void byteToBitsBE(uint8_t byte, bool* bits); + /// + static void byteToBitsLE(uint8_t byte, bool* bits); + + /// + static void bitsToByteBE(const bool* bits, uint8_t& byte); + /// + static void bitsToByteLE(const bool* bits, uint8_t& byte); + + /// + static uint32_t getBits(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop); + /// + static uint32_t getBitRange(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t length); + /// + static uint32_t setBits(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop); + /// + static uint32_t setBitRange(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t length); + + /// Returns the count of bits in the passed 8 byte value. + static uint8_t countBits8(uint8_t bits); + /// Returns the count of bits in the passed 32 byte value. + static uint8_t countBits32(uint32_t bits); + /// Returns the count of bits in the passed 64 byte value. + static uint8_t countBits64(ulong64_t bits); +}; + +#endif // __UTILS_H__ diff --git a/config.yml b/config.yml new file mode 100644 index 00000000..0a39839c --- /dev/null +++ b/config.yml @@ -0,0 +1,128 @@ +daemon: true +log: + displayLevel: 1 + fileLevel: 1 + filePath: . + activityFilePath: . + fileRoot: DVM +network: + enable: true + id: 100000 + address: 127.0.0.1 + port: 62031 + rconAddress: 127.0.0.1 + rconPort: 9990 + jitter: 360 + talkgroupHang: 10 + password: "PASSWORD" + slot1: true + slot2: true + transferActivityLog: false + updateLookups: false + debug: false +protocols: + dmr: + enable: true + beacons: + enable: false + interval: 60 + duration: 3 + embeddedLCOnly: false + dumpTAData: true + dumpDataPacket: false + repeatDataPacket: true + callHang: 5 + txHang: 8 + queueSize: 10000 + verbose: true + debug: false + p25: + enable: true + preambleCount: 4 + control: + enable: false + continuous: false + interval: 60 + duration: 1 + voiceOnControl: false + inhibitIllegal: false + legacyGroupGrnt: true + verifyAff: false + verifyReg: false + dumpDataPacket: false + repeatDataPacket: true + callHang: 5 + noStatusAck: false + noMessageAck: true + statusCmd: + enable: true + radioCheck: 1 + radioInhibit: 0 + radioUninhibit: 0 + radioForceReg: 0 + radioForceDereg: 0 + silenceThreshold: 124 + queueSize: 16000 + verbose: true + debug: false +system: + identity: ABCD123 + timeout: 180 + duplex: true + modeHang: 10 +# rfModeHang: 10 +# netModeHang: 10 +# fixedMode: false + info: + latitude: 0.0 + longitude: 0.0 + height: 0 + power: 10 + location: "Repeater Site, USA" + config: + channelId: 2 + channelNo: 1 + voiceChNo: + - 1 + colorCode: 5 + nac: 293 + pSuperGroup: FFFF + netId: BB800 + sysId: 001 + rfssId: 1 + siteId: 1 + modem: + port: null + rxInvert: false + txInvert: false + pttInvert: false + dcBlocker: true + cosLockout: false + txDelay: 1 + dmrDelay: 7 + rxDCOffset: 0 + txDCOffset: 0 + rxLevel: 50 + txLevel: 50 +# cwIdTxLevel: 50 +# dmrTxLevel: 50 +# p25TxLevel: 50 + rssiMappingFile: RSSI.dat + disableOFlowReset: false + trace: false + debug: false + cwId: + enable: true + time: 15 + callsign: ABCD123 + iden_table: + file: iden_table.dat + time: 30 + radio_id: + file: rid_acl.dat + time: 2 + acl: false + talkgroup_id: + file: tg_acl.dat + time: 2 + acl: false \ No newline at end of file diff --git a/cpp.hint b/cpp.hint new file mode 100644 index 00000000..7dbe0605 --- /dev/null +++ b/cpp.hint @@ -0,0 +1,13 @@ +// Creates a read-only get property. +#define __READONLY_PROPERTY(type, variableName, propName) +// Creates a read-only get property, does not use "get". +#define __READONLY_PROPERTY_PLAIN(type, variableName, propName) +// Creates a read-only get property by reference. +#define __READONLY_PROPERTY_BYREF(type, variableName, propName) + +// Creates a get and set property. +#define __PROPERTY(type, variableName, propName) +// Creates a get and set property, does not use "get"/"set". +#define __PROPERTY_PLAIN(type, variableName, propName) +// Creates a get and set property by reference. +#define __PROPERTY_BYREF(type, variableName, propName) diff --git a/dmr/Control.cpp b/dmr/Control.cpp new file mode 100644 index 00000000..7a7ec585 --- /dev/null +++ b/dmr/Control.cpp @@ -0,0 +1,254 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX +* Copyright (C) 2017,2020 by Bryan Biedenkapp +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/Control.h" +#include "dmr/acl/AccessControl.h" +#include "dmr/lc/CSBK.h" +#include "Log.h" + +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Control class. +/// +/// DMR access color code. +/// Amount of hangtime for a DMR call. +/// Size of DMR data frame ring buffer. +/// +/// +/// Transmit timeout. +/// Amount of time to hang on the last talkgroup mode from RF. +/// Instance of the Modem class. +/// Instance of the BaseNetwork class. +/// Flag indicating full-duplex operation. +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +/// Instance of the CRSSIInterpolator class. +/// +/// +/// +/// Flag indicating whether DMR debug is enabled. +/// Flag indicating whether DMR verbose logging is enabled. +Control::Control(uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, + lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::RSSIInterpolator* rssi, + uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool debug, bool verbose) : + m_colorCode(colorCode), + m_modem(modem), + m_network(network), + m_slot1(NULL), + m_slot2(NULL), + m_ridLookup(ridLookup), + m_tidLookup(tidLookup), + m_verbose(verbose), + m_debug(debug) +{ + assert(modem != NULL); + assert(ridLookup != NULL); + assert(tidLookup != NULL); + assert(rssi != NULL); + + acl::AccessControl::init(m_ridLookup, m_tidLookup); + Slot::init(colorCode, embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, m_ridLookup, m_tidLookup, rssi, jitter); + + m_slot1 = new Slot(1U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, debug, verbose); + m_slot2 = new Slot(2U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, debug, verbose); +} + +/// +/// Finalizes a instance of the Control class. +/// +Control::~Control() +{ + delete m_slot2; + delete m_slot1; +} + +/// +/// Helper to process wakeup frames from the RF interface. +/// +/// DMR wakeup frame data. +/// True, if wakeup frames were processed, otherwise false. +bool Control::processWakeup(const uint8_t* data) +{ + assert(data != NULL); + + // wakeups always come in on slot 1 + if (data[0U] != TAG_DATA || data[1U] != (DMR_IDLE_RX | DMR_SYNC_DATA | DT_CSBK)) + return false; + + // generate a new CSBK and check validity + lc::CSBK csbk = lc::CSBK(m_debug); + bool valid = csbk.decode(data + 2U); + if (!valid) + return false; + + uint8_t csbko = csbk.getCSBKO(); + if (csbko != CSBKO_BSDWNACT) + return false; + + uint32_t srcId = csbk.getSrcId(); + + // check the srcId against the ACL control + bool ret = acl::AccessControl::validateSrcId(srcId); + if (!ret) { + LogError(LOG_RF, "DMR, invalid CSBKO_BSDWNACT, srcId = %u", srcId); + return false; + } + + if (m_verbose) { + LogMessage(LOG_RF, "DMR, CSBKO_BSDWNACT, srcId = %u", srcId); + } + return true; +} + +/// +/// Process a data frame for slot 1, from the RF interface. +/// +/// DMR data frame buffer. +/// Length of data frame buffer. +/// True, if data frame was processed, otherwise false. +bool Control::processFrame1(uint8_t *data, uint32_t len) +{ + assert(data != NULL); + + return m_slot1->processFrame(data, len); +} + +/// +/// Get a frame data for slot 1, from data ring buffer. +/// +/// Buffer to put retrieved DMR data frame data. +/// Length of data retrieved from DMR ring buffer. +uint32_t Control::getFrame1(uint8_t* data) +{ + assert(data != NULL); + + return m_slot1->getFrame(data); +} + +/// +/// Process a data frame for slot 2, from the RF interface. +/// +/// DMR data frame buffer. +/// Length of data frame buffer. +/// True, if data frame was processed, otherwise false. +bool Control::processFrame2(uint8_t *data, uint32_t len) +{ + assert(data != NULL); + + return m_slot2->processFrame(data, len); +} + +/// +/// Get a frame data for slot 2, from data ring buffer. +/// +/// Buffer to put retrieved DMR data frame data. +/// Length of data retrieved from DMR ring buffer. +uint32_t Control::getFrame2(uint8_t *data) +{ + assert(data != NULL); + + return m_slot2->getFrame(data); +} + +/// +/// Updates the processor. +/// +void Control::clock() +{ + if (m_network != NULL) { + data::Data data; + bool ret = m_network->readDMR(data); + if (ret) { + uint32_t slotNo = data.getSlotNo(); + switch (slotNo) { + case 1U: + m_slot1->processNetwork(data); + break; + case 2U: + m_slot2->processNetwork(data); + break; + default: + LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); + break; + } + } + } + + m_slot1->clock(); + m_slot2->clock(); +} + +/// +/// Helper to write a DMR extended function packet on the RF interface. +/// +/// DMR slot number. +/// Extended function opcode. +/// Extended function argument. +/// Destination radio ID. +void Control::writeRF_Ext_Func(uint32_t slotNo, uint32_t func, uint32_t arg, uint32_t dstId) +{ + switch (slotNo) { + case 1U: + m_slot1->writeRF_Ext_Func(func, arg, dstId); + break; + case 2U: + m_slot2->writeRF_Ext_Func(func, arg, dstId); + break; + default: + LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); + break; + } +} + +/// +/// Helper to write a DMR call alert packet on the RF interface. +/// +/// DMR slot number. +/// Source radio ID. +/// Destination radio ID. +void Control::writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId) +{ + switch (slotNo) { + case 1U: + m_slot1->writeRF_Call_Alrt(srcId, dstId); + break; + case 2U: + m_slot2->writeRF_Call_Alrt(srcId, dstId); + break; + default: + LogError(LOG_NET, "DMR, invalid slot, slotNo = %u", slotNo); + break; + } +} diff --git a/dmr/Control.h b/dmr/Control.h new file mode 100644 index 00000000..a3ce4863 --- /dev/null +++ b/dmr/Control.h @@ -0,0 +1,103 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_CONTROL_H__) +#define __DMR_CONTROL_H__ + +#include "Defines.h" +#include "dmr/data/Data.h" +#include "dmr/Slot.h" +#include "modem/Modem.h" +#include "network/BaseNetwork.h" +#include "network/RemoteControl.h" +#include "lookups/RSSIInterpolator.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API Slot; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling DMR. + // --------------------------------------------------------------------------- + + class HOST_SW_API Control { + public: + /// Initializes a new instance of the Control class. + Control(uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, + bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex, + lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::RSSIInterpolator* rssi, + uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool debug, bool verbose); + /// Finalizes a instance of the Control class. + ~Control(); + + /// Helper to process wakeup frames from the RF interface. + bool processWakeup(const uint8_t* data); + + /// Process a data frame for slot 1, from the RF interface. + bool processFrame1(uint8_t* data, uint32_t len); + /// Get a frame data for slot 1, from data ring buffer. + uint32_t getFrame1(uint8_t* data); + /// Process a data frame for slot 2, from the RF interface. + bool processFrame2(uint8_t* data, uint32_t len); + /// Get a frame data for slot 2, from data ring buffer. + uint32_t getFrame2(uint8_t* data); + + /// Updates the processor. + void clock(); + + /// Helper to write a DMR extended function packet on the RF interface. + void writeRF_Ext_Func(uint32_t slotNo, uint32_t func, uint32_t arg, uint32_t dstId); + /// Helper to write a DMR call alert packet on the RF interface. + void writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId); + + private: + uint32_t m_colorCode; + + modem::Modem* m_modem; + network::BaseNetwork* m_network; + + Slot* m_slot1; + Slot* m_slot2; + + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + bool m_verbose; + bool m_debug; + }; +} // namespace dmr + +#endif // __DMR_CONTROL_H__ diff --git a/dmr/DMRDefines.h b/dmr/DMRDefines.h new file mode 100644 index 00000000..cf069c1a --- /dev/null +++ b/dmr/DMRDefines.h @@ -0,0 +1,192 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DEFINES_H__) +#define __DMR_DEFINES_H__ + +#include "Defines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t DMR_FRAME_LENGTH_BITS = 264U; + const uint32_t DMR_FRAME_LENGTH_BYTES = 33U; + + const uint32_t DMR_SYNC_LENGTH_BITS = 48U; + const uint32_t DMR_SYNC_LENGTH_BYTES = 6U; + + const uint32_t DMR_EMB_LENGTH_BITS = 8U; + const uint32_t DMR_EMB_LENGTH_BYTES = 1U; + + const uint32_t DMR_SLOT_TYPE_LENGTH_BITS = 8U; + const uint32_t DMR_SLOT_TYPE_LENGTH_BYTES = 1U; + + const uint32_t DMR_EMBEDDED_SIGNALLING_LENGTH_BITS = 32U; + const uint32_t DMR_EMBEDDED_SIGNALLING_LENGTH_BYTES = 4U; + + const uint32_t DMR_AMBE_LENGTH_BITS = 108U * 2U; + const uint32_t DMR_AMBE_LENGTH_BYTES = 27U; + + const uint32_t DMR_LC_HEADER_LENGTH_BYTES = 12U; + + const uint8_t BS_SOURCED_AUDIO_SYNC[] = { 0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U }; + const uint8_t BS_SOURCED_DATA_SYNC[] = { 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U }; + + const uint8_t MS_SOURCED_AUDIO_SYNC[] = { 0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U }; + const uint8_t MS_SOURCED_DATA_SYNC[] = { 0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U }; + + const uint8_t DIRECT_SLOT1_AUDIO_SYNC[] = { 0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U }; + const uint8_t DIRECT_SLOT1_DATA_SYNC[] = { 0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U }; + + const uint8_t DIRECT_SLOT2_AUDIO_SYNC[] = { 0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U }; + const uint8_t DIRECT_SLOT2_DATA_SYNC[] = { 0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U }; + + const uint8_t DMR_MS_DATA_SYNC_BYTES[] = { 0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U }; + const uint8_t DMR_MS_VOICE_SYNC_BYTES[] = { 0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U }; + + const uint8_t SYNC_MASK[] = { 0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U }; + + // The PR FILL and Data Sync pattern. + const uint8_t DMR_IDLE_DATA[] = { TAG_DATA, 0x00U, + 0x53U, 0xC2U, 0x5EU, 0xABU, 0xA8U, 0x67U, 0x1DU, 0xC7U, 0x38U, 0x3BU, 0xD9U, + 0x36U, 0x00U, 0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U, 0x03U, 0xF6U, + 0xE4U, 0x65U, 0x17U, 0x1BU, 0x48U, 0xCAU, 0x6DU, 0x4FU, 0xC6U, 0x10U, 0xB4U }; + + // A silence frame only + const uint8_t DMR_SILENCE_DATA[] = { TAG_DATA, 0x00U, + 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU, 0xB9U, 0xE8U, + 0x81U, 0x52U, 0x60U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x73U, 0x00U, + 0x2AU, 0x6BU, 0xB9U, 0xE8U, 0x81U, 0x52U, 0x61U, 0x73U, 0x00U, 0x2AU, 0x6BU }; + + const uint8_t PAYLOAD_LEFT_MASK[] = { 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U }; + const uint8_t PAYLOAD_RIGHT_MASK[] = { 0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU }; + + const uint8_t VOICE_LC_HEADER_CRC_MASK[] = { 0x96U, 0x96U, 0x96U }; + const uint8_t TERMINATOR_WITH_LC_CRC_MASK[] = { 0x99U, 0x99U, 0x99U }; + const uint8_t PI_HEADER_CRC_MASK[] = { 0x69U, 0x69U }; + const uint8_t DATA_HEADER_CRC_MASK[] = { 0xCCU, 0xCCU }; + const uint8_t CSBK_CRC_MASK[] = { 0xA5U, 0xA5U }; + + const uint32_t DMR_SLOT_TIME = 60U; + const uint32_t AMBE_PER_SLOT = 3U; + + const uint8_t DT_MASK = 0x0FU; + + const uint8_t DMR_IDLE_RX = 0x80U; + const uint8_t DMR_SYNC_DATA = 0x40U; + const uint8_t DMR_SYNC_VOICE = 0x20U; + + const uint8_t DMR_SLOT1 = 0x00U; + const uint8_t DMR_SLOT2 = 0x80U; + + const uint8_t DPF_UDT = 0x00U; + const uint8_t DPF_RESPONSE = 0x01U; + const uint8_t DPF_UNCONFIRMED_DATA = 0x02U; + const uint8_t DPF_CONFIRMED_DATA = 0x03U; + const uint8_t DPF_DEFINED_SHORT = 0x0DU; + const uint8_t DPF_DEFINED_RAW = 0x0EU; + const uint8_t DPF_PROPRIETARY = 0x0FU; + + const uint32_t DMR_MAX_PDU_COUNT = 32U; + const uint32_t DMR_MAX_PDU_LENGTH = 512U; + + const uint8_t FID_ETSI = 0x00U; // ETSI Standard Feature Set + const uint8_t FID_DMRA = 0x10U; // + + const uint32_t DMR_EXT_FNCT_CHECK = 0x0000U; // Radio Check + const uint32_t DMR_EXT_FNCT_UNINHIBIT = 0x007EU; // Radio Uninhibit + const uint32_t DMR_EXT_FNCT_INHIBIT = 0x007FU; // Radio Inhibit + const uint32_t DMR_EXT_FNCT_CHECK_ACK = 0x0080U; // Radio Check Ack + const uint32_t DMR_EXT_FNCT_UNINHIBIT_ACK = 0x00FEU; // Radio Uninhibit Ack + const uint32_t DMR_EXT_FNCT_INHIBIT_ACK = 0x00FFU; // Radio Inhibit Ack + + // Data Type(s) + const uint8_t DT_VOICE_PI_HEADER = 0x00U; + const uint8_t DT_VOICE_LC_HEADER = 0x01U; + const uint8_t DT_TERMINATOR_WITH_LC = 0x02U; + const uint8_t DT_CSBK = 0x03U; + const uint8_t DT_DATA_HEADER = 0x06U; + const uint8_t DT_RATE_12_DATA = 0x07U; + const uint8_t DT_RATE_34_DATA = 0x08U; + const uint8_t DT_IDLE = 0x09U; + const uint8_t DT_RATE_1_DATA = 0x0AU; + + // Dummy values + const uint8_t DT_VOICE_SYNC = 0xF0U; + const uint8_t DT_VOICE = 0xF1U; + + // Full-Link Control Opcode(s) + const uint8_t FLCO_GROUP = 0x00U; // GRP VCH USER - Group Voice Channel User + const uint8_t FLCO_PRIVATE = 0x03U; // UU VCH USER - Unit-to-Unit Voice Channel User + const uint8_t FLCO_TALKER_ALIAS_HEADER = 0x04U; // + const uint8_t FLCO_TALKER_ALIAS_BLOCK1 = 0x05U; // + const uint8_t FLCO_TALKER_ALIAS_BLOCK2 = 0x06U; // + const uint8_t FLCO_TALKER_ALIAS_BLOCK3 = 0x07U; // + const uint8_t FLCO_GPS_INFO = 0x08U; // + + // Control Signalling Block Opcode(s) + const uint8_t CSBKO_NONE = 0x00U; // + const uint8_t CSBKO_UU_V_REQ = 0x04U; // UU VCH REQ - Unit-to-Unit Voice Channel Request + const uint8_t CSBKO_UU_ANS_RSP = 0x05U; // UU ANS RSP - Unit to Unit Answer Response + const uint8_t CSBKO_CTCSBK = 0x07U; // CT CSBK - Channel Timing CSBK + const uint8_t CSBKO_CALL_ALRT = 0x1FU; // CALL ALRT - Call Alert + const uint8_t CSBKO_ACK_RSP = 0x20U; // ACK RSP - Acknowledge Response + const uint8_t CSBKO_EXT_FNCT = 0x24U; // EXT FNCT - Extended Function + const uint8_t CSBKO_NACK_RSP = 0x26U; // NACK RSP - Negative Acknowledgement Response + const uint8_t CSBKO_BSDWNACT = 0x38U; // BS DWN ACT - BS Outbound Activation + const uint8_t CSBKO_PRECCSBK = 0x3DU; // PRE CSBK - Preamble CSBK + + const uint8_t TALKER_ID_NONE = 0x00U; + const uint8_t TALKER_ID_HEADER = 0x01U; + const uint8_t TALKER_ID_BLOCK1 = 0x02U; + const uint8_t TALKER_ID_BLOCK2 = 0x04U; + const uint8_t TALKER_ID_BLOCK3 = 0x08U; + + const uint32_t NO_HEADERS_SIMPLEX = 8U; + const uint32_t NO_HEADERS_DUPLEX = 3U; + const uint32_t NO_PREAMBLE_CSBK = 15U; +} // namespace dmr + +// --------------------------------------------------------------------------- +// Namespace Prototypes +// --------------------------------------------------------------------------- +namespace edac { } +namespace dmr +{ + namespace edac + { + using namespace ::edac; + } // namespace edac +} // namespace dmr + +#endif // __DMR_DEFINES_H__ diff --git a/dmr/DataPacket.cpp b/dmr/DataPacket.cpp new file mode 100644 index 00000000..c46ceaf5 --- /dev/null +++ b/dmr/DataPacket.cpp @@ -0,0 +1,1191 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/DataPacket.h" +#include "dmr/acl/AccessControl.h" +#include "dmr/data/DataHeader.h" +#include "dmr/data/EMB.h" +#include "dmr/edac/Trellis.h" +#include "dmr/lc/ShortLC.h" +#include "dmr/lc/FullLC.h" +#include "dmr/lc/CSBK.h" +#include "dmr/SlotType.h" +#include "dmr/Sync.h" +#include "edac/BPTC19696.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace dmr; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- +// Don't process RF frames if the network isn't in a idle state. +#define CHECK_TRAFFIC_COLLISION(_DST_ID) \ + if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ + return false; \ + } + +#define CHECK_TRAFFIC_COLLISION_DELLC(_DST_ID) \ + if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ + delete lc; \ + return false; \ + } + +#define CHECK_NET_TG_HANG(_DST_ID) \ + if (m_slot->m_rfLastDstId != 0U) { \ + if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_networkTGHang.isRunning() && !m_slot->m_networkTGHang.hasExpired())) { \ + return; \ + } \ + } + +#define CHECK_NET_TG_HANG_DELLC(_DST_ID) \ + if (m_slot->m_rfLastDstId != 0U) { \ + if (m_slot->m_rfLastDstId != _DST_ID && (m_slot->m_networkTGHang.isRunning() && !m_slot->m_networkTGHang.hasExpired())) { \ + delete lc; \ + return; \ + } \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Process DMR data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool DataPacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + // Get the type from the packet metadata + uint8_t dataType = data[1U] & 0x0FU; + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(dataType); + + if (dataType == DT_VOICE_LC_HEADER) { + if (m_slot->m_rfState == RS_RF_AUDIO) + return true; + + lc::FullLC fullLC; + lc::LC * lc = fullLC.decode(data + 2U, DT_VOICE_LC_HEADER); + if (lc == NULL) + return false; + + uint32_t srcId = lc->getSrcId(); + uint32_t dstId = lc->getDstId(); + uint8_t flco = lc->getFLCO(); + + CHECK_TRAFFIC_COLLISION_DELLC(dstId); + + // validate source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, "DMR Slot %u, DMR_SYNC_DATA denial, RID rejection, srcId = %u", m_slot->m_slotNo, srcId); + delete lc; + return false; + } + + // validate target TID, if the target is a talkgroup + if (flco == FLCO_GROUP) { + if (!acl::AccessControl::validateTGId(m_slot->m_slotNo, dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, DMR_SYNC_DATA denial, TGID rejection, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); + delete lc; + return false; + } + } + + m_rfLC = lc; + + // The standby LC data + m_slot->m_voice->m_rfEmbeddedLC.setLC(*m_rfLC); + m_slot->m_voice->m_rfEmbeddedData[0U].setLC(*m_rfLC); + m_slot->m_voice->m_rfEmbeddedData[1U].setLC(*m_rfLC); + + // Regenerate the LC data + fullLC.encode(*m_rfLC, data + 2U, DT_VOICE_LC_HEADER); + + // 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); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + m_slot->m_rfTimeoutTimer.start(); + m_slot->m_rfTimeout = false; + + m_slot->m_rfFrames = 0U; + m_slot->m_rfSeqNo = 0U; + m_slot->m_rfBits = 1U; + m_slot->m_rfErrs = 0U; + + m_slot->m_voice->m_rfEmbeddedReadN = 0U; + m_slot->m_voice->m_rfEmbeddedWriteN = 1U; + m_slot->m_voice->m_rfTalkerId = TALKER_ID_NONE; + + m_slot->m_minRSSI = m_slot->m_rssi; + m_slot->m_maxRSSI = m_slot->m_rssi; + m_slot->m_aveRSSI = m_slot->m_rssi; + m_slot->m_rssiCount = 1U; + + if (m_slot->m_duplex) { + m_slot->m_queue.clear(); + m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + + for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) + m_slot->writeQueueRF(data); + } + + m_slot->writeNetworkRF(data, DT_VOICE_LC_HEADER); + + m_slot->m_rfState = RS_RF_AUDIO; + m_slot->m_rfLastDstId = dstId; + + if (m_slot->m_netState == RS_NET_IDLE) { + m_slot->setShortLC(m_slot->m_slotNo, dstId, flco, true); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_VOICE_LC_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + ::ActivityLog("DMR", true, "Slot %u, received RF voice header from %u to %s%u", m_slot->m_slotNo, srcId, flco == FLCO_GROUP ? "TG " : "", dstId); + return true; + } + else if (dataType == DT_VOICE_PI_HEADER) { + if (m_slot->m_rfState != RS_RF_AUDIO) + return false; + + // 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); + + // Regenerate the payload and the BPTC (196,96) FEC + edac::BPTC19696 bptc; + uint8_t payload[12U]; + bptc.decode(data + 2U, payload); + bptc.encode(payload, data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex) + m_slot->writeQueueRF(data); + + m_slot->writeNetworkRF(data, DT_VOICE_PI_HEADER); + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_VOICE_PI_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + return true; + } + else if (dataType == DT_TERMINATOR_WITH_LC) { + if (m_slot->m_rfState != RS_RF_AUDIO) + return false; + + // Regenerate the LC data + lc::FullLC fullLC; + fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); + + // 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); + + if (!m_slot->m_rfTimeout) { + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + m_slot->writeNetworkRF(data, DT_TERMINATOR_WITH_LC); + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + m_slot->writeQueueRF(data); + } + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_TERMINATOR_WITH_LC", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + if (m_slot->m_rssi != 0U) { + ::ActivityLog("DMR", true, "Slot %u, received RF end of voice transmission, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", + m_slot->m_slotNo, float(m_slot->m_rfFrames) / 16.667F, float(m_slot->m_rfErrs * 100U) / float(m_slot->m_rfBits), + m_slot->m_minRSSI, m_slot->m_maxRSSI, m_slot->m_aveRSSI / m_slot->m_rssiCount); + } + else { + ::ActivityLog("DMR", true, "Slot %u, received RF end of voice transmission, %.1f seconds, BER: %.1f%%", + 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%%", + 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)); + + if (m_slot->m_rfTimeout) { + writeEndRF(); + return false; + } + else { + writeEndRF(); + return true; + } + } + else if (dataType == DT_CSBK) { + // generate a new CSBK and check validity + lc::CSBK csbk = lc::CSBK(m_debug); + bool valid = csbk.decode(data + 2U); + if (!valid) + return false; + + uint8_t csbko = csbk.getCSBKO(); + if (csbko == CSBKO_BSDWNACT) + return false; + + bool gi = csbk.getGI(); + uint32_t srcId = csbk.getSrcId(); + uint32_t dstId = csbk.getDstId(); + + if (srcId != 0U || dstId != 0U) { + CHECK_TRAFFIC_COLLISION(dstId); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK denial, RID rejection, srcId = %u", m_slot->m_slotNo, srcId); + return false; + } + + // validate the target ID + if (gi) { + if (!acl::AccessControl::validateTGId(m_slot->m_slotNo, dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK denial, TGID rejection, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); + return false; + } + } + } + + // Regenerate the CSBK data + csbk.encode(data + 2U); + + // 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); + + m_slot->m_rfSeqNo = 0U; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex) + m_slot->writeQueueRF(data); + + m_slot->writeNetworkRF(data, DT_CSBK, gi ? FLCO_GROUP : FLCO_PRIVATE, srcId, dstId); + + if (m_verbose) { + switch (csbko) { + case CSBKO_UU_V_REQ: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_UU_V_REQ (Unit to Unit Voice Service Request), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_UU_ANS_RSP: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_UU_ANS_RSP (Unit to Unit Voice Service Answer Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_NACK_RSP: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_NACK_RSP (Negative Acknowledgment Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_CALL_ALRT: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + + ::ActivityLog("DMR", true, "Slot %u received call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); + break; + case CSBKO_ACK_RSP: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_ACK_RSP (Acknowledge Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + + ::ActivityLog("DMR", true, "Slot %u received ack response from %u to %u", m_slot->m_slotNo, srcId, dstId); + break; + case CSBKO_EXT_FNCT: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", + m_slot->m_slotNo, csbk.getCBF(), dstId, srcId); + } + + // generate activity log entry + if (csbk.getCBF() == DMR_EXT_FNCT_CHECK) { + ::ActivityLog("DMR", true, "Slot %u received radio check request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_INHIBIT) { + ::ActivityLog("DMR", true, "Slot %u received radio inhibit request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_UNINHIBIT) { + ::ActivityLog("DMR", true, "Slot %u received radio uninhibit request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_CHECK_ACK) { + ::ActivityLog("DMR", true, "Slot %u received radio check response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_INHIBIT_ACK) { + ::ActivityLog("DMR", true, "Slot %u received radio inhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_UNINHIBIT_ACK) { + ::ActivityLog("DMR", true, "Slot %u received radio uninhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + break; + case CSBKO_PRECCSBK: + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, src = %u, dst = %s%u", + m_slot->m_slotNo, csbk.getDataContent() ? "Data" : "CSBK", csbk.getCBF(), srcId, gi ? "TG " : "", dstId); + } + break; + default: + LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, unhandled CSBK, csbko = $%02X, fid = $%02X", m_slot->m_slotNo, csbko, csbk.getFID()); + break; + } + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_CSBK", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + return true; + } + else if (dataType == DT_DATA_HEADER) { + if (m_slot->m_rfState == RS_RF_DATA) + return true; + + data::DataHeader dataHeader; + bool valid = dataHeader.decode(data + 2U); + if (!valid) + return false; + + bool gi = dataHeader.getGI(); + uint32_t srcId = dataHeader.getSrcId(); + uint32_t dstId = dataHeader.getDstId(); + + CHECK_TRAFFIC_COLLISION(dstId); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, "DMR Slot %u, DT_DATA_HEADER denial, RID rejection, srcId = %u", m_slot->m_slotNo, srcId); + return false; + } + + // validate the target ID + if (gi) { + if (!acl::AccessControl::validateTGId(m_slot->m_slotNo, dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, DT_DATA_HEADER denial, TGID rejection, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); + return false; + } + } + + m_slot->m_rfFrames = dataHeader.getBlocks(); + m_slot->m_rfSeqNo = 0U; + m_rfLC = new lc::LC(gi ? FLCO_GROUP : FLCO_PRIVATE, srcId, dstId); + + // Regenerate the data header + dataHeader.encode(data + 2U); + + // 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); + + data[0U] = m_slot->m_rfFrames == 0U ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex && m_repeatDataPacket) + m_slot->writeQueueRF(data); + + m_slot->writeNetworkRF(data, DT_DATA_HEADER); + + m_slot->m_rfState = RS_RF_DATA; + m_slot->m_rfLastDstId = dstId; + + if (m_slot->m_netState == RS_NET_IDLE) { + m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO_GROUP : FLCO_PRIVATE, false); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_DATA_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + ::ActivityLog("DMR", true, "Slot %u, received RF data header from %u to %s%u, %u blocks", m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId, m_slot->m_rfFrames); + + ::memset(m_pduUserData, 0x00U, DMR_MAX_PDU_COUNT * DMR_MAX_PDU_LENGTH + 2U); + m_pduDataOffset = 0U; + + if (m_slot->m_rfFrames == 0U) { + ::ActivityLog("DMR", true, "Slot %u, ended RF data transmission", m_slot->m_slotNo); + writeEndRF(); + } + + return true; + } + else if (dataType == DT_RATE_12_DATA || dataType == DT_RATE_34_DATA || dataType == DT_RATE_1_DATA) { + if (m_slot->m_rfState != RS_RF_DATA || m_slot->m_rfFrames == 0U) + return false; + + edac::BPTC19696 bptc; + edac::Trellis trellis; + + // decode the rate 1/2 payload + if (dataType == DT_RATE_12_DATA) { + // decode the BPTC (196,96) FEC + uint8_t payload[12U]; + bptc.decode(data + 2U, payload); + + // store payload + ::memcpy(m_pduUserData, payload, 12U); + m_pduDataOffset += 12U; + + // encode the BPTC (196,96) FEC + bptc.encode(payload, data + 2U); + } + else if (dataType == DT_RATE_34_DATA) { + // decode the Trellis 3/4 rate FEC + uint8_t payload[18U]; + bool ret = trellis.decode(data + 2U, payload); + if (ret) { + // store payload + ::memcpy(m_pduUserData, payload, 18U); + + // encode the Trellis 3/4 rate FEC + trellis.encode(payload, data + 2U); + } + else { + LogWarning(LOG_RF, "DMR Slot %u, DT_RATE_34_DATA, unfixable RF rate 3/4 data", m_slot->m_slotNo); + Utils::dump(1U, "Data", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + m_pduDataOffset += 18U; + } + + m_slot->m_rfFrames--; + + data[0U] = m_slot->m_rfFrames == 0U ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + // 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); + + m_slot->writeNetworkRF(data, dataType); + + if (m_slot->m_duplex && m_repeatDataPacket) { + m_slot->writeQueueRF(data); + } + + if (m_slot->m_rfFrames == 0U) { + if (m_dumpDataPacket) { + Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + } + + LogMessage(LOG_RF, "DMR Slot %u, DT_RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); + writeEndRF(); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Frame - DT_RATE_12/34_DATA", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + return true; + } + + return false; +} + +/// +/// Process a data frame from the network. +/// +/// +void DataPacket::processNetwork(const data::Data& dmrData) +{ + uint8_t dataType = dmrData.getDataType(); + + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + dmrData.getData(data + 2U); + + if (dataType == DT_VOICE_LC_HEADER) { + if (m_slot->m_netState == RS_NET_AUDIO) + return; + + lc::FullLC fullLC; + lc::LC * lc = fullLC.decode(data + 2U, DT_VOICE_LC_HEADER); + if (lc == NULL) { + LogWarning(LOG_NET, "DMR Slot %u, DT_VOICE_LC_HEADER, bad LC received from the network, replacing", m_slot->m_slotNo); + lc = new lc::LC(dmrData.getFLCO(), dmrData.getSrcId(), dmrData.getDstId()); + } + + uint32_t srcId = lc->getSrcId(); + uint32_t dstId = lc->getDstId(); + uint8_t flco = lc->getFLCO(); + + CHECK_NET_TG_HANG_DELLC(dstId); + + if (dstId != dmrData.getDstId() || srcId != dmrData.getSrcId() || flco != dmrData.getFLCO()) + LogWarning(LOG_NET, "DMR Slot %u, DT_VOICE_LC_HEADER, header doesn't match the DMR RF header: %u->%s%u %u->%s%u", m_slot->m_slotNo, + dmrData.getSrcId(), dmrData.getFLCO() == FLCO_GROUP ? "TG" : "", dmrData.getDstId(), + srcId, flco == FLCO_GROUP ? "TG" : "", dstId); + + m_netLC = lc; + + // The standby LC data + m_slot->m_voice->m_netEmbeddedLC.setLC(*m_netLC); + m_slot->m_voice->m_netEmbeddedData[0U].setLC(*m_netLC); + m_slot->m_voice->m_netEmbeddedData[1U].setLC(*m_netLC); + + // Regenerate the LC data + fullLC.encode(*m_netLC, data + 2U, DT_VOICE_LC_HEADER); + + // Regenerate the Slot Type + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_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); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + m_slot->m_voice->m_lastFrameValid = false; + + m_slot->m_netTimeoutTimer.start(); + m_slot->m_netTimeout = false; + + m_slot->m_netFrames = 0U; + m_slot->m_netLost = 0U; + m_slot->m_netBits = 1U; + m_slot->m_netErrs = 0U; + + m_slot->m_voice->m_netEmbeddedReadN = 0U; + m_slot->m_voice->m_netEmbeddedWriteN = 1U; + m_slot->m_voice->m_netTalkerId = TALKER_ID_NONE; + + if (m_slot->m_duplex) { + m_slot->m_queue.clear(); + m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + } + + for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) + m_slot->writeQueueNet(m_slot->m_idle); + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) + m_slot->writeQueueNet(data); + } + else { + for (uint32_t i = 0U; i < NO_HEADERS_SIMPLEX; i++) + m_slot->writeQueueNet(data); + } + + m_slot->m_netState = RS_NET_AUDIO; + m_slot->m_netLastDstId = dstId; + + m_slot->setShortLC(m_slot->m_slotNo, dstId, flco, true); + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Network Frame - DT_VOICE_LC_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + ::ActivityLog("DMR", false, "Slot %u, received network voice header from %u to %s%u", m_slot->m_slotNo, srcId, flco == FLCO_GROUP ? "TG " : "", dstId); + } + else if (dataType == DT_VOICE_PI_HEADER) { + if (m_slot->m_netState != RS_NET_AUDIO) { + lc::LC* lc = new lc::LC(dmrData.getFLCO(), dmrData.getSrcId(), dmrData.getDstId()); + + uint32_t srcId = lc->getSrcId(); + uint32_t dstId = lc->getDstId(); + + CHECK_NET_TG_HANG_DELLC(dstId); + + m_netLC = lc; + + m_slot->m_voice->m_lastFrameValid = false; + + m_slot->m_netTimeoutTimer.start(); + m_slot->m_netTimeout = false; + + if (m_slot->m_duplex) { + m_slot->m_queue.clear(); + m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + } + + for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) + m_slot->writeQueueNet(m_slot->m_idle); + + // Create a dummy start frame + uint8_t start[DMR_FRAME_LENGTH_BYTES + 2U]; + + Sync::addDMRDataSync(start + 2U, m_slot->m_duplex); + + lc::FullLC fullLC; + fullLC.encode(*m_netLC, start + 2U, DT_VOICE_LC_HEADER); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_VOICE_LC_HEADER); + slotType.encode(start + 2U); + + start[0U] = TAG_DATA; + start[1U] = 0x00U; + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) + m_slot->writeQueueRF(start); + } + else { + for (uint32_t i = 0U; i < NO_HEADERS_SIMPLEX; i++) + m_slot->writeQueueRF(start); + } + + m_slot->m_netFrames = 0U; + m_slot->m_netLost = 0U; + m_slot->m_netBits = 1U; + m_slot->m_netErrs = 0U; + + m_slot->m_netState = RS_NET_AUDIO; + m_slot->m_netLastDstId = dstId; + + m_slot->setShortLC(m_slot->m_slotNo, dstId, m_netLC->getFLCO(), true); + + ::ActivityLog("DMR", false, "Slot %u, received network late entry from %u to %s%u", + m_slot->m_slotNo, srcId, m_netLC->getFLCO() == FLCO_GROUP ? "TG " : "", dstId); + } + + // Regenerate the Slot Type + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_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); + + // Regenerate the payload and the BPTC (196,96) FEC + edac::BPTC19696 bptc; + uint8_t payload[12U]; + bptc.decode(data + 2U, payload); + bptc.encode(payload, data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + m_slot->writeQueueNet(data); + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Network Frame - DT_VOICE_PI_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + } + else if (dataType == DT_TERMINATOR_WITH_LC) { + if (m_slot->m_netState != RS_NET_AUDIO) + return; + + // Regenerate the LC data + lc::FullLC fullLC; + fullLC.encode(*m_netLC, data + 2U, DT_TERMINATOR_WITH_LC); + + // Regenerate the Slot Type + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_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); + + if (!m_slot->m_netTimeout) { + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + m_slot->writeQueueNet(data); + } + else { + for (uint32_t i = 0U; i < 3U; i++) + m_slot->writeQueueNet(data); + } + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Network Frame - DT_TERMINATOR_WITH_LC", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + // We've received the voice header and terminator haven't we? + m_slot->m_netFrames += 2U; + ::ActivityLog("DMR", false, "Slot %u, received 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)); + + writeEndNet(); + } + else if (dataType == DT_CSBK) { + lc::CSBK csbk = lc::CSBK(m_debug); + bool valid = csbk.decode(data + 2U); + if (!valid) { + LogError(LOG_NET, "DMR Slot %u, DT_CSBK, unable to decode the network CSBK", m_slot->m_slotNo); + return; + } + + uint8_t csbko = csbk.getCSBKO(); + if (csbko == CSBKO_BSDWNACT) + return; + + bool gi = csbk.getGI(); + uint32_t srcId = csbk.getSrcId(); + uint32_t dstId = csbk.getDstId(); + + CHECK_NET_TG_HANG(dstId); + + // Regenerate the CSBK data + csbk.encode(data + 2U); + + // Regenerate the Slot Type + SlotType slotType; + slotType.decode(data + 2U); + slotType.setColorCode(m_slot->m_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); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (csbko == CSBKO_PRECCSBK && csbk.getDataContent()) { + uint32_t cbf = NO_PREAMBLE_CSBK + csbk.getCBF() - 1U; + for (uint32_t i = 0U; i < NO_PREAMBLE_CSBK; i++, cbf--) { + // Change blocks to follow + csbk.setCBF(cbf); + + // Regenerate the CSBK data + csbk.encode(data + 2U); + + // Regenerate the Slot Type + SlotType slotType; + slotType.decode(data + 2U); + slotType.setColorCode(m_slot->m_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); + + m_slot->writeQueueNet(data); + } + } + else + m_slot->writeQueueNet(data); + + if (m_verbose) { + switch (csbko) { + case CSBKO_UU_V_REQ: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_UU_V_REQ (Unit to Unit Voice Service Request), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_UU_ANS_RSP: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_UU_ANS_RSP (Unit to Unit Voice Service Answer Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_NACK_RSP: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_NACK_RSP (Negative Acknowledgment Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + break; + case CSBKO_CALL_ALRT: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + + ::ActivityLog("DMR", false, "Slot %u received call alert request from %u to %u", m_slot->m_slotNo, srcId, dstId); + break; + case CSBKO_ACK_RSP: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_ACK_RSP (Acknowledge Response), src = %u, dst = %s%u", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId); + } + + ::ActivityLog("DMR", false, "Slot %u received ack response from %u to %u", m_slot->m_slotNo, srcId, dstId); + break; + case CSBKO_EXT_FNCT: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", + m_slot->m_slotNo, csbk.getCBF(), dstId, srcId); + } + + // generate activity log entry + if (csbk.getCBF() == DMR_EXT_FNCT_CHECK) { + ::ActivityLog("DMR", false, "Slot %u received radio check request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_INHIBIT) { + ::ActivityLog("DMR", false, "Slot %u received radio inhibit request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_UNINHIBIT) { + ::ActivityLog("DMR", false, "Slot %u received radio uninhibit request from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_CHECK_ACK) { + ::ActivityLog("DMR", false, "Slot %u received radio check response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_INHIBIT_ACK) { + ::ActivityLog("DMR", false, "Slot %u received radio inhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + else if (csbk.getCBF() == DMR_EXT_FNCT_UNINHIBIT_ACK) { + ::ActivityLog("DMR", false, "Slot %u received radio uninhibit response from %u to %u", m_slot->m_slotNo, dstId, srcId); + } + break; + case CSBKO_PRECCSBK: + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, CSBKO_PRECCSBK (%s Preamble CSBK), toFollow = %u, src = %u, dst = %s%u", + m_slot->m_slotNo, csbk.getDataContent() ? "Data" : "CSBK", csbk.getCBF(), srcId, gi ? "TG " : "", dstId); + } + break; + default: + LogWarning(LOG_NET, "DMR Slot %u, DT_CSBK, unhandled network CSBK, csbko = $%02X, fid = $%02X", m_slot->m_slotNo, csbko, csbk.getFID()); + break; + } + } + } + else if (dataType == DT_DATA_HEADER) { + if (m_slot->m_netState == RS_NET_DATA) + return; + + data::DataHeader dataHeader; + bool valid = dataHeader.decode(data + 2U); + if (!valid) { + LogError(LOG_NET, "DMR Slot %u, DT_DATA_HEADER, unable to decode the network data header", m_slot->m_slotNo); + return; + } + + bool gi = dataHeader.getGI(); + uint32_t srcId = dataHeader.getSrcId(); + uint32_t dstId = dataHeader.getDstId(); + + CHECK_NET_TG_HANG(dstId); + + m_slot->m_netFrames = dataHeader.getBlocks(); + + // Regenerate the data header + dataHeader.encode(data + 2U); + + // Regenerate the Slot Type + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_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); + + data[0U] = m_slot->m_netFrames == 0U ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + // Put a small delay into starting transmission + m_slot->writeQueueNet(m_slot->m_idle); + m_slot->writeQueueNet(m_slot->m_idle); + + m_slot->writeQueueNet(data); + + m_slot->m_netState = RS_NET_DATA; + m_slot->m_netLastDstId = dstId; + + m_slot->setShortLC(m_slot->m_slotNo, dstId, gi ? FLCO_GROUP : FLCO_PRIVATE, false); + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Network Frame - DT_DATA_HEADER", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + ::ActivityLog("DMR", false, "Slot %u, received network data header from %u to %s%u, %u blocks", + m_slot->m_slotNo, srcId, gi ? "TG " : "", dstId, m_slot->m_netFrames); + + ::memset(m_pduUserData, 0x00U, DMR_MAX_PDU_COUNT * DMR_MAX_PDU_LENGTH + 2U); + m_pduDataOffset = 0U; + + if (m_slot->m_netFrames == 0U) { + ::ActivityLog("DMR", false, "Slot %u, ended network data transmission", m_slot->m_slotNo); + writeEndNet(); + } + } + else if (dataType == DT_RATE_12_DATA || dataType == DT_RATE_34_DATA || dataType == DT_RATE_1_DATA) { + if (m_slot->m_netState != RS_NET_DATA || m_slot->m_netFrames == 0U) { + writeEndNet(); + return; + } + + // regenerate the rate 1/2 payload + if (dataType == DT_RATE_12_DATA) { + // decode the BPTC (196,96) FEC + edac::BPTC19696 bptc; + uint8_t payload[12U]; + bptc.decode(data + 2U, payload); + + // store payload + ::memcpy(m_pduUserData, payload, 12U); + m_pduDataOffset += 12U; + + // encode the BPTC (196,96) FEC + bptc.encode(payload, data + 2U); + } + else if (dataType == DT_RATE_34_DATA) { + // decode the Trellis 3/4 rate FEC + edac::Trellis trellis; + uint8_t payload[18U]; + bool ret = trellis.decode(data + 2U, payload); + if (ret) { + // store payload + ::memcpy(m_pduUserData, payload, 18U); + + // encode the Trellis 3/4 rate FEC + trellis.encode(payload, data + 2U); + } + else { + LogWarning(LOG_NET, "DMR Slot %u, DT_RATE_34_DATA, unfixable network rate 3/4 data", m_slot->m_slotNo); + Utils::dump(1U, "Data", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + + m_pduDataOffset += 18U; + } + + m_slot->m_netFrames--; + + if (m_repeatDataPacket) { + // regenerate the Slot Type + SlotType slotType; + slotType.decode(data + 2U); + slotType.setColorCode(m_slot->m_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); + + data[0U] = m_slot->m_netFrames == 0U ? TAG_EOT : TAG_DATA; + data[1U] = 0x00U; + + m_slot->writeQueueNet(data); + + if (m_debug) { + Utils::dump(2U, "!!! *TX DMR Network Frame - DT_RATE_12/34_DATA", data + 2U, DMR_FRAME_LENGTH_BYTES); + } + } + + if (m_slot->m_netFrames == 0U) { + if (m_dumpDataPacket) { + Utils::dump(1U, "PDU Packet", m_pduUserData, m_pduDataOffset); + } + + LogMessage(LOG_NET, "DMR Slot %u, DT_RATE_12/34_DATA, ended data transmission", m_slot->m_slotNo); + writeEndNet(); + } + } + else { + // Unhandled data type + LogWarning(LOG_NET, "DMR Slot %u, unhandled network data, type = $%02X", m_slot->m_slotNo, dataType); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DataPacket class. +/// +/// DMR slot. +/// Instance of the BaseNetwork class. +/// +/// +/// Flag indicating whether DMR debug is enabled. +/// Flag indicating whether DMR verbose logging is enabled. +DataPacket::DataPacket(Slot* slot, network::BaseNetwork* network, bool dumpDataPacket, bool repeatDataPacket, bool debug, bool verbose) : + m_slot(slot), + m_rfLC(NULL), + m_netLC(NULL), + m_pduUserData(NULL), + m_pduDataOffset(0U), + m_dumpDataPacket(dumpDataPacket), + m_repeatDataPacket(repeatDataPacket), + m_verbose(verbose), + m_debug(debug) +{ + m_pduUserData = new uint8_t[DMR_MAX_PDU_COUNT * DMR_MAX_PDU_LENGTH + 2U]; + ::memset(m_pduUserData, 0x00U, DMR_MAX_PDU_COUNT * DMR_MAX_PDU_LENGTH + 2U); +} + +/// +/// Finalizes a instance of the DataPacket class. +/// +DataPacket::~DataPacket() +{ + delete[] m_pduUserData; +} + +/// +/// Helper to write RF end of frame data. +/// +/// +void DataPacket::writeEndRF(bool writeEnd) +{ + m_slot->m_rfState = RS_RF_LISTENING; + + if (m_slot->m_netState == RS_NET_IDLE) { + m_slot->setShortLC(m_slot->m_slotNo, 0U); + } + + if (writeEnd) { + if (m_slot->m_netState == RS_NET_IDLE && m_slot->m_duplex && !m_slot->m_rfTimeout) { + // Create a dummy start end frame + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + + Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + + lc::FullLC fullLC; + fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_TERMINATOR_WITH_LC); + slotType.encode(data + 2U); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + m_slot->writeQueueRF(data); + } + } + + m_pduDataOffset = 0U; + + if (m_slot->m_network != NULL) + m_slot->m_network->resetDMR(m_slot->m_slotNo); + + m_slot->m_rfTimeoutTimer.stop(); + m_slot->m_rfTimeout = false; + + m_slot->m_rfFrames = 0U; + m_slot->m_rfErrs = 0U; + m_slot->m_rfBits = 1U; + + delete m_rfLC; + m_rfLC = NULL; +} + +/// +/// Helper to write network end of frame data. +/// +/// +void DataPacket::writeEndNet(bool writeEnd) +{ + m_slot->m_netState = RS_NET_IDLE; + + m_slot->setShortLC(m_slot->m_slotNo, 0U); + + m_slot->m_voice->m_lastFrameValid = false; + + if (writeEnd && !m_slot->m_netTimeout) { + // Create a dummy start end frame + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + + Sync::addDMRDataSync(data + 2U, m_slot->m_duplex); + + lc::FullLC fullLC; + fullLC.encode(*m_netLC, data + 2U, DT_TERMINATOR_WITH_LC); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_TERMINATOR_WITH_LC); + slotType.encode(data + 2U); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < m_slot->m_hangCount; i++) + m_slot->writeQueueNet(data); + } + else { + for (uint32_t i = 0U; i < 3U; i++) + m_slot->writeQueueNet(data); + } + } + + m_pduDataOffset = 0U; + + if (m_slot->m_network != NULL) + m_slot->m_network->resetDMR(m_slot->m_slotNo); + + m_slot->m_networkWatchdog.stop(); + m_slot->m_netTimeoutTimer.stop(); + m_slot->m_packetTimer.stop(); + m_slot->m_netTimeout = false; + + m_slot->m_netFrames = 0U; + m_slot->m_netLost = 0U; + + m_slot->m_netErrs = 0U; + m_slot->m_netBits = 1U; + + delete m_netLC; + m_netLC = NULL; +} diff --git a/dmr/DataPacket.h b/dmr/DataPacket.h new file mode 100644 index 00000000..178fc06a --- /dev/null +++ b/dmr/DataPacket.h @@ -0,0 +1,99 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DATA_PACKET_H__) +#define __DMR_DATA_PACKET_H__ + +#include "Defines.h" +#include "dmr/data/Data.h" +#include "dmr/data/EmbeddedData.h" +#include "dmr/lc/LC.h" +#include "dmr/Slot.h" +#include "edac/AMBEFEC.h" +#include "modem/Modem.h" +#include "network/BaseNetwork.h" +#include "RingBuffer.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" +#include "StopWatch.h" +#include "Timer.h" + +#include + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API VoicePacket; + class HOST_SW_API Slot; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling DMR data packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API DataPacket { + public: + /// Process a data frame from the RF interface. + bool process(uint8_t* data, uint32_t len); + /// Process a data frame from the network. + void processNetwork(const data::Data& dmrData); + + private: + friend class VoicePacket; + friend class Slot; + Slot* m_slot; + + lc::LC* m_rfLC; + lc::LC* m_netLC; + + uint8_t* m_pduUserData; + uint32_t m_pduDataOffset; + + bool m_dumpDataPacket; + bool m_repeatDataPacket; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the DataPacket class. + DataPacket(Slot* slot, network::BaseNetwork* network, bool dumpDataPacket, bool repeatDataPacket, bool debug, bool verbose); + /// Finalizes a instance of the DataPacket class. + ~DataPacket(); + + /// Helper to write RF end of frame data. + void writeEndRF(bool writeEnd = false); + /// Helper to write network end of frame data. + void writeEndNet(bool writeEnd = false); + }; +} // namespace dmr + +#endif // __DMR_DATA_PACKET_H__ diff --git a/dmr/Slot.cpp b/dmr/Slot.cpp new file mode 100644 index 00000000..a34cdd14 --- /dev/null +++ b/dmr/Slot.cpp @@ -0,0 +1,715 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/Slot.h" +#include "dmr/acl/AccessControl.h" +#include "dmr/lc/FullLC.h" +#include "dmr/lc/ShortLC.h" +#include "dmr/lc/CSBK.h" +#include "dmr/SlotType.h" +#include "dmr/Sync.h" +#include "edac/BPTC19696.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace dmr; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +uint32_t Slot::m_colorCode = 0U; + +bool Slot::m_embeddedLCOnly = false; +bool Slot::m_dumpTAData = true; + +modem::Modem* Slot::m_modem = NULL; +network::BaseNetwork* Slot::m_network = NULL; +bool Slot::m_duplex = true; +lookups::RadioIdLookup* Slot::m_ridLookup = NULL; +lookups::TalkgroupIdLookup* Slot::m_tidLookup = NULL; +uint32_t Slot::m_hangCount = 3U * 17U; + +lookups::RSSIInterpolator* Slot::m_rssiMapper = NULL; + +uint32_t Slot::m_jitterTime = 360U; +uint32_t Slot::m_jitterSlots = 6U; + +uint8_t* Slot::m_idle = NULL; + +uint8_t Slot::m_flco1; +uint8_t Slot::m_id1 = 0U; +bool Slot::m_voice1 = true; +uint8_t Slot::m_flco2; +uint8_t Slot::m_id2 = 0U; +bool Slot::m_voice2 = true; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Slot class. +/// +/// DMR slot number. +/// Modem frame buffer queue size (bytes). +/// Transmit timeout. +/// Amount of time to hang on the last talkgroup mode from RF. +/// +/// +/// Flag indicating whether DMR debug is enabled. +/// Flag indicating whether DMR verbose logging is enabled. +Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSize, bool dumpDataPacket, bool repeatDataPacket, + bool debug, bool verbose) : + m_slotNo(slotNo), + m_queue(queueSize, "DMR Slot"), + m_rfState(RS_RF_LISTENING), + m_rfLastDstId(0U), + m_netState(RS_NET_IDLE), + m_netLastDstId(0U), + m_rfSeqNo(0U), + m_networkWatchdog(1000U, 0U, 1500U), + m_rfTimeoutTimer(1000U, timeout), + m_netTimeoutTimer(1000U, timeout), + m_packetTimer(1000U, 0U, 50U), + m_networkTGHang(1000U, tgHang), + m_interval(), + m_elapsed(), + m_rfFrames(0U), + m_netFrames(0U), + m_netLost(0U), + m_netMissed(0U), + m_rfBits(1U), + m_netBits(1U), + m_rfErrs(0U), + m_netErrs(0U), + m_rfTimeout(false), + m_netTimeout(false), + m_rssi(0U), + m_maxRSSI(0U), + m_minRSSI(0U), + m_aveRSSI(0U), + m_rssiCount(0U), + m_verbose(verbose), + m_debug(debug) +{ + m_interval.start(); + + m_voice = new VoicePacket(this, m_network, m_embeddedLCOnly, m_dumpTAData, debug, verbose); + m_data = new DataPacket(this, m_network, dumpDataPacket, repeatDataPacket, debug, verbose); +} + +/// +/// Finalizes a instance of the Slot class. +/// +Slot::~Slot() +{ + delete m_voice; + delete m_data; +} + +/// +/// Process DMR data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Slot::processFrame(uint8_t *data, uint32_t len) +{ + assert(data != NULL); + + // Utils::dump(2U, "!!! *RX DMR Raw", data, len); + + if (data[0U] == TAG_LOST && m_rfState == RS_RF_AUDIO) { + if (m_rssi != 0U) { + ::ActivityLog("DMR", true, "Slot %u, RF voice transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", + m_slotNo, float(m_rfFrames) / 16.667F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + } + else { + ::ActivityLog("DMR", true, "Slot %u, RF voice transmission lost, %.1f seconds, BER: %.1f%%", + m_slotNo, float(m_rfFrames) / 16.667F, float(m_rfErrs * 100U) / float(m_rfBits)); + } + + LogMessage(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)); + + if (m_rfTimeout) { + m_data->writeEndRF(); + return false; + } + else { + m_data->writeEndRF(true); + return true; + } + } + + if (data[0U] == TAG_LOST && m_rfState == RS_RF_DATA) { + ::ActivityLog("DMR", true, "Slot %u, RF data transmission lost", m_slotNo); + m_data->writeEndRF(); + return false; + } + + if (data[0U] == TAG_LOST) { + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + return false; + } + + // Have we got RSSI bytes on the end? + if (len == (DMR_FRAME_LENGTH_BYTES + 4U)) { + uint16_t raw = 0U; + raw |= (data[35U] << 8) & 0xFF00U; + raw |= (data[36U] << 0) & 0x00FFU; + + // Convert the raw RSSI to dBm + int rssi = m_rssiMapper->interpolate(raw); + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, raw RSSI = %u, reported RSSI = %d dBm", m_slotNo, raw, rssi); + } + + // RSSI is always reported as positive + m_rssi = (rssi >= 0) ? rssi : -rssi; + + if (m_rssi > m_minRSSI) + m_minRSSI = m_rssi; + if (m_rssi < m_maxRSSI) + m_maxRSSI = m_rssi; + + m_aveRSSI += m_rssi; + m_rssiCount++; + } + + bool dataSync = (data[1U] & DMR_SYNC_DATA) == DMR_SYNC_DATA; + bool voiceSync = (data[1U] & DMR_SYNC_VOICE) == DMR_SYNC_VOICE; + + if (!(dataSync || voiceSync) && m_rfState == RS_RF_LISTENING) { + if (m_verbose) { + uint8_t sync[DMR_SYNC_LENGTH_BYTES]; + ::memcpy(sync, data + 2U, DMR_SYNC_LENGTH_BYTES); + + // count data sync errors + uint8_t dataErrs = 0U; + for (uint8_t i = 0U; i < DMR_SYNC_LENGTH_BYTES; i++) + dataErrs += Utils::countBits8(sync[i] ^ DMR_MS_DATA_SYNC_BYTES[i]); + + // count voice sync errors + uint8_t voiceErrs = 0U; + for (uint8_t i = 0U; i < DMR_SYNC_LENGTH_BYTES; i++) + voiceErrs += Utils::countBits8(sync[i] ^ DMR_MS_VOICE_SYNC_BYTES[i]); + + LogDebug(LOG_RF, "DMR, sync word rejected, dataErrs = %u, voiceErrs = %u", dataErrs, voiceErrs); + } + } + + if ((dataSync || voiceSync) && m_debug) { + Utils::dump(1U, "!!! *RX DMR Modem Frame", data, len); + } + + if ((dataSync || voiceSync) && m_rfState != RS_RF_LISTENING) + m_networkTGHang.start(); + + if (dataSync) { + return m_data->process(data, len); + } + + return m_voice->process(data, len); +} + +/// +/// Get frame data from data ring buffer. +/// +/// Buffer to store frame data. +/// Length of frame data retreived. +uint32_t Slot::getFrame(uint8_t* data) +{ + assert(data != NULL); + + if (m_queue.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_queue.getData(&len, 1U); + + m_queue.getData(data, len); + + return len; +} + +/// +/// Process a data frame from the network. +/// +/// +void Slot::processNetwork(const data::Data& dmrData) +{ + // don't process network frames if the RF modem isn't in a listening state + if (m_rfState != RS_RF_LISTENING) { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic!"); + return; + } + + m_networkWatchdog.start(); + + uint8_t dataType = dmrData.getDataType(); + + switch (dataType) + { + case DT_VOICE_LC_HEADER: + case DT_VOICE_PI_HEADER: + case DT_TERMINATOR_WITH_LC: + case DT_DATA_HEADER: + case DT_CSBK: + case DT_RATE_12_DATA: + case DT_RATE_34_DATA: + case DT_RATE_1_DATA: + m_data->processNetwork(dmrData); + break; + case DT_VOICE_SYNC: + case DT_VOICE: + default: + m_voice->processNetwork(dmrData); + } +} + +/// +/// Updates the DMR slot processor. +/// +void Slot::clock() +{ + uint32_t ms = m_interval.elapsed(); + m_interval.start(); + + m_rfTimeoutTimer.clock(ms); + if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) { + if (!m_rfTimeout) { + LogMessage(LOG_RF, "DMR Slot %u, user has timed out", m_slotNo); + m_rfTimeout = true; + } + } + + m_netTimeoutTimer.clock(ms); + if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) { + if (!m_netTimeout) { + LogMessage(LOG_NET, "DMR Slot %u, user has timed out", m_slotNo); + m_netTimeout = true; + } + } + + if (m_networkTGHang.isRunning()) { + m_networkTGHang.clock(ms); + + if (m_networkTGHang.hasExpired()) { + m_networkTGHang.stop(); + m_rfLastDstId = 0U; + } + } + + if (m_netState == RS_NET_AUDIO || m_netState == RS_NET_DATA) { + m_networkWatchdog.clock(ms); + + if (m_networkWatchdog.hasExpired()) { + if (m_netState == RS_NET_AUDIO) { + // We've received the voice header haven't we? + m_netFrames += 1U; + ::ActivityLog("DMR", false, "Slot %u, network watchdog has expired, %.1f seconds, %u%% packet loss, BER: %.1f%%", + m_slotNo, float(m_netFrames) / 16.667F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits)); + m_data->writeEndNet(true); + } + else { + ::ActivityLog("DMR", false, "Slot %u, network watchdog has expired", m_slotNo); + m_data->writeEndNet(); + } + } + } + + if (m_netState == RS_NET_AUDIO) { + m_packetTimer.clock(ms); + + if (m_packetTimer.isRunning() && m_packetTimer.hasExpired()) { + uint32_t elapsed = m_elapsed.elapsed(); + if (elapsed >= m_jitterTime) { + LogWarning(LOG_NET, "DMR Slot %u, lost audio for %ums filling in", m_slotNo, elapsed); + m_voice->insertSilence(m_jitterSlots); + m_elapsed.start(); + } + + m_packetTimer.start(); + } + } +} + +/// +/// Helper to initialize the DMR slot processor. +/// +/// DMR access color code. +/// +/// +/// Amount of hangtime for a DMR call. +/// Instance of the Modem class. +/// Instance of the BaseNetwork class. +/// Flag indicating full-duplex operation. +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +/// Instance of the CRSSIInterpolator class. +/// +void Slot::init(uint32_t colorCode, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, + network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, + lookups::RSSIInterpolator* rssiMapper, uint32_t jitter) +{ + assert(modem != NULL); + assert(ridLookup != NULL); + assert(tidLookup != NULL); + assert(rssiMapper != NULL); + + m_colorCode = colorCode; + m_embeddedLCOnly = embeddedLCOnly; + m_dumpTAData = dumpTAData; + m_modem = modem; + m_network = network; + m_duplex = duplex; + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; + m_hangCount = callHang * 17U; + + m_rssiMapper = rssiMapper; + + m_jitterTime = jitter; + + float jitter_tmp = float(jitter) / 360.0F; + m_jitterSlots = (uint32_t)(std::ceil(jitter_tmp) * 6.0F); + + m_idle = new uint8_t[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memcpy(m_idle, DMR_IDLE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); + + // Generate the Slot Type for the Idle frame + SlotType slotType; + slotType.setColorCode(colorCode); + slotType.setDataType(DT_IDLE); + slotType.encode(m_idle + 2U); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Write data processed from RF to the data ring buffer. +/// +/// +void Slot::writeQueueRF(const uint8_t *data) +{ + assert(data != NULL); + + if (m_netState != RS_NET_IDLE) + return; + + uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; + + uint32_t space = m_queue.freeSpace(); + if (space < (len + 1U)) { + uint32_t queueLen = m_queue.length(); + m_queue.resize(queueLen + 2500); + + LogError(LOG_DMR, "Slot %u, overflow in the DMR slot RF queue; queue resized was %u is %u", m_slotNo, queueLen, m_queue.length()); + return; + } + + m_queue.addData(&len, 1U); + m_queue.addData(data, len); +} + +/// +/// Write data processed from the network to the data ring buffer. +/// +/// +void Slot::writeQueueNet(const uint8_t *data) +{ + assert(data != NULL); + + uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; + + uint32_t space = m_queue.freeSpace(); + if (space < (len + 1U)) { + LogError(LOG_DMR, "Slot %u, overflow in the DMR slot RF queue", m_slotNo); + return; + } + + m_queue.addData(&len, 1U); + m_queue.addData(data, len); +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +/// +void Slot::writeNetworkRF(const uint8_t* data, uint8_t dataType, uint8_t errors) +{ + assert(data != NULL); + assert(m_data->m_rfLC != NULL); + + writeNetworkRF(data, dataType, m_data->m_rfLC->getFLCO(), m_data->m_rfLC->getSrcId(), m_data->m_rfLC->getDstId(), errors); +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +/// +/// +/// +/// +void Slot::writeNetworkRF(const uint8_t* data, uint8_t dataType, uint8_t flco, uint32_t srcId, + uint32_t dstId, uint8_t errors) +{ + assert(data != NULL); + + if (m_netState != RS_NET_IDLE) + return; + + if (m_network == NULL) + return; + + data::Data dmrData; + dmrData.setSlotNo(m_slotNo); + dmrData.setDataType(dataType); + dmrData.setSrcId(srcId); + dmrData.setDstId(dstId); + dmrData.setFLCO(flco); + dmrData.setN(m_voice->m_rfN); + dmrData.setSeqNo(m_rfSeqNo); + dmrData.setBER(errors); + dmrData.setRSSI(m_rssi); + + m_rfSeqNo++; + + dmrData.setData(data + 2U); + + m_network->writeDMR(dmrData); +} + +/// +/// Helper to write a extended function packet on the RF interface. +/// +/// Extended function opcode. +/// Extended function argument. +/// Destination radio ID. +void Slot::writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId) +{ + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", + m_slotNo, func, arg, dstId); + } + + // generate activity log entry + if (func == DMR_EXT_FNCT_CHECK) { + ::ActivityLog("DMR", true, "Slot %u received radio check request from %u to %u", m_slotNo, arg, dstId); + } + else if (func == DMR_EXT_FNCT_INHIBIT) { + ::ActivityLog("DMR", true, "Slot %u received radio inhibit request from %u to %u", m_slotNo, arg, dstId); + } + else if (func == DMR_EXT_FNCT_UNINHIBIT) { + ::ActivityLog("DMR", true, "Slot %u received radio uninhibit request from %u to %u", m_slotNo, arg, dstId); + } + + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); + + SlotType slotType; + slotType.setColorCode(m_colorCode); + slotType.setDataType(DT_CSBK); + + lc::CSBK csbk = lc::CSBK(false); + csbk.setCSBKO(CSBKO_EXT_FNCT); + csbk.setFID(FID_DMRA); + + csbk.setGI(false); + csbk.setCBF(func); + csbk.setSrcId(arg); + csbk.setDstId(dstId); + + // Regenerate the CSBK data + csbk.encode(data + 2U); + + // 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_duplex); + + m_rfSeqNo = 0U; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_duplex) + writeQueueRF(data); +} + +/// +/// Helper to write a call alert packet on the RF interface. +/// +/// Source radio ID. +/// Destination radio ID. +void Slot::writeRF_Call_Alrt(uint32_t srcId, uint32_t dstId) +{ + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_CALL_ALRT (Call Alert), src = %u, dst = %u", + m_slotNo, srcId, dstId); + } + + ::ActivityLog("DMR", true, "Slot %u received call alert request from %u to %u", m_slotNo, srcId, dstId); + + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); + + SlotType slotType; + slotType.setColorCode(m_colorCode); + slotType.setDataType(DT_CSBK); + + lc::CSBK csbk = lc::CSBK(false); + csbk.setCSBKO(CSBKO_CALL_ALRT); + csbk.setFID(FID_DMRA); + + csbk.setGI(false); + csbk.setSrcId(srcId); + csbk.setDstId(dstId); + + // Regenerate the CSBK data + csbk.encode(data + 2U); + + // 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_duplex); + + m_rfSeqNo = 0U; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_duplex) + writeQueueRF(data); +} + +/// +/// +/// +/// +/// +/// +/// +void Slot::setShortLC(uint32_t slotNo, uint32_t id, uint8_t flco, bool voice) +{ + assert(m_modem != NULL); + + switch (slotNo) { + case 1U: + m_id1 = 0U; + m_flco1 = flco; + m_voice1 = voice; + 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); + } + break; + case 2U: + m_id2 = 0U; + m_flco2 = flco; + m_voice2 = voice; + 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); + } + break; + default: + LogError(LOG_DMR, "invalid slot number passed to setShortLC, slotNo = %u", slotNo); + return; + } + + // 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) + return; + + uint8_t lc[5U]; + lc[0U] = 0x01U; + lc[1U] = 0x00U; + lc[2U] = 0x00U; + lc[3U] = 0x00U; + + if (m_id1 != 0U) { + lc[2U] = m_id1; + if (m_voice1) { + if (m_flco1 == FLCO_GROUP) + lc[1U] |= 0x80U; + else + lc[1U] |= 0x90U; + } + else { + if (m_flco1 == FLCO_GROUP) + lc[1U] |= 0xB0U; + else + lc[1U] |= 0xA0U; + } + } + + if (m_id2 != 0U) { + lc[3U] = m_id2; + if (m_voice2) { + if (m_flco2 == FLCO_GROUP) + lc[1U] |= 0x08U; + else + lc[1U] |= 0x09U; + } + else { + if (m_flco2 == FLCO_GROUP) + lc[1U] |= 0x0BU; + else + lc[1U] |= 0x0AU; + } + } + + lc[4U] = edac::CRC::crc8(lc, 4U); + + uint8_t sLC[9U]; + + lc::ShortLC shortLC; + shortLC.encode(lc, sLC); + + m_modem->writeDMRShortLC(sLC); +} diff --git a/dmr/Slot.h b/dmr/Slot.h new file mode 100644 index 00000000..2114c2ca --- /dev/null +++ b/dmr/Slot.h @@ -0,0 +1,186 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_SLOT_H__) +#define __DMR_SLOT_H__ + +#include "Defines.h" +#include "dmr/Control.h" +#include "dmr/DataPacket.h" +#include "dmr/VoicePacket.h" +#include "modem/Modem.h" +#include "network/BaseNetwork.h" +#include "lookups/RSSIInterpolator.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" +#include "RingBuffer.h" +#include "StopWatch.h" +#include "Timer.h" + +#include + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API Control; + class HOST_SW_API VoicePacket; + class HOST_SW_API DataPacket; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling DMR slots. + // --------------------------------------------------------------------------- + + class HOST_SW_API Slot { + public: + /// Initializes a new instance of the Slot class. + Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSize, bool dumpDataPacket, bool repeatDataPacket, + bool debug, bool verbose); + /// Finalizes a instance of the Slot class. + ~Slot(); + + /// Process a data frame from the RF interface. + bool processFrame(uint8_t* data, uint32_t len); + /// Get frame data from data ring buffer. + uint32_t getFrame(uint8_t* data); + + /// Process a data frames from the network. + void processNetwork(const data::Data& data); + + /// Updates the slot processor. + void clock(); + + /// Helper to initialize the slot processor. + static void init(uint32_t colorCode, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem, + network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, + lookups::RSSIInterpolator* rssiMapper, uint32_t jitter); + + private: + friend class Control; + friend class VoicePacket; + VoicePacket* m_voice; + friend class DataPacket; + DataPacket* m_data; + + uint32_t m_slotNo; + + RingBuffer m_queue; + + RPT_RF_STATE m_rfState; + uint32_t m_rfLastDstId; + RPT_NET_STATE m_netState; + uint32_t m_netLastDstId; + + uint8_t m_rfSeqNo; + + Timer m_networkWatchdog; + Timer m_rfTimeoutTimer; + Timer m_netTimeoutTimer; + Timer m_packetTimer; + Timer m_networkTGHang; + + StopWatch m_interval; + StopWatch m_elapsed; + + uint32_t m_rfFrames; + uint32_t m_netFrames; + uint32_t m_netLost; + uint32_t m_netMissed; + + uint32_t m_rfBits; + uint32_t m_netBits; + uint32_t m_rfErrs; + uint32_t m_netErrs; + + bool m_rfTimeout; + bool m_netTimeout; + + uint8_t m_rssi; + uint8_t m_maxRSSI; + uint8_t m_minRSSI; + uint32_t m_aveRSSI; + uint32_t m_rssiCount; + + bool m_verbose; + bool m_debug; + + static uint32_t m_colorCode; + + static bool m_embeddedLCOnly; + static bool m_dumpTAData; + + static modem::Modem* m_modem; + static network::BaseNetwork* m_network; + + static bool m_duplex; + + static lookups::RadioIdLookup* m_ridLookup; + static lookups::TalkgroupIdLookup* m_tidLookup; + + static uint32_t m_hangCount; + + static lookups::RSSIInterpolator* m_rssiMapper; + + static uint32_t m_jitterTime; + static uint32_t m_jitterSlots; + + static uint8_t* m_idle; + + static uint8_t m_flco1; + static uint8_t m_id1; + static bool m_voice1; + + static uint8_t m_flco2; + static uint8_t m_id2; + static bool m_voice2; + + /// Write data processed from RF to the data ring buffer. + void writeQueueRF(const uint8_t* data); + /// Write data processed from the network to the data ring buffer. + void writeQueueNet(const uint8_t* data); + /// Write data processed from RF to the network. + void writeNetworkRF(const uint8_t* data, uint8_t dataType, uint8_t errors = 0U); + /// Write data processed from RF to the network. + void writeNetworkRF(const uint8_t* data, uint8_t dataType, uint8_t flco, uint32_t srcId, + uint32_t dstId, uint8_t errors = 0U); + + /// Helper to write a extended function packet on the RF interface. + void writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId); + /// Helper to write a call alert packet on the RF interface. + void writeRF_Call_Alrt(uint32_t srcId, uint32_t dstId); + + /// + static void setShortLC(uint32_t slotNo, uint32_t id, uint8_t flco = FLCO_GROUP, bool voice = true); + }; +} // namespace dmr + +#endif // __DMR_SLOT_H__ diff --git a/dmr/SlotType.cpp b/dmr/SlotType.cpp new file mode 100644 index 00000000..c6824c5a --- /dev/null +++ b/dmr/SlotType.cpp @@ -0,0 +1,104 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/SlotType.h" +#include "edac/Golay2087.h" + +using namespace dmr; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the SlotType class. +/// +SlotType::SlotType() : + m_colorCode(0U), + m_dataType(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the SlotType class. +/// +SlotType::~SlotType() +{ + /* stub */ +} + +/// +/// Decodes DMR slot type. +/// +/// +void SlotType::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t DMRSlotType[3U]; + DMRSlotType[0U] = (data[12U] << 2) & 0xFCU; + DMRSlotType[0U] |= (data[13U] >> 6) & 0x03U; + + DMRSlotType[1U] = (data[13U] << 2) & 0xC0U; + DMRSlotType[1U] |= (data[19U] << 2) & 0x3CU; + DMRSlotType[1U] |= (data[20U] >> 6) & 0x03U; + + DMRSlotType[2U] = (data[20U] << 2) & 0xF0U; + + uint8_t code = edac::Golay2087::decode(DMRSlotType); + + m_colorCode = (code >> 4) & 0x0FU; + m_dataType = (code >> 0) & 0x0FU; +} + +/// +/// Encodes DMR slot type. +/// +/// +void SlotType::encode(uint8_t* data) const +{ + assert(data != NULL); + + uint8_t DMRSlotType[3U]; + DMRSlotType[0U] = (m_colorCode << 4) & 0xF0U; + DMRSlotType[0U] |= (m_dataType << 0) & 0x0FU; + DMRSlotType[1U] = 0x00U; + DMRSlotType[2U] = 0x00U; + + edac::Golay2087::encode(DMRSlotType); + + data[12U] = (data[12U] & 0xC0U) | ((DMRSlotType[0U] >> 2) & 0x3FU); + data[13U] = (data[13U] & 0x0FU) | ((DMRSlotType[0U] << 6) & 0xC0U) | ((DMRSlotType[1U] >> 2) & 0x30U); + data[19U] = (data[19U] & 0xF0U) | ((DMRSlotType[1U] >> 2) & 0x0FU); + data[20U] = (data[20U] & 0x03U) | ((DMRSlotType[1U] << 6) & 0xC0U) | ((DMRSlotType[2U] >> 2) & 0x3CU); +} diff --git a/dmr/SlotType.h b/dmr/SlotType.h new file mode 100644 index 00000000..77ed0ac6 --- /dev/null +++ b/dmr/SlotType.h @@ -0,0 +1,63 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_SLOT_TYPE_H__) +#define __DMR_SLOT_TYPE_H__ + +#include "Defines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents DMR slot type. + // --------------------------------------------------------------------------- + + class HOST_SW_API SlotType { + public: + /// Initializes a new instance of the SlotType class. + SlotType(); + /// Finalizes a instance of the SlotType class. + ~SlotType(); + + /// Decodes DMR slot type. + void decode(const uint8_t* data); + /// Encodes DMR slot type. + void encode(uint8_t* data) const; + + public: + /// DMR access color code. + __PROPERTY(uint8_t, colorCode, ColorCode); + + /// + __PROPERTY(uint8_t, dataType, DataType); + }; +} // namespace dmr + +#endif // __DMR_SLOT_TYPE_H__ diff --git a/dmr/Sync.cpp b/dmr/Sync.cpp new file mode 100644 index 00000000..93af3f15 --- /dev/null +++ b/dmr/Sync.cpp @@ -0,0 +1,79 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/Sync.h" + +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Helper to append DMR data sync bytes to the passed buffer. +/// +/// Buffer to append sync bytes to. +/// Flag indicating whether this is duplex operation. +void Sync::addDMRDataSync(uint8_t* data, bool duplex) +{ + assert(data != NULL); + + if (duplex) { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_DATA_SYNC[i]; + } + else { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_DATA_SYNC[i]; + } +} + +/// +/// Helper to append DMR voice sync bytes to the passed buffer. +/// +/// Buffer to append sync bytes to. +/// Flag indicating whether this is duplex operation. +void Sync::addDMRAudioSync(uint8_t* data, bool duplex) +{ + assert(data != NULL); + + if (duplex) { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | BS_SOURCED_AUDIO_SYNC[i]; + } + else { + for (uint32_t i = 0U; i < 7U; i++) + data[i + 13U] = (data[i + 13U] & ~SYNC_MASK[i]) | MS_SOURCED_AUDIO_SYNC[i]; + } +} diff --git a/dmr/Sync.h b/dmr/Sync.h new file mode 100644 index 00000000..0711c4fb --- /dev/null +++ b/dmr/Sync.h @@ -0,0 +1,51 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_SYNC_H__) +#define __DMR_SYNC_H__ + +#include "Defines.h" + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Helper class for generating DMR sync data. + // --------------------------------------------------------------------------- + + class HOST_SW_API Sync { + public: + /// Helper to append DMR data sync bytes to the passed buffer. + static void addDMRDataSync(uint8_t* data, bool duplex); + /// Helper to append DMR voice sync bytes to the passed buffer. + static void addDMRAudioSync(uint8_t* data, bool duplex); + }; +} // namespace dmr + +#endif // __DMR_SYNC_H__ diff --git a/dmr/VoicePacket.cpp b/dmr/VoicePacket.cpp new file mode 100644 index 00000000..6d0cc8f3 --- /dev/null +++ b/dmr/VoicePacket.cpp @@ -0,0 +1,849 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017,2018 Jonathan Naylor, G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/VoicePacket.h" +#include "dmr/acl/AccessControl.h" +#include "dmr/data/DataHeader.h" +#include "dmr/data/EMB.h" +#include "dmr/edac/Trellis.h" +#include "dmr/lc/CSBK.h" +#include "dmr/lc/ShortLC.h" +#include "dmr/lc/FullLC.h" +#include "dmr/SlotType.h" +#include "dmr/Sync.h" +#include "edac/BPTC19696.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace dmr; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- +#define CHECK_TRAFFIC_COLLISION_DELLC(_DST_ID) \ + if (m_slot->m_netState != RS_NET_IDLE && _DST_ID == m_slot->m_netLastDstId) { \ + LogWarning(LOG_RF, "DMR Slot %u, Traffic collision detect, preempting new RF traffic to existing network traffic!", m_slot->m_slotNo); \ + delete lc; \ + return false; \ + } + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Process DMR voice frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool VoicePacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + bool voiceSync = (data[1U] & DMR_SYNC_VOICE) == DMR_SYNC_VOICE; + + if (voiceSync) { + if (m_slot->m_rfState == RS_RF_AUDIO) { + m_lastRfN = 0U; + + // Convert the Audio Sync to be from the BS or MS as needed + Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + + uint32_t errors = 0U; + uint8_t fid = m_slot->m_data->m_rfLC->getFID(); + if (fid == FID_ETSI || fid == FID_DMRA) { + errors = m_fec.regenerateDMR(data + 2U); + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DMR_SYNC_VOICE audio, sequence no = 0, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, errors, float(errors) / 1.41F); + } + + m_slot->m_rfErrs += errors; + } + + m_slot->m_rfBits += 141U; + m_slot->m_rfFrames++; + + m_rfEmbeddedReadN = (m_rfEmbeddedReadN + 1U) % 2U; + m_rfEmbeddedWriteN = (m_rfEmbeddedWriteN + 1U) % 2U; + m_rfEmbeddedData[m_rfEmbeddedWriteN].reset(); + + if (!m_slot->m_rfTimeout) { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex) + m_slot->writeQueueRF(data); + + m_slot->writeNetworkRF(data, DT_VOICE_SYNC, errors); + return true; + } + + return false; + } + else if (m_slot->m_rfState == RS_RF_LISTENING) { + m_rfEmbeddedLC.reset(); + m_slot->m_rfState = RS_RF_LATE_ENTRY; + return false; + } + } + else { + if (m_slot->m_rfState == RS_RF_AUDIO) { + m_rfN = data[1U] & 0x0FU; + + if (m_rfN > 5U) + return false; + if (m_rfN == m_lastRfN) + return false; + if (m_rfN != (m_lastRfN + 1U)) + return false; + m_lastRfN = m_rfN; + + uint32_t errors = 0U; + uint8_t fid = m_slot->m_data->m_rfLC->getFID(); + if (fid == FID_ETSI || fid == FID_DMRA) { + errors = m_fec.regenerateDMR(data + 2U); + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DMR_SYNC_VOICE audio, sequence no = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_rfN, errors, float(errors) / 1.41F); + } + + m_slot->m_rfErrs += errors; + } + + m_slot->m_rfBits += 141U; + m_slot->m_rfFrames++; + + // Get the LCSS from the EMB + data::EMB emb; + emb.decode(data + 2U); + uint8_t lcss = emb.getLCSS(); + + // Dump any interesting Embedded Data + bool ret = m_rfEmbeddedData[m_rfEmbeddedWriteN].addData(data + 2U, lcss); + if (ret) { + uint8_t flco = m_rfEmbeddedData[m_rfEmbeddedWriteN].getFLCO(); + + uint8_t data[9U]; + m_rfEmbeddedData[m_rfEmbeddedWriteN].getRawData(data); + + char text[80U]; + switch (flco) { + case FLCO_GROUP: + case FLCO_PRIVATE: + // ::sprintf(text, "DMR Slot %u, Embedded LC", m_slotNo); + // Utils::dump(1U, text, data, 9U); + break; + + case FLCO_GPS_INFO: + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_GPS_INFO (Embedded GPS Info)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + if (m_verbose) { + logGPSPosition(m_slot->m_data->m_rfLC->getSrcId(), data); + } + break; + + case FLCO_TALKER_ALIAS_HEADER: + if (!(m_rfTalkerId & TALKER_ID_HEADER)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_HEADER (Embedded Talker Alias Header)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_rfTalkerId |= TALKER_ID_HEADER; + } + break; + + case FLCO_TALKER_ALIAS_BLOCK1: + if (!(m_rfTalkerId & TALKER_ID_BLOCK1)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK1 (Embedded Talker Alias Block 1)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_rfTalkerId |= TALKER_ID_BLOCK1; + } + break; + + case FLCO_TALKER_ALIAS_BLOCK2: + if (!(m_rfTalkerId & TALKER_ID_BLOCK2)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK2 (Embedded Talker Alias Block 2)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_rfTalkerId |= TALKER_ID_BLOCK2; + } + break; + + case FLCO_TALKER_ALIAS_BLOCK3: + if (!(m_rfTalkerId & TALKER_ID_BLOCK3)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK3 (Embedded Talker Alias Block 3)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_rfTalkerId |= TALKER_ID_BLOCK3; + } + break; + + default: + ::sprintf(text, "DMR Slot %u, Unknown Embedded Data", m_slot->m_slotNo); + Utils::dump(1U, text, data, 9U); + break; + } + } + + // Regenerate the previous super blocks Embedded Data or substitude the LC for it + if (m_rfEmbeddedData[m_rfEmbeddedReadN].isValid()) + lcss = m_rfEmbeddedData[m_rfEmbeddedReadN].getData(data + 2U, m_rfN); + else + lcss = m_rfEmbeddedLC.getData(data + 2U, m_rfN); + + // Regenerate the EMB + emb.setColorCode(m_slot->m_colorCode); + emb.setLCSS(lcss); + emb.encode(data + 2U); + + if (!m_slot->m_rfTimeout) { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + m_slot->writeNetworkRF(data, DT_VOICE, errors); + + if (m_embeddedLCOnly) { + // Only send the previously received LC + lcss = m_rfEmbeddedLC.getData(data + 2U, m_rfN); + + // Regenerate the EMB + emb.setColorCode(m_slot->m_colorCode); + emb.setLCSS(lcss); + emb.encode(data + 2U); + } + + if (m_slot->m_duplex) + m_slot->writeQueueRF(data); + + return true; + } + + return false; + } + else if (m_slot->m_rfState == RS_RF_LATE_ENTRY) { + data::EMB emb; + emb.decode(data + 2U); + + // 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) + return false; + + m_rfEmbeddedLC.addData(data + 2U, emb.getLCSS()); + lc::LC* lc = m_rfEmbeddedLC.getLC(); + if (lc != NULL) { + uint32_t srcId = lc->getSrcId(); + uint32_t dstId = lc->getDstId(); + uint8_t flco = lc->getFLCO(); + + CHECK_TRAFFIC_COLLISION_DELLC(dstId); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, "DMR Slot %u, DMR_DATA_SYNC denial, RID rejection, srcId = %u", m_slot->m_slotNo, srcId); + delete lc; + return false; + } + + // validate the target ID, if the target is a talkgroup + if (flco == FLCO_GROUP) { + if (!acl::AccessControl::validateTGId(m_slot->m_slotNo, dstId)) { + LogWarning(LOG_RF, "DMR Slot %u, DMR_DATA_SYNC denial, TGID rejection, srcId = %u, dstId = %u", m_slot->m_slotNo, srcId, dstId); + delete lc; + return false; + } + } + + m_slot->m_data->m_rfLC = lc; + + // The standby LC data + m_rfEmbeddedLC.setLC(*m_slot->m_data->m_rfLC); + m_rfEmbeddedData[0U].setLC(*m_slot->m_data->m_rfLC); + m_rfEmbeddedData[1U].setLC(*m_slot->m_data->m_rfLC); + + // 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); + + lc::FullLC fullLC; + fullLC.encode(*m_slot->m_data->m_rfLC, start + 2U, DT_VOICE_LC_HEADER); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_VOICE_LC_HEADER); + slotType.encode(start + 2U); + + start[0U] = TAG_DATA; + start[1U] = 0x00U; + + m_slot->m_rfTimeoutTimer.start(); + m_slot->m_rfTimeout = false; + + m_slot->m_rfFrames = 0U; + m_slot->m_rfSeqNo = 0U; + m_slot->m_rfBits = 1U; + m_slot->m_rfErrs = 0U; + + m_rfEmbeddedReadN = 0U; + m_rfEmbeddedWriteN = 1U; + m_rfTalkerId = TALKER_ID_NONE; + + m_slot->m_minRSSI = m_slot->m_rssi; + m_slot->m_maxRSSI = m_slot->m_rssi; + m_slot->m_aveRSSI = m_slot->m_rssi; + m_slot->m_rssiCount = 1U; + + if (m_slot->m_duplex) { + m_slot->m_queue.clear(); + m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + + for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) + m_slot->writeQueueRF(start); + } + + m_slot->writeNetworkRF(start, DT_VOICE_LC_HEADER); + + m_rfN = data[1U] & 0x0FU; + + if (m_rfN > 5U) + return false; + if (m_rfN == m_lastRfN) + return false; + m_lastRfN = m_rfN; + + // Regenerate the EMB + emb.encode(data + 2U); + + // Send the original audio frame out + uint32_t errors = 0U; + uint8_t fid = m_slot->m_data->m_rfLC->getFID(); + if (fid == FID_ETSI || fid == FID_DMRA) { + errors = m_fec.regenerateDMR(data + 2U); + if (m_verbose) { + LogMessage(LOG_RF, "DMR Slot %u, DMR_AUDIO_SYNC audio, sequence no = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_rfN, errors, float(errors) / 1.41F); + } + + m_slot->m_rfErrs += errors; + } + + m_slot->m_rfBits += 141U; + m_slot->m_rfFrames++; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex) + m_slot->writeQueueRF(data); + + m_slot->writeNetworkRF(data, DT_VOICE, errors); + + m_slot->m_rfState = RS_RF_AUDIO; + m_slot->m_rfLastDstId = dstId; + + if (m_slot->m_netState == RS_NET_IDLE) { + m_slot->setShortLC(m_slot->m_slotNo, dstId, flco, true); + } + + ::ActivityLog("DMR", true, "Slot %u, received RF late entry from %u to %s%u", m_slot->m_slotNo, srcId, flco == FLCO_GROUP ? "TG " : "", dstId); + return true; + } + } + } + + return false; +} + +/// +/// Process a voice frame from the network. +/// +/// +void VoicePacket::processNetwork(const data::Data& dmrData) +{ + uint8_t dataType = dmrData.getDataType(); + + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + dmrData.getData(data + 2U); + + if (dataType == DT_VOICE_SYNC) { + if (m_slot->m_netState == RS_NET_IDLE) { + lc::LC* lc = new lc::LC(dmrData.getFLCO(), dmrData.getSrcId(), dmrData.getDstId()); + + uint32_t dstId = lc->getDstId(); + uint32_t srcId = lc->getSrcId(); + + m_slot->m_data->m_netLC = lc; + + // The standby LC data + m_netEmbeddedLC.setLC(*m_slot->m_data->m_netLC); + m_netEmbeddedData[0U].setLC(*m_slot->m_data->m_netLC); + m_netEmbeddedData[1U].setLC(*m_slot->m_data->m_netLC); + + m_lastFrameValid = false; + + m_slot->m_netTimeoutTimer.start(); + m_slot->m_netTimeout = false; + + if (m_slot->m_duplex) { + m_slot->m_queue.clear(); + m_slot->m_modem->writeDMRAbort(m_slot->m_slotNo); + } + + for (uint32_t i = 0U; i < m_slot->m_jitterSlots; i++) + m_slot->writeQueueNet(m_slot->m_idle); + + // Create a dummy start frame + uint8_t start[DMR_FRAME_LENGTH_BYTES + 2U]; + + Sync::addDMRDataSync(start + 2U, m_slot->m_duplex); + + lc::FullLC fullLC; + fullLC.encode(*m_slot->m_data->m_netLC, start + 2U, DT_VOICE_LC_HEADER); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_VOICE_LC_HEADER); + slotType.encode(start + 2U); + + start[0U] = TAG_DATA; + start[1U] = 0x00U; + + if (m_slot->m_duplex) { + for (uint32_t i = 0U; i < NO_HEADERS_DUPLEX; i++) + m_slot->writeQueueRF(start); + } + else { + for (uint32_t i = 0U; i < NO_HEADERS_SIMPLEX; i++) + m_slot->writeQueueRF(start); + } + + m_slot->m_netFrames = 0U; + m_slot->m_netLost = 0U; + m_slot->m_netBits = 1U; + m_slot->m_netErrs = 0U; + + m_netEmbeddedReadN = 0U; + m_netEmbeddedWriteN = 1U; + m_netTalkerId = TALKER_ID_NONE; + + m_slot->m_netState = RS_NET_AUDIO; + m_slot->m_netLastDstId = dstId; + + m_slot->setShortLC(m_slot->m_slotNo, dstId, m_slot->m_data->m_netLC->getFLCO(), true); + + ::ActivityLog("DMR", false, "Slot %u, received network late entry from %u to %s%u", + m_slot->m_slotNo, srcId, m_slot->m_data->m_netLC->getFLCO() == FLCO_GROUP ? "TG " : "", dstId); + } + + if (m_slot->m_netState == RS_NET_AUDIO) { + uint8_t fid = m_slot->m_data->m_netLC->getFID(); + if (fid == FID_ETSI || fid == FID_DMRA) { + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DMR_SYNC_VOICE audio, sequence no = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); + } + } + m_slot->m_netBits += 141U; + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + // Convert the Audio Sync to be from the BS or MS as needed + Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + + // Initialise the lost packet data + if (m_slot->m_netFrames == 0U) { + ::memcpy(m_lastFrame, data, DMR_FRAME_LENGTH_BYTES + 2U); + m_lastFrameValid = true; + m_netN = 5U; + m_slot->m_netLost = 0U; + } + + if (!m_slot->m_netTimeout) + m_slot->writeQueueNet(data); + + m_netEmbeddedReadN = (m_netEmbeddedReadN + 1U) % 2U; + m_netEmbeddedWriteN = (m_netEmbeddedWriteN + 1U) % 2U; + + m_netEmbeddedData[m_netEmbeddedWriteN].reset(); + + m_slot->m_packetTimer.start(); + m_slot->m_elapsed.start(); + + m_slot->m_netFrames++; + + // Save details in case we need to infill data + m_netN = dmrData.getN(); + } + } + else if (dataType == DT_VOICE) { + if (m_slot->m_netState != RS_NET_AUDIO) + return; + + uint8_t fid = m_slot->m_data->m_netLC->getFID(); + if (fid == FID_ETSI || fid == FID_DMRA) { + m_slot->m_netErrs += m_fec.regenerateDMR(data + 2U); + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DMR_SYNC_VOICE audio, sequence no = %u, errs = %u/141 (%.1f%%)", + m_slot->m_slotNo, m_netN, m_slot->m_netErrs, float(m_slot->m_netErrs) / 1.41F); + } + } + m_slot->m_netBits += 141U; + + // Get the LCSS from the EMB + data::EMB emb; + emb.decode(data + 2U); + uint8_t lcss = emb.getLCSS(); + + // Dump any interesting Embedded Data + bool ret = m_netEmbeddedData[m_netEmbeddedWriteN].addData(data + 2U, lcss); + if (ret) { + uint8_t flco = m_netEmbeddedData[m_netEmbeddedWriteN].getFLCO(); + + uint8_t data[9U]; + m_netEmbeddedData[m_netEmbeddedWriteN].getRawData(data); + + char text[80U]; + switch (flco) { + case FLCO_GROUP: + case FLCO_PRIVATE: + // ::sprintf(text, "DMR Slot %u, Embedded LC", m_slotNo); + // Utils::dump(1U, text, data, 9U); + break; + case FLCO_GPS_INFO: + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_GPS_INFO (Embedded GPS Info)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + logGPSPosition(m_slot->m_data->m_netLC->getSrcId(), data); + break; + case FLCO_TALKER_ALIAS_HEADER: + if (!(m_netTalkerId & TALKER_ID_HEADER)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_HEADER (Embedded Talker Alias Header)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_netTalkerId |= TALKER_ID_HEADER; + } + break; + case FLCO_TALKER_ALIAS_BLOCK1: + if (!(m_netTalkerId & TALKER_ID_BLOCK1)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK1 (Embedded Talker Alias Block 1)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_netTalkerId |= TALKER_ID_BLOCK1; + } + break; + case FLCO_TALKER_ALIAS_BLOCK2: + if (!(m_netTalkerId & TALKER_ID_BLOCK2)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK2 (Embedded Talker Alias Block 2)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_netTalkerId |= TALKER_ID_BLOCK2; + } + break; + case FLCO_TALKER_ALIAS_BLOCK3: + if (!(m_netTalkerId & TALKER_ID_BLOCK3)) { + if (m_dumpTAData) { + ::sprintf(text, "DMR Slot %u, FLCO_TALKER_ALIAS_BLOCK3 (Embedded Talker Alias Block 3)", m_slot->m_slotNo); + Utils::dump(2U, text, data, 9U); + } + + m_netTalkerId |= TALKER_ID_BLOCK3; + } + break; + default: + ::sprintf(text, "DMR Slot %u, Unknown Embedded Data", m_slot->m_slotNo); + Utils::dump(1U, text, data, 9U); + break; + } + } + + if (m_embeddedLCOnly) { + // Only send the previously received LC + lcss = m_netEmbeddedLC.getData(data + 2U, dmrData.getN()); + } + else { + // Regenerate the previous super blocks Embedded Data or substitude the LC for it + if (m_netEmbeddedData[m_netEmbeddedReadN].isValid()) + lcss = m_netEmbeddedData[m_netEmbeddedReadN].getData(data + 2U, dmrData.getN()); + else + lcss = m_netEmbeddedLC.getData(data + 2U, dmrData.getN()); + } + + // Regenerate the EMB + emb.setColorCode(m_slot->m_colorCode); + emb.setLCSS(lcss); + emb.encode(data + 2U); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + // Initialise the lost packet data + if (m_slot->m_netFrames == 0U) { + ::memcpy(m_lastFrame, data, DMR_FRAME_LENGTH_BYTES + 2U); + m_lastFrameValid = true; + m_netN = 5U; + m_slot->m_netLost = 0U; + } + + if (insertSilence(data, dmrData.getN())) { + if (!m_slot->m_netTimeout) + m_slot->writeQueueNet(data); + } + + m_slot->m_packetTimer.start(); + m_slot->m_elapsed.start(); + + m_slot->m_netFrames++; + + // Save details in case we need to infill data + m_netN = dmrData.getN(); + } + else { + // Unhandled data type + LogWarning(LOG_NET, "DMR Slot %u, unhandled network data, type = $%02X", m_slot->m_slotNo, dataType); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the VoicePacket class. +/// +/// DMR slot. +/// Instance of the BaseNetwork class. +/// +/// +/// Flag indicating whether DMR debug is enabled. +/// Flag indicating whether DMR verbose logging is enabled. +VoicePacket::VoicePacket(Slot* slot, network::BaseNetwork* network, bool embeddedLCOnly, bool dumpTAData, bool debug, bool verbose) : + m_slot(slot), + m_lastFrame(NULL), + m_lastFrameValid(false), + m_rfN(0U), + m_lastRfN(0U), + m_netN(0U), + m_rfEmbeddedLC(), + m_rfEmbeddedData(NULL), + m_rfEmbeddedReadN(0U), + m_rfEmbeddedWriteN(1U), + m_netEmbeddedLC(), + m_netEmbeddedData(NULL), + m_netEmbeddedReadN(0U), + m_netEmbeddedWriteN(1U), + m_rfTalkerId(TALKER_ID_NONE), + m_netTalkerId(TALKER_ID_NONE), + m_fec(), + m_embeddedLCOnly(embeddedLCOnly), + m_dumpTAData(dumpTAData), + m_verbose(verbose), + m_debug(debug) +{ + m_lastFrame = new uint8_t[DMR_FRAME_LENGTH_BYTES + 2U]; + + m_rfEmbeddedData = new data::EmbeddedData[2U]; + m_netEmbeddedData = new data::EmbeddedData[2U]; +} + +/// +/// Finalizes a instance of the VoicePacket class. +/// +VoicePacket::~VoicePacket() +{ + delete[] m_lastFrame; + + delete[] m_rfEmbeddedData; + delete[] m_netEmbeddedData; +} + +/// +/// +/// +/// Source radio ID. +/// +void VoicePacket::logGPSPosition(const uint32_t srcId, const uint8_t* data) +{ + uint32_t errorVal = (data[2U] & 0x0E) >> 1U; + + const char* error; + switch (errorVal) { + case 0U: + error = "< 2m"; + break; + case 1U: + error = "< 20m"; + break; + case 2U: + error = "< 200m"; + break; + case 3U: + error = "< 2km"; + break; + case 4U: + error = "< 20km"; + break; + case 5U: + error = "< 200km"; + break; + case 6U: + error = "> 200km"; + break; + default: + error = "not known"; + break; + } + + int longitudeVal = ((data[2U] & 0x01U) << 31) | (data[3U] << 23) | (data[4U] << 15) | (data[5U] << 7); + longitudeVal >>= 7; + + int latitudeVal = (data[6U] << 24) | (data[7U] << 16) | (data[8U] << 8); + latitudeVal >>= 8; + + float longitude = 360.0F / 33554432.0F; // 360/2^25 steps + float latitude = 180.0F / 16777216.0F; // 180/2^24 steps + + longitude *= float(longitudeVal); + latitude *= float(latitudeVal); + + LogMessage(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); +} + +/// +/// Helper to insert DMR AMBE silence frames. +/// +/// +/// +/// +bool VoicePacket::insertSilence(const uint8_t* data, uint8_t seqNo) +{ + assert(data != NULL); + + // Do not send duplicate + if (seqNo == m_netN) + return false; + + // Check to see if we have any spaces to fill? + uint8_t seq = (m_netN + 1U) % 6U; + if (seq == seqNo) { + // Just copy the data, nothing else to do here + ::memcpy(m_lastFrame, data, DMR_FRAME_LENGTH_BYTES + 2U); + m_lastFrameValid = true; + return true; + } + + uint32_t count = (seqNo - seq + 6U) % 6U; + + insertSilence(count); + + ::memcpy(m_lastFrame, data, DMR_FRAME_LENGTH_BYTES + 2U); + m_lastFrameValid = true; + + return true; +} + +/// +/// Helper to insert DMR AMBE silence frames. +/// +/// +void VoicePacket::insertSilence(uint32_t count) +{ + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + + if (m_lastFrameValid) { + ::memcpy(data, m_lastFrame, 2U); // The control data + ::memcpy(data + 2U, m_lastFrame + 24U + 2U, 9U); // Copy the last audio block to the first + ::memcpy(data + 24U + 2U, data + 2U, 9U); // Copy the last audio block to the last + ::memcpy(data + 9U + 2U, data + 2U, 5U); // Copy the last audio block to the middle (1/2) + ::memcpy(data + 19U + 2U, data + 4U + 2U, 5U); // Copy the last audio block to the middle (2/2) + } + else { + // Not sure what to do if this isn't AMBE audio + ::memcpy(data, DMR_SILENCE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); + } + + uint8_t n = (m_netN + 1U) % 6U; + + uint8_t fid = m_slot->m_data->m_netLC->getFID(); + + data::EMB emb; + emb.setColorCode(m_slot->m_colorCode); + + for (uint32_t i = 0U; i < count; i++) { + // Only use our silence frame if its AMBE audio data + if (fid == FID_ETSI || fid == FID_DMRA) { + if (i > 0U) { + ::memcpy(data, DMR_SILENCE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); + m_lastFrameValid = false; + } + } + + if (n == 0U) { + Sync::addDMRAudioSync(data + 2U, m_slot->m_duplex); + } + else { + uint8_t lcss = m_netEmbeddedLC.getData(data + 2U, n); + emb.setLCSS(lcss); + emb.encode(data + 2U); + } + + m_slot->writeQueueNet(data); + + m_netN = n; + + m_slot->m_netFrames++; + m_slot->m_netLost++; + + n = (n + 1U) % 6U; + } +} diff --git a/dmr/VoicePacket.h b/dmr/VoicePacket.h new file mode 100644 index 00000000..ef7d5ad4 --- /dev/null +++ b/dmr/VoicePacket.h @@ -0,0 +1,114 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_VOICE_PACKET_H__) +#define __DMR_VOICE_PACKET_H__ + +#include "Defines.h" +#include "dmr/data/Data.h" +#include "dmr/data/EmbeddedData.h" +#include "dmr/lc/LC.h" +#include "dmr/Slot.h" +#include "edac/AMBEFEC.h" +#include "network/BaseNetwork.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +#include + +namespace dmr +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API DataPacket; + class HOST_SW_API Slot; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling DMR voice packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API VoicePacket { + public: + /// Process a voice frame from the RF interface. + bool process(uint8_t* data, uint32_t len); + /// Process a voice frame from the network. + void processNetwork(const data::Data& dmrData); + + private: + friend class DataPacket; + friend class Slot; + Slot* m_slot; + + uint8_t* m_lastFrame; + bool m_lastFrameValid; + + uint8_t m_rfN; + uint8_t m_lastRfN; + uint8_t m_netN; + + data::EmbeddedData m_rfEmbeddedLC; + data::EmbeddedData* m_rfEmbeddedData; + uint32_t m_rfEmbeddedReadN; + uint32_t m_rfEmbeddedWriteN; + + data::EmbeddedData m_netEmbeddedLC; + data::EmbeddedData* m_netEmbeddedData; + uint32_t m_netEmbeddedReadN; + uint32_t m_netEmbeddedWriteN; + + uint8_t m_rfTalkerId; + uint8_t m_netTalkerId; + + edac::AMBEFEC m_fec; + + bool m_embeddedLCOnly; + bool m_dumpTAData; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the VoicePacket class. + VoicePacket(Slot* slot, network::BaseNetwork* network, bool embeddedLCOnly, bool dumpTAData, bool debug, bool verbose); + /// Finalizes a instance of the VoicePacket class. + ~VoicePacket(); + + /// + void logGPSPosition(const uint32_t srcId, const uint8_t* data); + + /// Helper to insert DMR AMBE silence frames. + bool insertSilence(const uint8_t* data, uint8_t seqNo); + /// Helper to insert DMR AMBE silence frames. + void insertSilence(uint32_t count); + }; +} // namespace dmr + +#endif // __DMR_VOICE_PACKET_H__ diff --git a/dmr/acl/AccessControl.cpp b/dmr/acl/AccessControl.cpp new file mode 100644 index 00000000..627db247 --- /dev/null +++ b/dmr/acl/AccessControl.cpp @@ -0,0 +1,108 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +*/ +#include "Defines.h" +#include "dmr/acl/AccessControl.h" +#include "Log.h" + +using namespace dmr::acl; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +RadioIdLookup* AccessControl::m_ridLookup; +TalkgroupIdLookup* AccessControl::m_tidLookup; + +/// +/// Initializes the DMR access control. +/// +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Helper to validate a source radio ID. +/// +/// Source Radio ID. +/// True, if source radio ID is valid, otherwise false. +bool AccessControl::validateSrcId(uint32_t id) +{ + // check if RID ACLs are enabled + if (m_ridLookup->getACL() == false) { + return true; + } + + // lookup RID and perform test for validity + RadioId rid = m_ridLookup->find(id); + if (!rid.radioEnabled()) + return false; + + return true; +} + +/// +/// Helper to validate a talkgroup ID. +/// +/// DMR slot number. +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::validateTGId(uint32_t slotNo, uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (m_tidLookup->getACL() == false) { + return true; + } + + // lookup TID and perform test for validity + TalkgroupId tid = m_tidLookup->find(id); + if (!tid.tgEnabled()) + return false; + + if (tid.tgSlot() == 0) + return true; // TG Slot of 0 for the talkgroup entry means both + + if (tid.tgSlot() != slotNo) + return false; + + return true; +} diff --git a/dmr/acl/AccessControl.h b/dmr/acl/AccessControl.h new file mode 100644 index 00000000..27de586b --- /dev/null +++ b/dmr/acl/AccessControl.h @@ -0,0 +1,66 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +*/ +#if !defined(__DMR_ACL__ACCESS_CONTROL_H__) +#define __DMR_ACL__ACCESS_CONTROL_H__ + +#include "Defines.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +namespace dmr +{ + namespace acl + { + using namespace ::lookups; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements radio and talkgroup ID access control checking. + // --------------------------------------------------------------------------- + + class HOST_SW_API AccessControl { + public: + /// Initializes the DMR access control. + static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + + /// Helper to validate a source radio ID. + static bool validateSrcId(uint32_t id); + /// Helper to validate a talkgroup ID. + static bool validateTGId(uint32_t slotNo, uint32_t id); + + private: + static RadioIdLookup* m_ridLookup; + static TalkgroupIdLookup* m_tidLookup; + }; + } // namespace acl +} // namespace dmr + +#endif // __DMR_ACL__ACCESS_CONTROL_H__ diff --git a/dmr/data/Data.cpp b/dmr/data/Data.cpp new file mode 100644 index 00000000..18ceef01 --- /dev/null +++ b/dmr/data/Data.cpp @@ -0,0 +1,128 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/data/Data.h" +#include "Utils.h" + +using namespace dmr::data; +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Data class. +/// +/// +Data::Data(const Data& data) : + m_slotNo(data.m_slotNo), + m_srcId(data.m_srcId), + m_dstId(data.m_dstId), + m_flco(data.m_flco), + m_n(data.m_n), + m_seqNo(data.m_seqNo), + m_dataType(data.m_dataType), + m_ber(data.m_ber), + m_rssi(data.m_rssi), + m_data(NULL) +{ + m_data = new uint8_t[2U * DMR_FRAME_LENGTH_BYTES]; + ::memcpy(m_data, data.m_data, 2U * DMR_FRAME_LENGTH_BYTES); +} + +/// +/// Initializes a new instance of the Data class. +/// +Data::Data() : + m_slotNo(1U), + m_srcId(0U), + m_dstId(0U), + m_flco(FLCO_GROUP), + m_n(0U), + m_seqNo(0U), + m_dataType(0U), + m_ber(0U), + m_rssi(0U), + m_data(NULL) +{ + m_data = new uint8_t[2U * DMR_FRAME_LENGTH_BYTES]; +} + +/// +/// Finalizes a instance of the Data class. +/// +Data::~Data() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +Data& Data::operator=(const Data& data) +{ + if (this != &data) { + ::memcpy(m_data, data.m_data, DMR_FRAME_LENGTH_BYTES); + + m_slotNo = data.m_slotNo; + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + m_flco = data.m_flco; + m_dataType = data.m_dataType; + m_seqNo = data.m_seqNo; + m_n = data.m_n; + m_ber = data.m_ber; + m_rssi = data.m_rssi; + } + + return *this; +} + +/// +/// +void Data::setData(const uint8_t* buffer) +{ + assert(buffer != NULL); + + ::memcpy(m_data, buffer, DMR_FRAME_LENGTH_BYTES); +} + +/// +/// +uint32_t Data::getData(uint8_t* buffer) const +{ + assert(buffer != NULL); + + ::memcpy(buffer, m_data, DMR_FRAME_LENGTH_BYTES); + + return DMR_FRAME_LENGTH_BYTES; +} diff --git a/dmr/data/Data.h b/dmr/data/Data.h new file mode 100644 index 00000000..2bec603f --- /dev/null +++ b/dmr/data/Data.h @@ -0,0 +1,90 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#if !defined(__DMR_DATA__DATA_H__) +#define __DMR_DATA__DATA_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +namespace dmr +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents general DMR data. + // --------------------------------------------------------------------------- + + class HOST_SW_API Data { + public: + /// Initializes a new instance of the Data class. + Data(const Data& data); + /// Initializes a new instance of the Data class. + Data(); + /// Finalizes a instance of the Data class. + ~Data(); + + /// Equals operator. + Data& operator=(const Data& data); + + /// + void setData(const uint8_t* buffer); + /// + uint32_t getData(uint8_t* buffer) const; + + public: + /// DMR slot number. + __PROPERTY(uint32_t, slotNo, SlotNo); + + /// Source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + /// Sets the full-link control opcode. + __PROPERTY(uint8_t, flco, FLCO); + + /// + __PROPERTY(uint8_t, n, N); + + /// Sequence number. + __PROPERTY(uint8_t, seqNo, SeqNo); + + /// + __PROPERTY(uint8_t, dataType, DataType); + + /// + __PROPERTY(uint8_t, ber, BER); + + /// + __PROPERTY(uint8_t, rssi, RSSI); + + private: + uint8_t* m_data; + }; + } // namespace data +} // namespace dmr + +#endif // __DMR_DATA__DATA_H__ diff --git a/dmr/data/DataHeader.cpp b/dmr/data/DataHeader.cpp new file mode 100644 index 00000000..d8a28e70 --- /dev/null +++ b/dmr/data/DataHeader.cpp @@ -0,0 +1,199 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2012 by Ian Wraith +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/data/DataHeader.h" +#include "edac/BPTC19696.h" +#include "edac/RS129.h" +#include "edac/CRC.h" +#include "Utils.h" + +using namespace dmr::data; +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t UDTF_NMEA = 0x05U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DataHeader class. +/// +DataHeader::DataHeader() : + m_GI(false), + m_srcId(0U), + m_dstId(0U), + m_blocks(0U), + m_data(NULL), + m_A(false), + m_F(false), + m_S(false), + m_Ns(0U) +{ + m_data = new uint8_t[12U]; +} + +/// +/// Finalizes a instance of the DataHeader class. +/// +DataHeader::~DataHeader() +{ + delete[] m_data; +} + +/// +/// Equals operator. +/// +/// +/// +DataHeader& DataHeader::operator=(const DataHeader& header) +{ + if (&header != this) { + ::memcpy(m_data, header.m_data, 12U); + m_GI = header.m_GI; + m_A = header.m_A; + m_srcId = header.m_srcId; + m_dstId = header.m_dstId; + m_blocks = header.m_blocks; + m_F = header.m_F; + m_S = header.m_S; + m_Ns = header.m_Ns; + } + + return *this; +} + +/// +/// Decodes a DMR data header. +/// +/// +/// True, if DMR data header was decoded, otherwise false. +bool DataHeader::decode(const uint8_t* bytes) +{ + assert(bytes != NULL); + + // decode BPTC (196,96) FEC + edac::BPTC19696 bptc; + bptc.decode(bytes, m_data); + + // validate the CRC-CCITT 16 + m_data[10U] ^= DATA_HEADER_CRC_MASK[0U]; + m_data[11U] ^= DATA_HEADER_CRC_MASK[1U]; + + bool valid = edac::CRC::checkCCITT162(m_data, DMR_LC_HEADER_LENGTH_BYTES); + if (!valid) + return false; + + // restore the checksum + m_data[10U] ^= DATA_HEADER_CRC_MASK[0U]; + m_data[11U] ^= DATA_HEADER_CRC_MASK[1U]; + + m_GI = (m_data[0U] & 0x80U) == 0x80U; + m_A = (m_data[0U] & 0x40U) == 0x40U; + + uint8_t dpf = m_data[0U] & 0x0FU; + if (dpf == DPF_PROPRIETARY) + return true; + + m_dstId = m_data[2U] << 16 | m_data[3U] << 8 | m_data[4U]; + m_srcId = m_data[5U] << 16 | m_data[6U] << 8 | m_data[7U]; + + switch (dpf) { + case DPF_UNCONFIRMED_DATA: + Utils::dump(1U, "DMR, Unconfirmed Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_F = (m_data[8U] & 0x80U) == 0x80U; + m_blocks = m_data[8U] & 0x7FU; + break; + + case DPF_CONFIRMED_DATA: + Utils::dump(1U, "DMR, Confirmed Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_F = (m_data[8U] & 0x80U) == 0x80U; + m_blocks = m_data[8U] & 0x7FU; + m_S = (m_data[9U] & 0x80U) == 0x80U; + m_Ns = (m_data[9U] >> 4) & 0x07U; + break; + + case DPF_RESPONSE: + Utils::dump(1U, "DMR, Response Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_blocks = m_data[8U] & 0x7FU; + break; + + case DPF_PROPRIETARY: + Utils::dump(1U, "DMR, Proprietary Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case DPF_DEFINED_RAW: + Utils::dump(1U, "DMR, Raw or Status/Precoded Short Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_blocks = (m_data[0U] & 0x30U) + (m_data[1U] & 0x0FU); + m_F = (m_data[8U] & 0x01U) == 0x01U; + m_S = (m_data[8U] & 0x02U) == 0x02U; + break; + + case DPF_DEFINED_SHORT: + Utils::dump(1U, "DMR, Defined Short Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_blocks = (m_data[0U] & 0x30U) + (m_data[1U] & 0x0FU); + m_F = (m_data[8U] & 0x01U) == 0x01U; + m_S = (m_data[8U] & 0x02U) == 0x02U; + break; + + case DPF_UDT: + Utils::dump(1U, "DMR, Unified Data Transport Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + m_blocks = (m_data[8U] & 0x03U) + 1U; + break; + + default: + Utils::dump("DMR, Unknown Data Header", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + } + + return true; +} + +/// +/// Encodes a DMR data header. +/// +/// +void DataHeader::encode(uint8_t* bytes) const +{ + assert(bytes != NULL); + + // encode BPTC (196,96) FEC + edac::BPTC19696 bptc; + bptc.encode(m_data, bytes); +} diff --git a/dmr/data/DataHeader.h b/dmr/data/DataHeader.h new file mode 100644 index 00000000..8e445364 --- /dev/null +++ b/dmr/data/DataHeader.h @@ -0,0 +1,83 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DATA__DATA_HEADER_H__) +#define __DMR_DATA__DATA_HEADER_H__ + +#include "Defines.h" + +namespace dmr +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a DMR data header. + // --------------------------------------------------------------------------- + + class HOST_SW_API DataHeader { + public: + /// Initializes a new instance of the DataHeader class. + DataHeader(); + /// Finalizes a instance of the DataHeader class. + ~DataHeader(); + + /// Equals operator. + DataHeader& operator=(const DataHeader& header); + + /// Decodes a DMR data header. + bool decode(const uint8_t* bytes); + /// Encodes a DMR data header. + void encode(uint8_t* bytes) const; + + public: + /// Flag indicating whether the CSBK is group or individual. + __READONLY_PROPERTY(bool, GI, GI); + + /// Source ID. + __READONLY_PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __READONLY_PROPERTY(uint32_t, dstId, DstId); + + /// Gets the number of data blocks following the header. + __READONLY_PROPERTY(uint32_t, blocks, Blocks); + + private: + uint8_t* m_data; + + bool m_A; + + bool m_F; + bool m_S; + uint8_t m_Ns; + }; + } // namespace data +} // namespace dmr + +#endif // __DMR_DATA__DATA_HEADER_H__ diff --git a/dmr/data/EMB.cpp b/dmr/data/EMB.cpp new file mode 100644 index 00000000..b425e9fd --- /dev/null +++ b/dmr/data/EMB.cpp @@ -0,0 +1,105 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/data/EMB.h" +#include "edac/QR1676.h" + +using namespace dmr::data; +using namespace dmr; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the EMB class. +/// +EMB::EMB() : + m_colorCode(0U), + m_PI(false), + m_LCSS(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the EMB class. +/// +EMB::~EMB() +{ + /* stub */ +} + +/// +/// Decodes DMR embedded signalling data. +/// +/// +void EMB::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t DMREMB[2U]; + DMREMB[0U] = (data[13U] << 4) & 0xF0U; + DMREMB[0U] |= (data[14U] >> 4) & 0x0FU; + DMREMB[1U] = (data[18U] << 4) & 0xF0U; + DMREMB[1U] |= (data[19U] >> 4) & 0x0FU; + + // decode QR (16,7,6) FEC + edac::QR1676::decode(DMREMB); + + m_colorCode = (DMREMB[0U] >> 4) & 0x0FU; + m_PI = (DMREMB[0U] & 0x08U) == 0x08U; + m_LCSS = (DMREMB[0U] >> 1) & 0x03U; +} + +/// +/// Encodes DMR embedded signalling data. +/// +/// +void EMB::encode(uint8_t* data) const +{ + assert(data != NULL); + + uint8_t DMREMB[2U]; + DMREMB[0U] = (m_colorCode << 4) & 0xF0U; + DMREMB[0U] |= m_PI ? 0x08U : 0x00U; + DMREMB[0U] |= (m_LCSS << 1) & 0x06U; + DMREMB[1U] = 0x00U; + + // encode QR (16,7,6) FEC + edac::QR1676::encode(DMREMB); + + data[13U] = (data[13U] & 0xF0U) | ((DMREMB[0U] >> 4U) & 0x0FU); + data[14U] = (data[14U] & 0x0FU) | ((DMREMB[0U] << 4U) & 0xF0U); + data[18U] = (data[18U] & 0xF0U) | ((DMREMB[1U] >> 4U) & 0x0FU); + data[19U] = (data[19U] & 0x0FU) | ((DMREMB[1U] << 4U) & 0xF0U); +} diff --git a/dmr/data/EMB.h b/dmr/data/EMB.h new file mode 100644 index 00000000..46baf2ce --- /dev/null +++ b/dmr/data/EMB.h @@ -0,0 +1,69 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DATA__EMB_H__) +#define __DMR_DATA__EMB_H__ + +#include "Defines.h" + +namespace dmr +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a DMR embedded signalling data. + // --------------------------------------------------------------------------- + + class HOST_SW_API EMB { + public: + /// Initializes a new instance of the EMB class. + EMB(); + /// Finalizes a instance of the EMB class. + ~EMB(); + + /// Decodes DMR embedded signalling data. + void decode(const uint8_t* data); + /// Encodes DMR embedded signalling data. + void encode(uint8_t* data) const; + + public: + /// DMR access color code. + __PROPERTY(uint8_t, colorCode, ColorCode); + + /// Flag indicating whether the privacy indicator is set or not. + __PROPERTY(bool, PI, PI); + + /// Link control start/stop. + __PROPERTY(uint8_t, LCSS, LCSS); + }; + } // namespace data +} // namespace dmr + +#endif // __DMR_DATA__EMB_H__ diff --git a/dmr/data/EmbeddedData.cpp b/dmr/data/EmbeddedData.cpp new file mode 100644 index 00000000..93666961 --- /dev/null +++ b/dmr/data/EmbeddedData.cpp @@ -0,0 +1,367 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/data/EmbeddedData.h" +#include "edac/Hamming.h" +#include "edac/CRC.h" +#include "Utils.h" + +using namespace dmr::data; +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the EmbeddedData class. +/// +EmbeddedData::EmbeddedData() : + m_valid(false), + m_FLCO(FLCO_GROUP), + m_state(LCS_NONE), + m_data(NULL), + m_raw(NULL) +{ + m_raw = new bool[128U]; + m_data = new bool[72U]; +} + +/// +/// Finalizes a instance of the EmbeddedData class. +/// +EmbeddedData::~EmbeddedData() +{ + delete[] m_raw; + delete[] m_data; +} + +/// +/// Add LC data (which may consist of 4 blocks) to the data store. +/// +/// +/// +/// +bool EmbeddedData::addData(const uint8_t* data, uint8_t lcss) +{ + assert(data != NULL); + + bool rawData[40U]; + Utils::byteToBitsBE(data[14U], rawData + 0U); + Utils::byteToBitsBE(data[15U], rawData + 8U); + Utils::byteToBitsBE(data[16U], rawData + 16U); + Utils::byteToBitsBE(data[17U], rawData + 24U); + Utils::byteToBitsBE(data[18U], rawData + 32U); + + // Is this the first block of a 4 block embedded LC ? + if (lcss == 1U) { + for (uint32_t a = 0U; a < 32U; a++) + m_raw[a] = rawData[a + 4U]; + + // Show we are ready for the next LC block + m_state = LCS_FIRST; + m_valid = false; + + return false; + } + + // Is this the 2nd block of a 4 block embedded LC ? + if (lcss == 3U && m_state == LCS_FIRST) { + for (uint32_t a = 0U; a < 32U; a++) + m_raw[a + 32U] = rawData[a + 4U]; + + // Show we are ready for the next LC block + m_state = LCS_SECOND; + + return false; + } + + // Is this the 3rd block of a 4 block embedded LC ? + if (lcss == 3U && m_state == LCS_SECOND) { + for (uint32_t a = 0U; a < 32U; a++) + m_raw[a + 64U] = rawData[a + 4U]; + + // Show we are ready for the final LC block + m_state = LCS_THIRD; + + return false; + } + + // Is this the final block of a 4 block embedded LC ? + if (lcss == 2U && m_state == LCS_THIRD) { + for (uint32_t a = 0U; a < 32U; a++) + m_raw[a + 96U] = rawData[a + 4U]; + + // Show that we're not ready for any more data + m_state = LCS_NONE; + + // Process the complete data block + decodeEmbeddedData(); + if (m_valid) + encodeEmbeddedData(); + + return m_valid; + } + + return false; +} + +/// +/// +/// +/// +/// +/// +uint8_t EmbeddedData::getData(uint8_t* data, uint8_t n) const +{ + assert(data != NULL); + + if (n >= 1U && n < 5U) { + n--; + + bool bits[40U]; + ::memset(bits, 0x00U, 40U * sizeof(bool)); + ::memcpy(bits + 4U, m_raw + n * 32U, 32U * sizeof(bool)); + + uint8_t bytes[5U]; + Utils::bitsToByteBE(bits + 0U, bytes[0U]); + Utils::bitsToByteBE(bits + 8U, bytes[1U]); + Utils::bitsToByteBE(bits + 16U, bytes[2U]); + Utils::bitsToByteBE(bits + 24U, bytes[3U]); + Utils::bitsToByteBE(bits + 32U, bytes[4U]); + + data[14U] = (data[14U] & 0xF0U) | (bytes[0U] & 0x0FU); + data[15U] = bytes[1U]; + data[16U] = bytes[2U]; + data[17U] = bytes[3U]; + data[18U] = (data[18U] & 0x0FU) | (bytes[4U] & 0xF0U); + + switch (n) { + case 0U: + return 1U; + case 3U: + return 2U; + default: + return 3U; + } + } + else { + data[14U] &= 0xF0U; + data[15U] = 0x00U; + data[16U] = 0x00U; + data[17U] = 0x00U; + data[18U] &= 0x0FU; + + return 0U; + } +} + +/// Sets link control data. +/// +void EmbeddedData::setLC(const lc::LC& lc) +{ + lc.getData(m_data); + + m_FLCO = lc.getFLCO(); + m_valid = true; + + encodeEmbeddedData(); +} + +/// Gets link control data. +/// +lc::LC* EmbeddedData::getLC() const +{ + if (!m_valid) + return NULL; + + if (m_FLCO != FLCO_GROUP && m_FLCO != FLCO_PRIVATE) + return NULL; + + return new lc::LC(m_data); +} + +/// +/// +/// +/// +/// +bool EmbeddedData::getRawData(uint8_t* data) const +{ + assert(data != NULL); + + if (!m_valid) + return false; + + Utils::bitsToByteBE(m_data + 0U, data[0U]); + Utils::bitsToByteBE(m_data + 8U, data[1U]); + Utils::bitsToByteBE(m_data + 16U, data[2U]); + Utils::bitsToByteBE(m_data + 24U, data[3U]); + Utils::bitsToByteBE(m_data + 32U, data[4U]); + Utils::bitsToByteBE(m_data + 40U, data[5U]); + Utils::bitsToByteBE(m_data + 48U, data[6U]); + Utils::bitsToByteBE(m_data + 56U, data[7U]); + Utils::bitsToByteBE(m_data + 64U, data[8U]); + + return true; +} + +/// +/// Helper to reset data values to defaults. +/// +void EmbeddedData::reset() +{ + m_state = LCS_NONE; + m_valid = false; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Unpack and error check an embedded LC. +/// +void EmbeddedData::decodeEmbeddedData() +{ + // The data is unpacked downwards in columns + bool data[128U]; + ::memset(data, 0x00U, 128U * sizeof(bool)); + + uint32_t b = 0U; + for (uint32_t a = 0U; a < 128U; a++) { + data[b] = m_raw[a]; + b += 16U; + if (b > 127U) + b -= 127U; + } + + // Hamming (16,11,4) check each row except the last one + for (uint32_t a = 0U; a < 112U; a += 16U) { + if (!edac::Hamming::decode16114(data + a)) + return; + } + + // Check the parity bits + for (uint32_t a = 0U; a < 16U; a++) { + bool parity = data[a + 0U] ^ data[a + 16U] ^ data[a + 32U] ^ data[a + 48U] ^ data[a + 64U] ^ data[a + 80U] ^ data[a + 96U] ^ data[a + 112U]; + if (parity) + return; + } + + // We have passed the Hamming check so extract the actual payload + b = 0U; + for (uint32_t a = 0U; a < 11U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 16U; a < 27U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 32U; a < 42U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 48U; a < 58U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 64U; a < 74U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 80U; a < 90U; a++, b++) + m_data[b] = data[a]; + for (uint32_t a = 96U; a < 106U; a++, b++) + m_data[b] = data[a]; + + // Extract the 5 bit CRC + uint32_t crc = 0U; + if (data[42]) crc += 16U; + if (data[58]) crc += 8U; + if (data[74]) crc += 4U; + if (data[90]) crc += 2U; + if (data[106]) crc += 1U; + + // Now CRC check this + if (!edac::CRC::checkFiveBit(m_data, crc)) + return; + + m_valid = true; + + // Extract the FLCO + uint8_t flco; + Utils::bitsToByteBE(m_data + 0U, flco); + m_FLCO = flco & 0x3FU; +} + +/// +/// Pack and FEC for an embedded LC. +/// +void EmbeddedData::encodeEmbeddedData() +{ + uint32_t crc; + edac::CRC::encodeFiveBit(m_data, crc); + + bool data[128U]; + ::memset(data, 0x00U, 128U * sizeof(bool)); + + data[106U] = (crc & 0x01U) == 0x01U; + data[90U] = (crc & 0x02U) == 0x02U; + data[74U] = (crc & 0x04U) == 0x04U; + data[58U] = (crc & 0x08U) == 0x08U; + data[42U] = (crc & 0x10U) == 0x10U; + + uint32_t b = 0U; + for (uint32_t a = 0U; a < 11U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 16U; a < 27U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 32U; a < 42U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 48U; a < 58U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 64U; a < 74U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 80U; a < 90U; a++, b++) + data[a] = m_data[b]; + for (uint32_t a = 96U; a < 106U; a++, b++) + data[a] = m_data[b]; + + // Hamming (16,11,4) check each row except the last one + for (uint32_t a = 0U; a < 112U; a += 16U) + edac::Hamming::encode16114(data + a); + + // Add the parity bits for each column + for (uint32_t a = 0U; a < 16U; a++) + data[a + 112U] = data[a + 0U] ^ data[a + 16U] ^ data[a + 32U] ^ data[a + 48U] ^ data[a + 64U] ^ data[a + 80U] ^ data[a + 96U]; + + // The data is packed downwards in columns + b = 0U; + for (uint32_t a = 0U; a < 128U; a++) { + m_raw[a] = data[b]; + b += 16U; + if (b > 127U) + b -= 127U; + } +} diff --git a/dmr/data/EmbeddedData.h b/dmr/data/EmbeddedData.h new file mode 100644 index 00000000..2ce13bdd --- /dev/null +++ b/dmr/data/EmbeddedData.h @@ -0,0 +1,100 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_DATA__EMBEDDED_DATA_H__) +#define __DMR_DATA__EMBEDDED_DATA_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/lc/LC.h" + +namespace dmr +{ + namespace data + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum LC_STATE { + LCS_NONE, + LCS_FIRST, + LCS_SECOND, + LCS_THIRD + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a DMR embedded data. + // --------------------------------------------------------------------------- + + class HOST_SW_API EmbeddedData { + public: + /// Initializes a new instance of the EmbeddedData class. + EmbeddedData(); + /// Finalizes a instance of the EmbeddedData class. + ~EmbeddedData(); + + /// Add LC data (which may consist of 4 blocks) to the data store. + bool addData(const uint8_t* data, uint8_t lcss); + /// + uint8_t getData(uint8_t* data, uint8_t n) const; + + /// Sets link control data. + void setLC(const lc::LC& lc); + /// Gets link control data. + lc::LC* getLC() const; + + /// + bool getRawData(uint8_t* data) const; + + /// Helper to reset data values to defaults. + void reset(); + + public: + /// Flag indicating whether or not the embedded data is valid. + __READONLY_PROPERTY_PLAIN(bool, valid, isValid); + /// Full-link control opcode. + __READONLY_PROPERTY(uint8_t, FLCO, FLCO); + + private: + LC_STATE m_state; + bool* m_data; + + bool* m_raw; + + /// Unpack and error check an embedded LC. + void decodeEmbeddedData(); + /// Pack and FEC for an embedded LC. + void encodeEmbeddedData(); + }; + } // namespace data +} // namespace dmr + +#endif // __DMR_DATA__EMBEDDED_DATA_H__ diff --git a/dmr/edac/Trellis.cpp b/dmr/edac/Trellis.cpp new file mode 100644 index 00000000..c1631590 --- /dev/null +++ b/dmr/edac/Trellis.cpp @@ -0,0 +1,453 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "dmr/edac/Trellis.h" + +using namespace dmr::edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t INTERLEAVE_TABLE[] = { + 0U, 1U, 8U, 9U, 16U, 17U, 24U, 25U, 32U, 33U, 40U, 41U, 48U, 49U, 56U, 57U, 64U, 65U, 72U, 73U, 80U, 81U, 88U, 89U, 96U, 97U, + 2U, 3U, 10U, 11U, 18U, 19U, 26U, 27U, 34U, 35U, 42U, 43U, 50U, 51U, 58U, 59U, 66U, 67U, 74U, 75U, 82U, 83U, 90U, 91U, + 4U, 5U, 12U, 13U, 20U, 21U, 28U, 29U, 36U, 37U, 44U, 45U, 52U, 53U, 60U, 61U, 68U, 69U, 76U, 77U, 84U, 85U, 92U, 93U, + 6U, 7U, 14U, 15U, 22U, 23U, 30U, 31U, 38U, 39U, 46U, 47U, 54U, 55U, 62U, 63U, 70U, 71U, 78U, 79U, 86U, 87U, 94U, 95U }; + +const uint8_t ENCODE_TABLE[] = { + 0U, 8U, 4U, 12U, 2U, 10U, 6U, 14U, + 4U, 12U, 2U, 10U, 6U, 14U, 0U, 8U, + 1U, 9U, 5U, 13U, 3U, 11U, 7U, 15U, + 5U, 13U, 3U, 11U, 7U, 15U, 1U, 9U, + 3U, 11U, 7U, 15U, 1U, 9U, 5U, 13U, + 7U, 15U, 1U, 9U, 5U, 13U, 3U, 11U, + 2U, 10U, 6U, 14U, 0U, 8U, 4U, 12U, + 6U, 14U, 0U, 8U, 4U, 12U, 2U, 10U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Trellis class. +/// +Trellis::Trellis() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Trellis class. +/// +Trellis::~Trellis() +{ + /* stub */ +} + +/// +/// Decodes 3/4 rate Trellis. +/// +/// +/// +/// +bool Trellis::decode(const uint8_t* data, uint8_t* payload) +{ + assert(data != NULL); + assert(payload != NULL); + + int8_t dibits[98U]; + deinterleave(data, dibits); + + uint8_t points[49U]; + dibitsToPoints(dibits, points); + + // Check the original code + uint8_t tribits[49U]; + uint32_t failPos = checkCode(points, tribits); + if (failPos == 999U) { + tribitsToBits(tribits, payload); + return true; + } + + uint8_t savePoints[49U]; + for (uint32_t i = 0U; i < 49U; i++) + savePoints[i] = points[i]; + + bool ret = fixCode(points, failPos, payload); + if (ret) + return true; + + if (failPos == 0U) + return false; + + // Backtrack one place for a last go + return fixCode(savePoints, failPos - 1U, payload); +} + +/// +/// Encodes 3/4 rate Trellis. +/// +/// +/// +void Trellis::encode(const uint8_t* payload, uint8_t* data) +{ + assert(payload != NULL); + assert(data != NULL); + + uint8_t tribits[49U]; + bitsToTribits(payload, tribits); + + uint8_t points[49U]; + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + uint8_t tribit = tribits[i]; + + points[i] = ENCODE_TABLE[state * 8U + tribit]; + + state = tribit; + } + + int8_t dibits[98U]; + pointsToDibits(points, dibits); + + interleave(dibits, data); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +void Trellis::deinterleave(const uint8_t* data, int8_t* dibits) const +{ + for (uint32_t i = 0U; i < 98U; i++) { + uint32_t n = i * 2U + 0U; + if (n >= 98U) n += 68U; + bool b1 = READ_BIT(data, n) != 0x00U; + + n = i * 2U + 1U; + if (n >= 98U) n += 68U; + bool b2 = READ_BIT(data, n) != 0x00U; + + int8_t dibit; + if (!b1 && b2) + dibit = +3; + else if (!b1 && !b2) + dibit = +1; + else if (b1 && !b2) + dibit = -1; + else + dibit = -3; + + n = INTERLEAVE_TABLE[i]; + dibits[n] = dibit; + } +} + +/// +/// +/// +/// +/// +void Trellis::interleave(const int8_t* dibits, uint8_t* data) const +{ + for (uint32_t i = 0U; i < 98U; i++) { + uint32_t n = INTERLEAVE_TABLE[i]; + + bool b1, b2; + switch (dibits[n]) { + case +3: + b1 = false; + b2 = true; + break; + case +1: + b1 = false; + b2 = false; + break; + case -1: + b1 = true; + b2 = false; + break; + default: + b1 = true; + b2 = true; + break; + } + + n = i * 2U + 0U; + if (n >= 98U) n += 68U; + WRITE_BIT(data, n, b1); + + n = i * 2U + 1U; + if (n >= 98U) n += 68U; + WRITE_BIT(data, n, b2); + } +} + +/// +/// +/// +/// +/// +void Trellis::dibitsToPoints(const int8_t* dibits, uint8_t* points) const +{ + for (uint32_t i = 0U; i < 49U; i++) { + if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -1) + points[i] = 0U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -1) + points[i] = 1U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -3) + points[i] = 2U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -3) + points[i] = 3U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -1) + points[i] = 4U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -1) + points[i] = 5U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -3) + points[i] = 6U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -3) + points[i] = 7U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +3) + points[i] = 8U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +3) + points[i] = 9U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +1) + points[i] = 10U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +1) + points[i] = 11U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +3) + points[i] = 12U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +3) + points[i] = 13U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +1) + points[i] = 14U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +1) + points[i] = 15U; + } +} + +/// +/// +/// +/// +/// +void Trellis::pointsToDibits(const uint8_t* points, int8_t* dibits) const +{ + for (uint32_t i = 0U; i < 49U; i++) { + switch (points[i]) { + case 0U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -1; + break; + case 1U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -1; + break; + case 2U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -3; + break; + case 3U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -3; + break; + case 4U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -1; + break; + case 5U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -1; + break; + case 6U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -3; + break; + case 7U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -3; + break; + case 8U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +3; + break; + case 9U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +3; + break; + case 10U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +1; + break; + case 11U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +1; + break; + case 12U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +3; + break; + case 13U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +3; + break; + case 14U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +1; + break; + default: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +1; + break; + } + } +} + +/// +/// +/// +/// +/// +void Trellis::bitsToTribits(const uint8_t* payload, uint8_t* tribits) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint32_t n = 143U - i * 3U; + + bool b1 = READ_BIT(payload, n) != 0x00U; + n--; + bool b2 = READ_BIT(payload, n) != 0x00U; + n--; + bool b3 = READ_BIT(payload, n) != 0x00U; + + uint8_t tribit = 0U; + tribit |= b1 ? 4U : 0U; + tribit |= b2 ? 2U : 0U; + tribit |= b3 ? 1U : 0U; + + tribits[i] = tribit; + } + + tribits[48U] = 0U; +} + +/// +/// +/// +/// +/// +void Trellis::tribitsToBits(const uint8_t* tribits, uint8_t* payload) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint8_t tribit = tribits[i]; + + bool b1 = (tribit & 0x04U) == 0x04U; + bool b2 = (tribit & 0x02U) == 0x02U; + bool b3 = (tribit & 0x01U) == 0x01U; + + uint32_t n = 143U - i * 3U; + + WRITE_BIT(payload, n, b1); + n--; + WRITE_BIT(payload, n, b2); + n--; + WRITE_BIT(payload, n, b3); + } +} + +/// +/// +/// +/// +/// +/// +/// +bool Trellis::fixCode(uint8_t* points, uint32_t failPos, uint8_t* payload) const +{ + for (unsigned j = 0U; j < 20U; j++) { + uint32_t bestPos = 0U; + uint32_t bestVal = 0U; + + for (uint32_t i = 0U; i < 16U; i++) { + points[failPos] = i; + + uint8_t tribits[49U]; + uint32_t pos = checkCode(points, tribits); + if (pos == 999U) { + tribitsToBits(tribits, payload); + return true; + } + + if (pos > bestPos) { + bestPos = pos; + bestVal = i; + } + } + + points[failPos] = bestVal; + failPos = bestPos; + } + + return false; +} + +/// +/// +/// +/// +/// +/// +uint32_t Trellis::checkCode(const uint8_t* points, uint8_t* tribits) const +{ + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + tribits[i] = 9U; + + for (uint32_t j = 0U; j < 8U; j++) { + if (points[i] == ENCODE_TABLE[state * 8U + j]) { + tribits[i] = j; + break; + } + } + + if (tribits[i] == 9U) + return i; + + state = tribits[i]; + } + + if (tribits[48U] != 0U) + return 48U; + + return 999U; +} diff --git a/dmr/edac/Trellis.h b/dmr/edac/Trellis.h new file mode 100644 index 00000000..528bdb21 --- /dev/null +++ b/dmr/edac/Trellis.h @@ -0,0 +1,73 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#if !defined(__DMR_EDAC__TRELLIS_H__) +#define __DMR_EDAC__TRELLIS_H__ + +#include "Defines.h" + +namespace dmr +{ + namespace edac + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements 3/4 rate Trellis for DMR. + // --------------------------------------------------------------------------- + + class HOST_SW_API Trellis { + public: + /// Initializes a new instance of the Trellis class. + Trellis(); + /// Finalizes a instance of the Trellis class. + ~Trellis(); + + /// Decodes 3/4 rate Trellis. + bool decode(const uint8_t* data, uint8_t* payload); + /// Encodes 3/4 rate Trellis. + void encode(const uint8_t* payload, uint8_t* data); + + private: + /// + void deinterleave(const uint8_t* in, int8_t* dibits) const; + /// + void interleave(const int8_t* dibits, uint8_t* out) const; + /// + void dibitsToPoints(const int8_t* dibits, uint8_t* points) const; + /// + void pointsToDibits(const uint8_t* points, int8_t* dibits) const; + /// + void bitsToTribits(const uint8_t* payload, uint8_t* tribits) const; + /// + void tribitsToBits(const uint8_t* tribits, uint8_t* payload) const; + + /// + bool fixCode(uint8_t* points, uint32_t failPos, uint8_t* payload) const; + /// + uint32_t checkCode(const uint8_t* points, uint8_t* tribits) const; + }; + } // namespace edac +} // namespace dmr + +#endif // __DMR_EDAC__TRELLIS_H__ diff --git a/dmr/lc/CSBK.cpp b/dmr/lc/CSBK.cpp new file mode 100644 index 00000000..ec436a17 --- /dev/null +++ b/dmr/lc/CSBK.cpp @@ -0,0 +1,247 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/lc/CSBK.h" +#include "edac/BPTC19696.h" +#include "edac/CRC.h" +#include "Utils.h" + +using namespace dmr::lc; +using namespace dmr; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the CSBK class. +/// +/// +CSBK::CSBK(bool debug) : + m_CSBKO(CSBKO_NONE), + m_FID(0x00U), + m_bsId(0U), + m_GI(false), + m_srcId(0U), + m_dstId(0U), + m_dataContent(false), + m_CBF(0U), + m_data(NULL), + m_debug(debug) +{ + m_data = new uint8_t[12U]; +} + +/// +/// Finalizes a instance of the CSBK class. +/// +CSBK::~CSBK() +{ + delete[] m_data; +} + +/// +/// Decodes a DMR CSBK. +/// +/// +/// True, if DMR CSBK was decoded, otherwise false. +bool CSBK::decode(const uint8_t* bytes) +{ + assert(bytes != NULL); + + // decode BPTC (196,96) FEC + edac::BPTC19696 bptc; + bptc.decode(bytes, m_data); + + // validate the CRC-CCITT 16 + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + + bool valid = edac::CRC::checkCCITT162(m_data, DMR_LC_HEADER_LENGTH_BYTES); + if (!valid) + return false; + + // restore the checksum + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + + m_CSBKO = m_data[0U] & 0x3FU; + m_FID = m_data[1U]; + + switch (m_CSBKO) { + case CSBKO_BSDWNACT: + m_GI = false; + m_bsId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = 0U; + if (m_debug) + Utils::dump(1U, "Downlink Activate CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_UU_V_REQ: + m_GI = false; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = 0U; + if (m_debug) + Utils::dump(1U, "Unit to Unit Service Request CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_UU_ANS_RSP: + m_GI = false; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = 0U; + if (m_debug) + Utils::dump(1U, "Unit to Unit Service Answer Response CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_PRECCSBK: + m_GI = (m_data[2U] & 0x40U) == 0x40U; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = (m_data[2U] & 0x80U) == 0x80U; + m_CBF = m_data[3U]; + if (m_debug) + Utils::dump(1U, "Preamble CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_CALL_ALRT: + m_GI = (m_data[2U] & 0x40U) == 0x40U; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = (m_data[2U] & 0x80U) == 0x80U; + m_CBF = m_data[3U]; + if (m_debug) + Utils::dump(1U, "Call Alert CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_ACK_RSP: + m_GI = (m_data[2U] & 0x40U) == 0x40U; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = (m_data[2U] & 0x80U) == 0x80U; + m_CBF = m_data[3U]; + if (m_debug) + Utils::dump(1U, "ACK Response CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_EXT_FNCT: + m_GI = false; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = (m_data[2U] & 0x80U) == 0x80U; + m_CBF = m_data[3U]; + if (m_debug) + Utils::dump(1U, "Extended Function CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + case CSBKO_NACK_RSP: + m_GI = false; + m_srcId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_dstId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = 0U; + if (m_debug) + Utils::dump(1U, "Negative Acknowledge Response CSBK", m_data, DMR_LC_HEADER_LENGTH_BYTES); + break; + + default: + m_GI = false; + m_srcId = 0U; + m_dstId = 0U; + m_dataContent = false; + m_CBF = 0U; + if (m_debug) + Utils::dump("Unhandled CSBK type", m_data, DMR_LC_HEADER_LENGTH_BYTES); + return true; + } + + return true; +} + +/// +/// Encodes a DMR CSBK. +/// +/// +void CSBK::encode(uint8_t* bytes) const +{ + assert(bytes != NULL); + + m_data[0U] = m_CSBKO; + m_data[1U] = m_FID; + + if (m_GI) { + m_data[2U] |= 0x40U; + } + + if (m_dataContent) { + m_data[2U] |= 0x80U; + } + + m_data[3U] = m_CBF; + + if (m_CSBKO == CSBKO_EXT_FNCT) { + m_data[4U] = (m_srcId >> 16) & 0xFFU; + m_data[5U] = (m_srcId >> 8) & 0xFFU; + m_data[6U] = (m_srcId >> 0) & 0xFFU; + + m_data[7U] = (m_dstId >> 16) & 0xFFU; + m_data[8U] = (m_dstId >> 8) & 0xFFU; + m_data[9U] = (m_dstId >> 0) & 0xFFU; + } + else { + m_data[4U] = (m_dstId >> 16) & 0xFFU; + m_data[5U] = (m_dstId >> 8) & 0xFFU; + m_data[6U] = (m_dstId >> 0) & 0xFFU; + + m_data[7U] = (m_srcId >> 16) & 0xFFU; + m_data[8U] = (m_srcId >> 8) & 0xFFU; + m_data[9U] = (m_srcId >> 0) & 0xFFU; + } + + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + + edac::CRC::addCCITT162(m_data, 12U); + + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + + // encode BPTC (196,96) FEC + edac::BPTC19696 bptc; + bptc.encode(m_data, bytes); +} diff --git a/dmr/lc/CSBK.h b/dmr/lc/CSBK.h new file mode 100644 index 00000000..b9fcfb26 --- /dev/null +++ b/dmr/lc/CSBK.h @@ -0,0 +1,90 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_LC__CSBK_H__) +#define __DMR_LC__CSBK_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +namespace dmr +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents DMR control signalling block data. + // --------------------------------------------------------------------------- + + class HOST_SW_API CSBK { + public: + /// Initializes a new instance of the CSBK class. + CSBK(bool debug); + /// Finalizes a instance of the CSBK class. + ~CSBK(); + + /// Decodes a DMR CSBK. + bool decode(const uint8_t* bytes); + /// Encodes a DMR CSBK. + void encode(uint8_t* bytes) const; + + public: + // Generic fields + /// CSBK opcode. + __PROPERTY(uint8_t, CSBKO, CSBKO); + /// CSBK feature ID. + __PROPERTY(uint8_t, FID, FID); + + // For BS Dwn Act + __READONLY_PROPERTY(uint32_t, bsId, BSId); + + // For Pre + /// Flag indicating whether the CSBK is group or individual. + __PROPERTY(bool, GI, GI); + + /// Source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + /// + __READONLY_PROPERTY(bool, dataContent, DataContent); + + /// Sets the number of blocks to follow. + __PROPERTY(uint8_t, CBF, CBF); + + private: + uint8_t* m_data; + bool m_debug; + }; + } // namespace lc +} // namespace dmr + +#endif // __DMR_LC__CSBK_H__ diff --git a/dmr/lc/FullLC.cpp b/dmr/lc/FullLC.cpp new file mode 100644 index 00000000..255d08e0 --- /dev/null +++ b/dmr/lc/FullLC.cpp @@ -0,0 +1,140 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2012 by Ian Wraith +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "dmr/lc/FullLC.h" +#include "edac/RS129.h" +#include "Log.h" +#include "Utils.h" + +using namespace dmr::lc; +using namespace dmr; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initialize a new instance of the FullLC class. +/// +FullLC::FullLC() : + m_bptc() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the FullLC class. +/// +FullLC::~FullLC() +{ + /* stub */ +} + +/// +/// Decode DMR full-link control data. +/// +/// +/// +/// +LC* FullLC::decode(const uint8_t* data, uint8_t type) +{ + assert(data != NULL); + + // decode BPTC (196,96) FEC + uint8_t lcData[DMR_LC_HEADER_LENGTH_BYTES]; + m_bptc.decode(data, lcData); + + switch (type) { + case DT_VOICE_LC_HEADER: + lcData[9U] ^= VOICE_LC_HEADER_CRC_MASK[0U]; + lcData[10U] ^= VOICE_LC_HEADER_CRC_MASK[1U]; + lcData[11U] ^= VOICE_LC_HEADER_CRC_MASK[2U]; + break; + + case DT_TERMINATOR_WITH_LC: + lcData[9U] ^= TERMINATOR_WITH_LC_CRC_MASK[0U]; + lcData[10U] ^= TERMINATOR_WITH_LC_CRC_MASK[1U]; + lcData[11U] ^= TERMINATOR_WITH_LC_CRC_MASK[2U]; + break; + + default: + LogError(LOG_DMR, "Unsupported LC type, type = %d", int(type)); + return NULL; + } + + // check RS (12,9) FEC + if (!edac::RS129::check(lcData)) + return NULL; + + return new LC(lcData); +} + +/// +/// Encode DMR full-link control data. +/// +/// +/// +/// +void FullLC::encode(const LC& lc, uint8_t* data, uint8_t type) +{ + assert(data != NULL); + + uint8_t lcData[DMR_LC_HEADER_LENGTH_BYTES]; + lc.getData(lcData); + + // encode RS (12,9) FEC + uint8_t parity[4U]; + edac::RS129::encode(lcData, 9U, parity); + + switch (type) { + case DT_VOICE_LC_HEADER: + lcData[9U] = parity[2U] ^ VOICE_LC_HEADER_CRC_MASK[0U]; + lcData[10U] = parity[1U] ^ VOICE_LC_HEADER_CRC_MASK[1U]; + lcData[11U] = parity[0U] ^ VOICE_LC_HEADER_CRC_MASK[2U]; + break; + + case DT_TERMINATOR_WITH_LC: + lcData[9U] = parity[2U] ^ TERMINATOR_WITH_LC_CRC_MASK[0U]; + lcData[10U] = parity[1U] ^ TERMINATOR_WITH_LC_CRC_MASK[1U]; + lcData[11U] = parity[0U] ^ TERMINATOR_WITH_LC_CRC_MASK[2U]; + break; + + default: + LogError(LOG_DMR, "Unsupported LC type, type = %d", int(type)); + return; + } + + // encode BPTC (196,96) FEC + m_bptc.encode(lcData, data); +} diff --git a/dmr/lc/FullLC.h b/dmr/lc/FullLC.h new file mode 100644 index 00000000..cb645480 --- /dev/null +++ b/dmr/lc/FullLC.h @@ -0,0 +1,65 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_LC__FULL_LC_H__) +#define __DMR_LC__FULL_LC_H__ + +#include "Defines.h" +#include "dmr/lc/LC.h" +#include "dmr/SlotType.h" +#include "edac/BPTC19696.h" + +namespace dmr +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents full DMR link control. + // --------------------------------------------------------------------------- + + class HOST_SW_API FullLC { + public: + /// Initializes a new instance of the FullLC class. + FullLC(); + /// Finalizes a instance of the FullLC class. + ~FullLC(); + + /// Decode DMR full-link control data. + LC* decode(const uint8_t* data, uint8_t type); + /// Encode DMR full-link control data. + void encode(const LC& lc, uint8_t* data, uint8_t type); + + private: + edac::BPTC19696 m_bptc; + }; + } // namespace lc +} // namespace dmr + +#endif // __DMR_LC__FULL_LC_H__ diff --git a/dmr/lc/LC.cpp b/dmr/lc/LC.cpp new file mode 100644 index 00000000..4fa28a34 --- /dev/null +++ b/dmr/lc/LC.cpp @@ -0,0 +1,221 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/lc/LC.h" +#include "Utils.h" + +using namespace dmr::lc; +using namespace dmr; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the LC class. +/// +/// Full-link Control Opcode. +/// Source ID. +/// Destination ID. +LC::LC(uint8_t flco, uint32_t srcId, uint32_t dstId) : + m_PF(false), + m_FLCO(flco), + m_FID(0U), + m_srcId(srcId), + m_dstId(dstId), + m_R(false), + m_options(0U) +{ + /* stub */ +} +/// +/// Initializes a new instance of the LC class. +/// +/// +LC::LC(const uint8_t* bytes) : + m_PF(false), + m_FLCO(FLCO_GROUP), + m_FID(0U), + m_srcId(0U), + m_dstId(0U), + m_R(false), + m_options(0U) +{ + assert(bytes != NULL); + + m_PF = (bytes[0U] & 0x80U) == 0x80U; + m_R = (bytes[0U] & 0x40U) == 0x40U; + + m_FLCO = bytes[0U] & 0x3FU; + + m_FID = bytes[1U]; + + m_options = bytes[2U]; + + m_dstId = bytes[3U] << 16 | bytes[4U] << 8 | bytes[5U]; + m_srcId = bytes[6U] << 16 | bytes[7U] << 8 | bytes[8U]; +} +/// +/// Initializes a new instance of the LC class. +/// +/// +LC::LC(const bool* bits) : + m_PF(false), + m_FLCO(FLCO_GROUP), + m_FID(0U), + m_srcId(0U), + m_dstId(0U), + m_R(false), + m_options(0U) +{ + assert(bits != NULL); + + m_PF = bits[0U]; + m_R = bits[1U]; + + uint8_t temp1, temp2, temp3; + Utils::bitsToByteBE(bits + 0U, temp1); + m_FLCO = temp1 & 0x3FU; + + Utils::bitsToByteBE(bits + 8U, temp2); + m_FID = temp2; + + Utils::bitsToByteBE(bits + 16U, temp3); + m_options = temp3; + + uint8_t d1, d2, d3; + Utils::bitsToByteBE(bits + 24U, d1); + Utils::bitsToByteBE(bits + 32U, d2); + Utils::bitsToByteBE(bits + 40U, d3); + + uint8_t s1, s2, s3; + Utils::bitsToByteBE(bits + 48U, s1); + Utils::bitsToByteBE(bits + 56U, s2); + Utils::bitsToByteBE(bits + 64U, s3); + + m_srcId = s1 << 16 | s2 << 8 | s3; + m_dstId = d1 << 16 | d2 << 8 | d3; +} +/// +/// Initializes a new instance of the LC class. +/// +LC::LC() : + m_PF(false), + m_FLCO(FLCO_GROUP), + m_FID(0U), + m_srcId(0U), + m_dstId(0U), + m_R(false), + m_options(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the LC class. +/// +LC::~LC() +{ + /* stub */ +} + +/// +/// +/// +/// +void LC::getData(uint8_t* bytes) const +{ + assert(bytes != NULL); + + bytes[0U] = (uint8_t)m_FLCO; + + if (m_PF) + bytes[0U] |= 0x80U; + + if (m_R) + bytes[0U] |= 0x40U; + + bytes[1U] = m_FID; + + bytes[2U] = m_options; + + bytes[3U] = m_dstId >> 16; + bytes[4U] = m_dstId >> 8; + bytes[5U] = m_dstId >> 0; + + bytes[6U] = m_srcId >> 16; + bytes[7U] = m_srcId >> 8; + bytes[8U] = m_srcId >> 0; +} + +/// +/// +/// +/// +void LC::getData(bool* bits) const +{ + assert(bits != NULL); + + uint8_t bytes[9U]; + getData(bytes); + + Utils::byteToBitsBE(bytes[0U], bits + 0U); + Utils::byteToBitsBE(bytes[1U], bits + 8U); + Utils::byteToBitsBE(bytes[2U], bits + 16U); + Utils::byteToBitsBE(bytes[3U], bits + 24U); + Utils::byteToBitsBE(bytes[4U], bits + 32U); + Utils::byteToBitsBE(bytes[5U], bits + 40U); + Utils::byteToBitsBE(bytes[6U], bits + 48U); + Utils::byteToBitsBE(bytes[7U], bits + 56U); + Utils::byteToBitsBE(bytes[8U], bits + 64U); +} + +/// +/// +/// +/// +bool LC::getOVCM() const +{ + return (m_options & 0x04U) == 0x04U; +} + +/// +/// +/// +/// +void LC::setOVCM(bool ovcm) +{ + if (ovcm) + m_options |= 0x04U; + else + m_options &= 0xFBU; +} diff --git a/dmr/lc/LC.h b/dmr/lc/LC.h new file mode 100644 index 00000000..2dcbe5b6 --- /dev/null +++ b/dmr/lc/LC.h @@ -0,0 +1,90 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_LC__LC_H__) +#define __DMR_LC__LC_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" + +namespace dmr +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents DMR link control data. + // --------------------------------------------------------------------------- + + class HOST_SW_API LC { + public: + /// Initializes a new instance of the LC class. + LC(uint8_t flco, uint32_t srcId, uint32_t dstId); + /// Initializes a new instance of the LC class. + LC(const uint8_t* bytes); + /// Initializes a new instance of the LC class. + LC(const bool* bits); + /// Initializes a new instance of the LC class. + LC(); + /// Finalizes a instance of the LC class. + ~LC(); + + /// + void getData(uint8_t* bytes) const; + /// + void getData(bool* bits) const; + + /// + bool getOVCM() const; + /// + void setOVCM(bool ovcm); + public: + /// Flag indicating whether link protection is enabled. + __PROPERTY(bool, PF, PF); + + /// Full-link control opcode. + __PROPERTY(uint8_t, FLCO, FLCO); + + /// CSBK feature ID. + __PROPERTY(uint8_t, FID, FID); + + /// Sets the source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Sets the destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + private: + bool m_R; + + uint8_t m_options; + }; + } // namespace lc +} // namespace dmr + +#endif // __DMR_LC__LC_H__ diff --git a/dmr/lc/ShortLC.cpp b/dmr/lc/ShortLC.cpp new file mode 100644 index 00000000..a4cc9d10 --- /dev/null +++ b/dmr/lc/ShortLC.cpp @@ -0,0 +1,287 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/lc/ShortLC.h" +#include "edac/Hamming.h" +#include "Utils.h" + +using namespace dmr::lc; +using namespace dmr; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the ShortLC class. +/// +ShortLC::ShortLC() : + m_rawData(NULL), + m_deInterData(NULL) +{ + m_rawData = new bool[72U]; + m_deInterData = new bool[68U]; +} + +/// +/// Finalizes a instance of the ShortLC class. +/// +ShortLC::~ShortLC() +{ + delete[] m_rawData; + delete[] m_deInterData; +} + +/// +/// Decode DMR short-link control data. +/// +/// +/// +/// +bool ShortLC::decode(const uint8_t* in, uint8_t* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Get the raw binary + decodeExtractBinary(in); + + // Deinterleave + decodeDeInterleave(); + + // Error check + bool ret = decodeErrorCheck(); + if (!ret) + return false; + + // Extract Data + decodeExtractData(out); + + return true; +} + +/// +/// Encode DMR short-link control data. +/// +/// +/// +void ShortLC::encode(const uint8_t* in, uint8_t* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Extract Data + encodeExtractData(in); + + // Error check + encodeErrorCheck(); + + // Deinterleave + encodeInterleave(); + + // Get the raw binary + encodeExtractBinary(out); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +void ShortLC::decodeExtractBinary(const uint8_t* in) +{ + assert(in != NULL); + + Utils::byteToBitsBE(in[0U], m_rawData + 0U); + Utils::byteToBitsBE(in[1U], m_rawData + 8U); + Utils::byteToBitsBE(in[2U], m_rawData + 16U); + Utils::byteToBitsBE(in[3U], m_rawData + 24U); + Utils::byteToBitsBE(in[4U], m_rawData + 32U); + Utils::byteToBitsBE(in[5U], m_rawData + 40U); + Utils::byteToBitsBE(in[6U], m_rawData + 48U); + Utils::byteToBitsBE(in[7U], m_rawData + 56U); + Utils::byteToBitsBE(in[8U], m_rawData + 64U); +} + +/// +/// +/// +void ShortLC::decodeDeInterleave() +{ + for (uint32_t i = 0U; i < 68U; i++) + m_deInterData[i] = false; + + for (uint32_t a = 0U; a < 67U; a++) { + // Calculate the interleave sequence + uint32_t interleaveSequence = (a * 4U) % 67U; + // Shuffle the data + m_deInterData[a] = m_rawData[interleaveSequence]; + } + + m_deInterData[67U] = m_rawData[67U]; +} + +/// +/// +/// +/// +bool ShortLC::decodeErrorCheck() +{ + // Run through each of the 3 rows containing data + edac::Hamming::decode17123(m_deInterData + 0U); + edac::Hamming::decode17123(m_deInterData + 17U); + edac::Hamming::decode17123(m_deInterData + 34U); + + // Run through each of the 17 columns + for (uint32_t c = 0U; c < 17U; c++) { + bool bit = m_deInterData[c + 0U] ^ m_deInterData[c + 17U] ^ m_deInterData[c + 34U]; + if (bit != m_deInterData[c + 51U]) + return false; + } + + return true; +} + +/// +/// +/// +/// +void ShortLC::decodeExtractData(uint8_t* data) const +{ + assert(data != NULL); + + bool bData[40U]; + + for (uint32_t i = 0U; i < 40U; i++) + bData[i] = false; + + uint32_t pos = 4U; + for (uint32_t a = 0U; a < 12U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 17U; a < 29U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 34U; a < 46U; a++, pos++) + bData[pos] = m_deInterData[a]; + + Utils::bitsToByteBE(bData + 0U, data[0U]); + Utils::bitsToByteBE(bData + 8U, data[1U]); + Utils::bitsToByteBE(bData + 16U, data[2U]); + Utils::bitsToByteBE(bData + 24U, data[3U]); + Utils::bitsToByteBE(bData + 32U, data[4U]); +} + +/// +/// +/// +/// +void ShortLC::encodeExtractData(const uint8_t* in) const +{ + assert(in != NULL); + + bool bData[40U]; + Utils::byteToBitsBE(in[0U], bData + 0U); + Utils::byteToBitsBE(in[1U], bData + 8U); + Utils::byteToBitsBE(in[2U], bData + 16U); + Utils::byteToBitsBE(in[3U], bData + 24U); + Utils::byteToBitsBE(in[4U], bData + 32U); + + for (uint32_t i = 0U; i < 68U; i++) + m_deInterData[i] = false; + + uint32_t pos = 4U; + for (uint32_t a = 0U; a < 12U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 17U; a < 29U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 34U; a < 46U; a++, pos++) + m_deInterData[a] = bData[pos]; +} + +/// +/// +/// +void ShortLC::encodeErrorCheck() +{ + // Run through each of the 3 rows containing data + edac::Hamming::encode17123(m_deInterData + 0U); + edac::Hamming::encode17123(m_deInterData + 17U); + edac::Hamming::encode17123(m_deInterData + 34U); + + // Run through each of the 17 columns + for (uint32_t c = 0U; c < 17U; c++) + m_deInterData[c + 51U] = m_deInterData[c + 0U] ^ m_deInterData[c + 17U] ^ m_deInterData[c + 34U]; +} + +/// +/// +/// +void ShortLC::encodeInterleave() +{ + for (uint32_t i = 0U; i < 72U; i++) + m_rawData[i] = false; + + for (uint32_t a = 0U; a < 67U; a++) { + // Calculate the interleave sequence + uint32_t interleaveSequence = (a * 4U) % 67U; + + // Unshuffle the data + m_rawData[interleaveSequence] = m_deInterData[a]; + } + + m_rawData[67U] = m_deInterData[67U]; +} + +/// +/// +/// +/// +void ShortLC::encodeExtractBinary(uint8_t* data) +{ + assert(data != NULL); + + Utils::bitsToByteBE(m_rawData + 0U, data[0U]); + Utils::bitsToByteBE(m_rawData + 8U, data[1U]); + Utils::bitsToByteBE(m_rawData + 16U, data[2U]); + Utils::bitsToByteBE(m_rawData + 24U, data[3U]); + Utils::bitsToByteBE(m_rawData + 32U, data[4U]); + Utils::bitsToByteBE(m_rawData + 40U, data[5U]); + Utils::bitsToByteBE(m_rawData + 48U, data[6U]); + Utils::bitsToByteBE(m_rawData + 56U, data[7U]); + Utils::bitsToByteBE(m_rawData + 64U, data[8U]); +} diff --git a/dmr/lc/ShortLC.h b/dmr/lc/ShortLC.h new file mode 100644 index 00000000..50932cb7 --- /dev/null +++ b/dmr/lc/ShortLC.h @@ -0,0 +1,81 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__DMR_LC__SHORT_LC_H__) +#define __DMR_LC__SHORT_LC_H__ + +#include "Defines.h" + +namespace dmr +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents short DMR link control. + // --------------------------------------------------------------------------- + + class HOST_SW_API ShortLC { + public: + /// Initializes a new instance of the ShortLC class. + ShortLC(); + /// Finalizes a instance of the ShortLC class. + ~ShortLC(); + + /// Decode DMR short-link control data. + bool decode(const uint8_t* in, uint8_t* out); + /// Encode DMR short-link control data. + void encode(const uint8_t* in, uint8_t* out); + + private: + bool* m_rawData; + bool* m_deInterData; + + /// + void decodeExtractBinary(const uint8_t* in); + /// + void decodeDeInterleave(); + /// + bool decodeErrorCheck(); + /// + void decodeExtractData(uint8_t* data) const; + + /// + void encodeExtractData(const uint8_t* in) const; + /// + void encodeErrorCheck(); + /// + void encodeInterleave(); + /// + void encodeExtractBinary(uint8_t* data); + }; + } // namespace lc +} // namespace dmr + +#endif // __DMR_LC__SHORT_LC_H__ diff --git a/edac/AMBEFEC.cpp b/edac/AMBEFEC.cpp new file mode 100644 index 00000000..bf900a62 --- /dev/null +++ b/edac/AMBEFEC.cpp @@ -0,0 +1,554 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2010,2014,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2016 Mathias Weyland, HB9FRV +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/AMBEFEC.h" +#include "edac/Golay24128.h" +#include "edac/Hamming.h" + +using namespace edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the AMBEFEC class. +/// +AMBEFEC::AMBEFEC() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the AMBEFEC class. +/// +AMBEFEC::~AMBEFEC() +{ + /* stub */ +} + +/// +/// Regnerates the DMR AMBE FEC for the input bytes. +/// +/// +/// Count of errors. +uint32_t AMBEFEC::regenerateDMR(uint8_t* bytes) const +{ + assert(bytes != NULL); + + uint32_t a1 = 0U, a2 = 0U, a3 = 0U; + uint32_t b1 = 0U, b2 = 0U, b3 = 0U; + uint32_t c1 = 0U, c2 = 0U, c3 = 0U; + + uint32_t MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++) { + uint32_t a1Pos = DMR_A_TABLE[i]; + uint32_t b1Pos = DMR_B_TABLE[i]; + uint32_t c1Pos = DMR_C_TABLE[i]; + + uint32_t a2Pos = a1Pos + 72U; + if (a2Pos >= 108U) + a2Pos += 48U; + uint32_t b2Pos = b1Pos + 72U; + if (b2Pos >= 108U) + b2Pos += 48U; + uint32_t c2Pos = c1Pos + 72U; + if (c2Pos >= 108U) + c2Pos += 48U; + + uint32_t a3Pos = a1Pos + 192U; + uint32_t b3Pos = b1Pos + 192U; + uint32_t c3Pos = c1Pos + 192U; + + if (READ_BIT(bytes, a1Pos)) + a1 |= MASK; + if (READ_BIT(bytes, a2Pos)) + a2 |= MASK; + if (READ_BIT(bytes, a3Pos)) + a3 |= MASK; + if (READ_BIT(bytes, b1Pos)) + b1 |= MASK; + if (READ_BIT(bytes, b2Pos)) + b2 |= MASK; + if (READ_BIT(bytes, b3Pos)) + b3 |= MASK; + if (READ_BIT(bytes, c1Pos)) + c1 |= MASK; + if (READ_BIT(bytes, c2Pos)) + c2 |= MASK; + if (READ_BIT(bytes, c3Pos)) + c3 |= MASK; + + MASK >>= 1; + } + + uint32_t errors = regenerate(a1, b1, c1, true); + errors += regenerate(a2, b2, c2, true); + errors += regenerate(a3, b3, c3, true); + + MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++) { + uint32_t a1Pos = DMR_A_TABLE[i]; + uint32_t b1Pos = DMR_B_TABLE[i]; + uint32_t c1Pos = DMR_C_TABLE[i]; + + uint32_t a2Pos = a1Pos + 72U; + if (a2Pos >= 108U) + a2Pos += 48U; + uint32_t b2Pos = b1Pos + 72U; + if (b2Pos >= 108U) + b2Pos += 48U; + uint32_t c2Pos = c1Pos + 72U; + if (c2Pos >= 108U) + c2Pos += 48U; + + uint32_t a3Pos = a1Pos + 192U; + uint32_t b3Pos = b1Pos + 192U; + uint32_t c3Pos = c1Pos + 192U; + + WRITE_BIT(bytes, a1Pos, a1 & MASK); + WRITE_BIT(bytes, a2Pos, a2 & MASK); + WRITE_BIT(bytes, a3Pos, a3 & MASK); + WRITE_BIT(bytes, b1Pos, b1 & MASK); + WRITE_BIT(bytes, b2Pos, b2 & MASK); + WRITE_BIT(bytes, b3Pos, b3 & MASK); + WRITE_BIT(bytes, c1Pos, c1 & MASK); + WRITE_BIT(bytes, c2Pos, c2 & MASK); + WRITE_BIT(bytes, c3Pos, c3 & MASK); + + MASK >>= 1; + } + + return errors; +} + +/// +/// Returns the number of errors on the DMR BER input bytes. +/// +/// +/// Count of errors. +uint32_t AMBEFEC::measureDMRBER(const uint8_t* bytes) const +{ + assert(bytes != NULL); + + uint32_t a1 = 0U, a2 = 0U, a3 = 0U; + uint32_t b1 = 0U, b2 = 0U, b3 = 0U; + uint32_t c1 = 0U, c2 = 0U, c3 = 0U; + + uint32_t MASK = 0x800000U; + for (uint32_t i = 0U; i < 24U; i++) { + uint32_t a1Pos = DMR_A_TABLE[i]; + uint32_t b1Pos = DMR_B_TABLE[i]; + uint32_t c1Pos = DMR_C_TABLE[i]; + + uint32_t a2Pos = a1Pos + 72U; + if (a2Pos >= 108U) + a2Pos += 48U; + uint32_t b2Pos = b1Pos + 72U; + if (b2Pos >= 108U) + b2Pos += 48U; + uint32_t c2Pos = c1Pos + 72U; + if (c2Pos >= 108U) + c2Pos += 48U; + + uint32_t a3Pos = a1Pos + 192U; + uint32_t b3Pos = b1Pos + 192U; + uint32_t c3Pos = c1Pos + 192U; + + if (READ_BIT(bytes, a1Pos)) + a1 |= MASK; + if (READ_BIT(bytes, a2Pos)) + a2 |= MASK; + if (READ_BIT(bytes, a3Pos)) + a3 |= MASK; + if (READ_BIT(bytes, b1Pos)) + b1 |= MASK; + if (READ_BIT(bytes, b2Pos)) + b2 |= MASK; + if (READ_BIT(bytes, b3Pos)) + b3 |= MASK; + if (READ_BIT(bytes, c1Pos)) + c1 |= MASK; + if (READ_BIT(bytes, c2Pos)) + c2 |= MASK; + if (READ_BIT(bytes, c3Pos)) + c3 |= MASK; + + MASK >>= 1; + } + + uint32_t errors = regenerate(a1, b1, c1, true); + errors += regenerate(a2, b2, c2, true); + errors += regenerate(a3, b3, c3, true); + + return errors; +} + +/// +/// Regenerates the P25 IMBE FEC for the input bytes. +/// +/// +/// Count of errors. +uint32_t AMBEFEC::regenerateIMBE(uint8_t* bytes) const +{ + assert(bytes != NULL); + + bool orig[144U]; + bool temp[144U]; + + // De-interleave + for (uint32_t i = 0U; i < 144U; i++) { + uint32_t n = IMBE_INTERLEAVE[i]; + orig[i] = temp[i] = READ_BIT(bytes, n); + } + + // now .. + + // 12 voice bits 0 + // 11 golay bits 12 + // + // 12 voice bits 23 + // 11 golay bits 35 + // + // 12 voice bits 46 + // 11 golay bits 58 + // + // 12 voice bits 69 + // 11 golay bits 81 + // + // 11 voice bits 92 + // 4 hamming bits 103 + // + // 11 voice bits 107 + // 4 hamming bits 118 + // + // 11 voice bits 122 + // 4 hamming bits 133 + // + // 7 voice bits 137 + + // Process the c0 section first to allow the de-whitening to be accurate + + // Check/Fix FEC + bool* bit = temp; + + // c0 + uint32_t g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c0data = Golay24128::decode23127(g1); + uint32_t g2 = Golay24128::encode23127(c0data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + bool prn[114U]; + + // Create the whitening vector and save it for future use + uint32_t p = 16U * c0data; + for (uint32_t i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // De-whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + temp[i + 23U] ^= prn[i]; + + // c1 + g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c1data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c1data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c2 + g1 = 0; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c2data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c2data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c3 + g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c3data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c3data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c4 + Hamming::decode15113_1(bit); + bit += 15U; + + // c5 + Hamming::decode15113_1(bit); + bit += 15U; + + // c6 + Hamming::decode15113_1(bit); + + // Whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + temp[i + 23U] ^= prn[i]; + + uint32_t errors = 0U; + for (uint32_t i = 0U; i < 144U; i++) { + if (orig[i] != temp[i]) + errors++; + } + + // Interleave + for (uint32_t i = 0U; i < 144U; i++) { + uint32_t n = IMBE_INTERLEAVE[i]; + WRITE_BIT(bytes, n, temp[i]); + } + + return errors; +} + +/// +/// Returns the number of errors on the P25 BER input bytes. +/// +/// +/// Count of errors. +uint32_t AMBEFEC::measureP25BER(const uint8_t* bytes) const +{ + assert(bytes != NULL); + + bool orig[144U]; + bool temp[144U]; + + // De-interleave + for (uint32_t i = 0U; i < 144U; i++) { + uint32_t n = IMBE_INTERLEAVE[i]; + orig[i] = temp[i] = READ_BIT(bytes, n); + } + + // now .. + + // 12 voice bits 0 + // 11 golay bits 12 + // + // 12 voice bits 23 + // 11 golay bits 35 + // + // 12 voice bits 46 + // 11 golay bits 58 + // + // 12 voice bits 69 + // 11 golay bits 81 + // + // 11 voice bits 92 + // 4 hamming bits 103 + // + // 11 voice bits 107 + // 4 hamming bits 118 + // + // 11 voice bits 122 + // 4 hamming bits 133 + // + // 7 voice bits 137 + + // Process the c0 section first to allow the de-whitening to be accurate + + // Check/Fix FEC + bool* bit = temp; + + // c0 + uint32_t g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c0data = Golay24128::decode23127(g1); + uint32_t g2 = Golay24128::encode23127(c0data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + bool prn[114U]; + + // Create the whitening vector and save it for future use + uint32_t p = 16U * c0data; + for (uint32_t i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // De-whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + temp[i + 23U] ^= prn[i]; + + // c1 + g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c1data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c1data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c2 + g1 = 0; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c2data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c2data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c3 + g1 = 0U; + for (uint32_t i = 0U; i < 23U; i++) + g1 = (g1 << 1) | (bit[i] ? 0x01U : 0x00U); + uint32_t c3data = Golay24128::decode23127(g1); + g2 = Golay24128::encode23127(c3data); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c4 + Hamming::decode15113_1(bit); + bit += 15U; + + // c5 + Hamming::decode15113_1(bit); + bit += 15U; + + // c6 + Hamming::decode15113_1(bit); + + // Whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + temp[i + 23U] ^= prn[i]; + + uint32_t errors = 0U; + for (uint32_t i = 0U; i < 144U; i++) { + if (orig[i] != temp[i]) + errors++; + } + + return errors; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +/// +/// +/// +uint32_t AMBEFEC::regenerate(uint32_t& a, uint32_t& b, uint32_t& c, bool b23) const +{ + uint32_t old_a = a; + uint32_t old_b = b; + + // For the b23 bypass + bool b24 = (b & 0x01U) == 0x01U; + + uint32_t data = Golay24128::decode24128(a); + + uint32_t new_a = Golay24128::encode24128(data); + + // The PRNG + uint32_t p = PRNG_TABLE[data]; + + b ^= p; + + uint32_t datb = Golay24128::decode24128(b); + + uint32_t new_b = Golay24128::encode24128(datb); + + new_b ^= p; + + if (b23) { + new_b &= 0xFFFFFEU; + new_b |= b24 ? 0x01U : 0x00U; + } + + uint32_t errsA = 0U, errsB = 0U; + + uint32_t v = new_a ^ old_a; + while (v != 0U) { + v &= v - 1U; + errsA++; + } + + v = new_b ^ old_b; + while (v != 0U) { + v &= v - 1U; + errsB++; + } + + if (b23) { + if (errsA >= 4U || ((errsA + errsB) >= 6U && errsA >= 2U)) { + a = 0xF00292U; + b = 0x0E0B20U; + c = 0x000000U; + } + } + + a = new_a; + b = new_b; + + return errsA + errsB; +} diff --git a/edac/AMBEFEC.h b/edac/AMBEFEC.h new file mode 100644 index 00000000..0c602a1c --- /dev/null +++ b/edac/AMBEFEC.h @@ -0,0 +1,500 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2010,2014,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__AMBE_FEC_H__) +#define __AMBE_FEC_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t PRNG_TABLE[] = { + 0x42CC47U, 0x19D6FEU, 0x304729U, 0x6B2CD0U, 0x60BF47U, 0x39650EU, 0x7354F1U, 0xEACF60U, 0x819C9FU, 0xDE25CEU, + 0xD7B745U, 0x8CC8B8U, 0x8D592BU, 0xF71257U, 0xBCA084U, 0xA5B329U, 0xEE6AFAU, 0xF7D9A7U, 0xBCC21CU, 0x4712D9U, + 0x4F2922U, 0x14FA37U, 0x5D43ECU, 0x564115U, 0x299A92U, 0x20A9EBU, 0x7B707DU, 0x3BE3A4U, 0x20D95BU, 0x6B085AU, + 0x5233A5U, 0x99A474U, 0xC0EDCBU, 0xCB5F12U, 0x918455U, 0xF897ECU, 0xE32E3BU, 0xAA7CC2U, 0xB1E7C9U, 0xFC561DU, + 0xA70DE6U, 0x8DBE73U, 0xD4F608U, 0x57658DU, 0x0E5E56U, 0x458DABU, 0x7E15B8U, 0x376645U, 0x2DFD86U, 0x64EC3BU, + 0x3F1F60U, 0x3481B4U, 0x4DA00FU, 0x067BCEU, 0x1B68B1U, 0xD19328U, 0xCA03FFU, 0xA31856U, 0xF8EB81U, 0xF9F2F8U, + 0xA26067U, 0xA91BB6U, 0xF19A59U, 0x9A6148U, 0x8372B6U, 0xC8E86FU, 0x9399DCU, 0x1A0291U, 0x619142U, 0x6DE9FFU, + 0x367A2CU, 0x7D2511U, 0x6484DAU, 0x2F1F0FU, 0x1E6DB4U, 0x55F6E1U, 0x0EA70AU, 0x061C96U, 0xDD0E45U, 0xB4D738U, + 0xAF64ABU, 0xE47F42U, 0xFDBE9DU, 0xB684ACU, 0xFE5773U, 0xC1E4A2U, 0x8AFD0DU, 0x932ED4U, 0xD814E3U, 0x81853AU, + 0x225EECU, 0x7A6945U, 0x31A112U, 0x2AB2EBU, 0x630974U, 0x785AB5U, 0x11E3CEU, 0x4A715BU, 0x402AA0U, 0x199B7DU, + 0x16C05EU, 0x6F5283U, 0xA4FB10U, 0xBFA8ECU, 0xF633B7U, 0xEC4012U, 0xADD8C9U, 0xD6EB1CU, 0xDD3027U, 0x84A1FAU, + 0xCF9E19U, 0xD64C80U, 0xBC4557U, 0xA7B62EU, 0x6E2DA1U, 0x311F50U, 0x38C68EU, 0x63D5BFU, 0x486E60U, 0x10BFE1U, + 0x5BAD1EU, 0x4A4647U, 0x0157F0U, 0x7ACC29U, 0x73BEEAU, 0x2825D7U, 0xA0940CU, 0xFBCFF9U, 0xB05C62U, 0x892426U, + 0xC6B3DDU, 0xDF3840U, 0x9449B3U, 0xCED3BEU, 0xE7804DU, 0xBC3B90U, 0xF5AA0BU, 0xE6D17EU, 0x2D43B5U, 0x345A04U, + 0x5EA9DBU, 0x07A202U, 0x0C7134U, 0x45C9FDU, 0x5EDA0AU, 0x310193U, 0x6830C4U, 0x62AA3DU, 0x3B59B2U, 0xB04043U, + 0xEB975CU, 0x82BCADU, 0x912E62U, 0xD8F7FBU, 0x82C489U, 0x895F54U, 0xF00FE7U, 0xFBBC2AU, 0xA2E771U, 0xE956C4U, + 0xF6CD1FU, 0x3F8FEAU, 0x0534E1U, 0x4C653CU, 0x17FE8FU, 0x1C4C52U, 0x4515A1U, 0x2E86A9U, 0x3FBD56U, 0x756C87U, + 0x6ED218U, 0x279179U, 0x7C0AA6U, 0xD53B17U, 0x8EE0C8U, 0x85F291U, 0xD94B36U, 0x9298EFU, 0xAB8318U, 0xE07301U, + 0xBB68DFU, 0xB2CB7CU, 0xE910A5U, 0xE101D2U, 0x92BB4BU, 0x59E8B4U, 0x407175U, 0x0B026AU, 0x12989BU, 0x792944U, + 0x2376EDU, 0x2EF5BAU, 0x758663U, 0x7C1ED5U, 0x078D0CU, 0x4EF6ABU, 0x5567F2U, 0x9F7C29U, 0xC68E9CU, 0xC51747U, + 0xBC6422U, 0xB7EFB9U, 0xECFD44U, 0xA50497U, 0xAF178AU, 0xD68C69U, 0xD97DB5U, 0x82670EU, 0xCBB45BU, 0x508D90U, + 0x190A25U, 0x63F0FEU, 0x68E3C7U, 0x317A10U, 0x3A09D9U, 0x6B926EU, 0x004237U, 0x1B79C8U, 0x53EA59U, 0x48B3B7U, + 0x811166U, 0xDE4A79U, 0xF5F988U, 0xAC6057U, 0xE733FEU, 0xFF89ADU, 0xB49830U, 0x8F4BC3U, 0xC6F00EU, 0x9DA135U, + 0x942FE0U, 0xC71C3BU, 0x4DC78FU, 0x3476C4U, 0x7F6C39U, 0x66BFAAU, 0x298657U, 0x725504U, 0x5B4E89U, 0x01FE72U, + 0x0835A3U, 0x53269CU, 0x189D4DU, 0x01CDC2U, 0xEA763BU, 0xF3A56DU, 0xB0BCD4U, 0xE80F13U, 0xE355CAU, 0x98C47DU, + 0x91AB24U, 0xCE38DBU, 0x87A35AU, 0x9CD3A5U, 0xD648F4U, 0xAF7B6FU, 0x24A292U, 0x7D3011U, 0x764B6DU, 0x2DDABEU, + 0x44D123U, 0x5E22D8U, 0x1FB09DU, 0x04A926U, 0x4F5AF3U, 0x064128U, 0x3DB105U, 0x70AAD6U, 0xAA392FU, 0xA1C4B8U, + 0xF8C7C0U, 0xD35D0FU, 0x8A2E9EU, 0xC1B761U, 0xDA44F0U, 0x925E8FU, 0x89CF4EU, 0xE8B4D1U, 0xB32728U, 0xB8FE7FU, + 0x61DCC6U, 0x2A4701U, 0x1614D8U, 0x5DADE2U, 0x46BE37U, 0x0F44DCU, 0x54D549U, 0x5D8E32U, 0x263DAFU, 0x2C237CU, + 0x75E291U, 0xBE5982U, 0xA74A7FU, 0xC493A4U, 0xDFA131U, 0x967A5AU, 0xCCCB8EU, 0xC1D835U, 0x9A02ECU, 0xF331BBU, + 0xE8B812U, 0xA3EBC5U, 0xBA507CU, 0x7080ABU, 0x099BC2U, 0x02285DU, 0x59718CU, 0x50C273U, 0x0B1862U, 0x4A1F8CU, + 0x70A655U, 0x3BF5C2U, 0x666FBBU, 0x6DDE68U, 0x3485C5U, 0x9F161EU, 0xC46F4BU, 0x8CFDF0U, 0x97C625U, 0xDE058EU, + 0xC59CD3U, 0xAEAE20U, 0xF775BCU, 0xFC647FU, 0xBD9F02U, 0xE70C91U, 0xCC1468U, 0x11E7B7U, 0x1AFC36U, 0x435B49U, + 0x080398U, 0x139027U, 0x7B63FEU, 0x607AF9U, 0x29E900U, 0x7293D6U, 0x79026FU, 0x00D930U, 0x0BEAF1U, 0xD3614EU, + 0x90119FU, 0x8B8AE4U, 0xC61969U, 0xBD609AU, 0xB4F247U, 0xEFA954U, 0xE518A9U, 0xBC0362U, 0xD7D0D6U, 0xCE7E8DU, + 0x856F18U, 0x1C94E3U, 0x578726U, 0x0D5F1DU, 0x24ECC0U, 0x7FF713U, 0x3E26AAU, 0x251D6DU, 0x6A8F14U, 0x53648BU, + 0x19757AU, 0x40AEB4U, 0xCB9CA5U, 0x90055AU, 0x9956C3U, 0xE2ED34U, 0xAB3C7DU, 0xB126EAU, 0xFA9513U, 0xA3D2C8U, + 0x886BFDU, 0xD9F836U, 0xD2A2E3U, 0x8D1359U, 0x454804U, 0x5EDBF7U, 0x37637AU, 0x2C3089U, 0x67ABD4U, 0x3E8847U, + 0x3551BAU, 0x4D6331U, 0x46B8C4U, 0x1D299FU, 0x54120EU, 0x5FC0E1U, 0x86D93BU, 0xE56A0EU, 0xFBB1D5U, 0xB2B600U, + 0xA94EABU, 0xE05DF6U, 0x9BE605U, 0x90B798U, 0xC92C6BU, 0xC3DE66U, 0x9AC7BDU, 0xD15448U, 0x6A3FD3U, 0x23ADA3U, + 0x78346CU, 0x7147F5U, 0x2BDC02U, 0x0EAD5BU, 0x553FFCU, 0x1EA425U, 0x07D5F2U, 0x4C4ECBU, 0x554C14U, 0x3EB3F5U, + 0xE4A26AU, 0xED799BU, 0xB6CA85U, 0xFFD25CU, 0xC421BFU, 0x8F3A22U, 0x96AB51U, 0xDC518CU, 0x895217U, 0x8289F2U, + 0xF9B8A9U, 0xF0231CU, 0x2BF1C7U, 0x62C80AU, 0x781B39U, 0x1320E5U, 0x4AB156U, 0x41EB8FU, 0x1848E0U, 0x13D771U, + 0x4886AEU, 0x203C5FU, 0x3B6F40U, 0x76F6A1U, 0xE5457EU, 0xAE1EE7U, 0xD7AC10U, 0xDCB549U, 0x8476EFU, 0x8FC536U, + 0xD49DE9U, 0x9D0ED8U, 0xA63513U, 0xEFE4A6U, 0xB4DF7DU, 0x3E0D00U, 0x779693U, 0x4CA75EU, 0x0568ADU, 0x527BB0U, + 0x59C34BU, 0x00109FU, 0x0A0B14U, 0x73FA61U, 0x38E0BAU, 0x23530FU, 0x6A88D4U, 0xB199DDU, 0x98322AU, 0xC260F3U, + 0xCBF944U, 0x908A0DU, 0xDB11F2U, 0xC28163U, 0xADFABDU, 0xBC694CU, 0xF65243U, 0xAD83BAU, 0xA40D6DU, 0x5F7EF4U, + 0x16E787U, 0x0DF44AU, 0x460EF1U, 0x5E1F24U, 0x15CC3FU, 0x6C77CAU, 0x676401U, 0x3C9CBDU, 0x359FEEU, 0x6A0413U, + 0x02F590U, 0x91EE4DU, 0xDA3C3EU, 0xC305A3U, 0x889658U, 0xF14D99U, 0xFA7F86U, 0xA1E677U, 0xE981E8U, 0xF21A10U, + 0xBB4BD7U, 0x80F1CEU, 0xCB6239U, 0x123BE0U, 0x1D885FU, 0x45921EU, 0x6641E1U, 0x3DE870U, 0x74BBAFU, 0x6F00C6U, + 0x261055U, 0x7DCBA8U, 0x57787AU, 0x0E2167U, 0x05B28CU, 0xCC8819U, 0x975BE2U, 0xBC52B7U, 0xE5E52CU, 0xEB37C9U, + 0xB20E12U, 0xF9DD2FU, 0xE8C6FCU, 0x837701U, 0xD8AD82U, 0xD1BE5AU, 0x0B0525U, 0x0244B4U, 0x79FE5BU, 0x322DCAU, + 0x2B3495U, 0x60876CU, 0x79DCFBU, 0x334C12U, 0x4C7745U, 0x45A4DCU, 0x1E3F23U, 0x175FF2U, 0xC4C0D8U, 0xAFF30DU, + 0xB72AF6U, 0xFCB96BU, 0xA5C338U, 0xAE5295U, 0xF54946U, 0xDCBABBU, 0x87A1A8U, 0xCF2165U, 0xD4DA9EU, 0x9FC90BU, + 0x223070U, 0x6922A4U, 0x30B92FU, 0x3348D6U, 0x695B01U, 0x20C038U, 0x1BB2EFU, 0x523B06U, 0x49EC99U, 0x02D7C8U, + 0x5B4777U, 0x713CA6U, 0xA8AF49U, 0xA3B650U, 0xF84586U, 0xB5DF7FU, 0xAE8CF8U, 0xC72581U, 0x9D3652U, 0x9EEDCFU, + 0xC75D34U, 0xCC0671U, 0xB5B5CAU, 0xFEAC1FU, 0x677EA4U, 0x2DC5F9U, 0x26D63AU, 0x7F1F86U, 0x142855U, 0x0DF2A8U, + 0x42E3B3U, 0x195872U, 0x108B8DU, 0x6AB31CU, 0x632063U, 0x307BAAU, 0xFBC83DU, 0xE201C4U, 0xA91393U, 0x90A82AU, + 0xDAF9E4U, 0x816A55U, 0x88D00AU, 0xD383DBU, 0xFA3A64U, 0xA569A5U, 0xEEE2DEU, 0x76D243U, 0x3D0D90U, 0x649E6DU, + 0x47E76EU, 0x1C7491U, 0x156E49U, 0x4E9DDEU, 0x0604B7U, 0x3D3720U, 0x76FDD9U, 0x6FEC06U, 0x2417B7U, 0xFD04F8U, + 0xF29D29U, 0x886F92U, 0xC1744FU, 0xDAC73CU, 0x939EB1U, 0x880C63U, 0xEBE79EU, 0xB2F285U, 0xB86970U, 0xE11ABBU, + 0xEA822EU, 0x311155U, 0x586AC0U, 0x43F92BU, 0x0A81F6U, 0x5412C5U, 0x5D111CU, 0x26E8CBU, 0x2D7B63U, 0x74213CU, + 0x3F90CDU, 0x2E8B52U, 0x645883U, 0xDFE36CU, 0x96F375U, 0xDD0882U, 0xC40B1BU, 0x8FD6CCU, 0xB464A5U, 0xFC7F3EU, + 0xA7AECBU, 0xAA9511U, 0xF10634U, 0xBA5CEFU, 0x83ED32U, 0x483681U, 0x5015DCU, 0x138D3FU, 0x48DEA2U, 0x616571U, + 0x3AF40CU, 0x33AF97U, 0x681D72U, 0x2246E9U, 0x3BD7B9U, 0x506C46U, 0x0D2FDFU, 0x869338U, 0xDDC061U, 0xD45BD6U, + 0xAF6A0FU, 0xE7B8C0U, 0xFC2371U, 0xBF102EU, 0xA6C9DFU, 0xEDDA40U, 0x943089U, 0x9FA1BFU, 0x459A66U, 0x0C4995U, + 0x175108U, 0x7AE243U, 0x6139B6U, 0x2A2A2DU, 0x73D3D8U, 0x79C183U, 0x204A26U, 0x0B3FFDU, 0x5AA420U, 0x111613U, + 0x8A4FDFU, 0xC3DC2CU, 0xF9A7B5U, 0xB034EAU, 0xEBAC5BU, 0xE0CF94U, 0xBD5465U, 0xF605FAU, 0xCFBEA3U, 0x85AC54U, + 0x9E55DDU, 0xD7C62AU, 0x0CDD73U, 0x252FCDU, 0x76361CU, 0x7DF5D3U, 0x3546E2U, 0x6E5B39U, 0x67A98CU, 0x1CB247U, + 0x57231AU, 0x4AD8A9U, 0x01CA74U, 0x191187U, 0xF2208AU, 0xA9AB50U, 0xA0F8A5U, 0xFB403EU, 0xF2D34BU, 0xA9A880U, + 0xCB393DU, 0xD262EEU, 0x99D0B7U, 0xC04B00U, 0xCB1AC9U, 0xB0B176U, 0x39E3A7U, 0x677EF8U, 0x2ECD58U, 0x359687U, + 0x7E277EU, 0x473D69U, 0x0CEEB0U, 0x55D557U, 0x5F04CEU, 0x0C8EBDU, 0x25BD60U, 0x7E64DBU, 0xB7771EU, 0xACCC05U, + 0xE51CF0U, 0xBF2F2AU, 0x90F497U, 0xC9E7D4U, 0xC25F09U, 0x9B9CBAU, 0xD08767U, 0xEB320CU, 0xA36999U, 0x38FB42U, + 0x7180B3U, 0x22112CU, 0x29AA45U, 0x50F9D2U, 0x1B610AU, 0x0202FDU, 0x4899E4U, 0x57080BU, 0x3E72DAU, 0x65E165U, + 0x6CFA34U, 0xB70BEBU, 0xBC104AU, 0xE4E295U, 0x8F7BECU, 0x96787FU, 0xD583B2U, 0x9E9740U, 0x870C5DU, 0xECFFA6U, + 0xF4E433U, 0xBF35F8U, 0xE00F8DU, 0x699C16U, 0x3265EBU, 0x1B6638U, 0x40F515U, 0x0A8DC6U, 0x131E1BU, 0x5845A0U, + 0x21F670U, 0x2A6E1FU, 0x791D8EU, 0x708651U, 0x2AD7E8U, 0xE37CAFU, 0xD8EE56U, 0x97B3C1U, 0x8E0018U, 0xC51B6FU, + 0x9CC9E6U, 0xB67019U, 0xEF23C8U, 0xE498F2U, 0xBF9927U, 0xF643ECU, 0xCD7051U, 0x04E902U, 0x563AFFU, 0x5D006CU, + 0x04D3A1U, 0x0FCA9AU, 0x72794FU, 0x39A2B4U, 0x228231U, 0x6A19EAU, 0x714E96U, 0x18F705U, 0x4324FCU, 0xC83E3BU, + 0x918D02U, 0xDADCD5U, 0xC2470CU, 0xA135B3U, 0xBABCF2U, 0xF30F4DU, 0xA8549EU, 0xA1C543U, 0xDEFF78U, 0xD42CBCU, + 0x0DB747U, 0x46C6D2U, 0x5F5C89U, 0x144F60U, 0x6FA6F7U, 0x66350EU, 0x2C0A59U, 0x35DAE0U, 0x7EC12FU, 0x0D32FEU, + 0x0429C1U, 0x5FB911U, 0xD642AEU, 0x895167U, 0xC3D8B0U, 0xFAAB89U, 0xB1315AU, 0xA8C0A7U, 0xE3DB24U, 0xB84879U, + 0x913382U, 0xCBA317U, 0x82F8FCU, 0x994BA9U, 0x50C213U, 0x4390CEU, 0x282F5DU, 0x713E30U, 0x7FCDE3U, 0x26565EU, + 0x2D0485U, 0x56BDD4U, 0x1FAE7BU, 0x0475AAU, 0x4DD555U, 0x17CE4CU, 0x9C1D9BU, 0xE52473U, 0xEEF7E4U, 0xB7CD1DU, + 0xF45E42U, 0xEF87E3U, 0x87B43CU, 0x986FADU, 0xD16FD2U, 0x8AD403U, 0x8103A8U, 0xD83A75U, 0x33A826U, 0x2BF39BU, + 0x604049U, 0x7B99A4U, 0x328ABFU, 0x49306AU, 0x407191U, 0x1BEA04U, 0x19D96FU, 0x4001F2U, 0x0FB201U, 0x36E9DCU, + 0xFD7ADFU, 0xE64326U, 0xAF91F9U, 0xF51249U, 0xDC2B16U, 0x87F8D7U, 0xCCE668U, 0xC517B1U, 0x9E8C46U, 0x97BF5FU, + 0xED6498U, 0xA67461U, 0x378FF6U, 0x788C8FU, 0x611514U, 0x0AE6F1U, 0x53FC2BU, 0x596F3EU, 0x0216C5U, 0x4B8508U, + 0x507FBBU, 0x396EE6U, 0x22F535U, 0xE99688U, 0xB10F43U, 0xBA1D36U, 0xC3E2ADU, 0xC07178U, 0x9B28C3U, 0xD69A8BU, + 0xCD817CU, 0x8570E5U, 0xFEEB12U, 0xF5E8CBU, 0xAC10C4U, 0x270335U, 0x7ED8EAU, 0x156B5BU, 0x0E7A14U, 0x46A0C5U, + 0x5D937AU, 0x144AA3U, 0x4F79D5U, 0x6CF35CU, 0x31228FU, 0x7A1932U, 0x628E69U, 0xA9D59CU, 0x926517U, 0xDBBEE2U, + 0x80ADB9U, 0x891424U, 0xD246D7U, 0xD8ED1AU, 0xA17C28U, 0xEA27F5U, 0xF3942EU, 0xB8CE8FU, 0xAB5FD0U, 0x466461U, + 0x1CB7BEU, 0x152F6FU, 0x4E1CC0U, 0x05D799U, 0x1CE66EU, 0x773DF7U, 0x7EAB00U, 0x249048U, 0x6D41D7U, 0x765A26U, + 0x1DA9F9U, 0x8431C8U, 0xCF0203U, 0x96C1DEU, 0x90D86DU, 0xCB6A30U, 0xA23193U, 0xB9A24EU, 0xF05B95U, 0xEB48A0U, + 0xA0D27AU, 0xD8A39FU, 0xD33804U, 0x0A9B79U, 0x01C3AAU, 0x5A5437U, 0x132FD4U, 0x28BC0DU, 0x60253AU, 0x3F57E3U, + 0x3CCC7CU, 0x65DD9DU, 0x4E26C2U, 0x172572U, 0xDCDDADU, 0xC64E64U, 0x8F5553U, 0x94A68AU, 0xFDBE7DU, 0xA66DE4U, + 0xADD68BU, 0xF4C75AU, 0xFE0CC1U, 0x873E34U, 0xC8A72FU, 0xDBD0C2U, 0x124B10U, 0x49998DU, 0x40A8FEU, 0x3A3323U, + 0x316088U, 0x68D95DU, 0x235B06U, 0x3A00B3U, 0x51B178U, 0x4AEA89U, 0x025816U, 0x59C36FU, 0xD092B8U, 0x8B2930U, + 0xE43AC7U, 0xF5E2DEU, 0xBEC121U, 0xA71AF0U, 0xED8B7FU, 0x94B40EU, 0x9F66D1U, 0xD45D68U, 0xCD8CBFU, 0x8617F6U, + 0x5F2545U, 0x75FC98U, 0x2EFF62U, 0x674467U, 0x7C959CU, 0x318F09U, 0x0A7CD2U, 0x4967AFU, 0x11D62CU, 0x1A8CD1U, + 0x431F02U, 0x48A69DU, 0xB3E5ECU, 0xFA7623U, 0xE10E9AU, 0xA99948U, 0xB20215U, 0xD971A6U, 0x80E86BU, 0x8BDA90U, + 0xD60185U, 0x9D907EU, 0x8FFBFBU, 0xE66920U, 0x7D705DU, 0x3483CEU, 0x6F9833U, 0x646BF1U, 0x1DF3E8U, 0x17E017U, + 0x4E1BC6U, 0x050A79U, 0x1E8038U, 0x5773E7U, 0x2C685EU, 0xA1BD89U, 0xFB86B0U, 0xF01477U, 0xA16D8EU, 0xCAFE19U, + 0xD365C1U, 0x9815AEU, 0x839E3FU, 0xCBCDC4U, 0x907611U, 0xB9E70AU, 0xE2BDE7U, 0x2B0E34U, 0x301789U, 0x7BE4DAU, + 0x477707U, 0x0C2FACU, 0x558C79U, 0x5E9743U, 0x0D4496U, 0x04786DU, 0x7FABE0U, 0x3730B3U, 0x3C014AU, 0xE7DADDU, + 0xEEE834U, 0x956163U, 0xDCB2FAU, 0xC78905U, 0x8D5BD4U, 0xD0427BU, 0xDBF12BU, 0xA22AB4U, 0xA93B4DU, 0xFA819AU, + 0xB3D2B3U, 0x287B64U, 0x40289DU, 0x5BB206U, 0x100153U, 0x495CB8U, 0x42CF2DU, 0x3BF4D6U, 0x70248BU, 0x6ABF19U, + 0x23CCF4U, 0x3C4527U, 0x75761AU, 0x8EACC1U, 0x853F44U, 0xD44EBFU, 0xDED5EEU, 0x87C751U, 0xEC3E80U, 0xF72D6FU, + 0xBEB676U, 0xE557A1U, 0xEC4D59U, 0xB6BECEU, 0x9DA527U, 0x443078U, 0x0BCAE9U, 0x12D916U, 0x594087U, 0x6033E8U, + 0x22A831U, 0x7948A2U, 0x70535FU, 0x2BC01CU, 0x62BBA1U, 0x592A7BU, 0x92308EU, 0x8AC395U, 0xC15A50U, 0x9809ABU, + 0xB3B336U, 0xECB245U, 0xE54998U, 0xBEDA1BU, 0xF681E6U, 0xED35F5U, 0x8E2E0CU, 0x87FDD3U, 0x5CC453U, 0x1556ACU, + 0x0E85FDU, 0x64AC42U, 0x3D7F8BU, 0x36447CU, 0x6FD665U, 0x640FB2U, 0x3B3C4BU, 0x52A7C4U, 0x48F7B5U, 0x014C2EU, + 0x9A9FFBU, 0xD19601U, 0xA0250CU, 0xAB7FFFU, 0xF2C822U, 0xB8D1B1U, 0xA302CCU, 0xEAB907U, 0xD1E9B2U, 0x987269U, + 0xC3411CU, 0xCC8897U, 0x141A42U, 0x3F61B8U, 0x66F2A1U, 0x2DCB56U, 0x3618DFU, 0x778208U, 0x2CB3F1U, 0x0468EEU, + 0x5F7B1FU, 0x5693D0U, 0x0D8041U, 0x461B3EU, 0xFFECE7U, 0xB4FD50U, 0xA94798U, 0xE314CFU, 0xB88D76U, 0xB17EADU, + 0xCA7508U, 0xC3E553U, 0x989EA6U, 0xDB0D3DU, 0xC396E8U, 0xA8E683U, 0x717D1EU, 0x7A0EEDU, 0x219730U, 0x288422U, + 0x736ECFU, 0x1BFF14U, 0x04A4A1U, 0x4F177AU, 0x56092BU, 0x1DD884U, 0x64635DU, 0xEF70EAU, 0xA589B3U, 0xF49B54U, + 0xFF50CDU, 0xA66312U, 0x8DFA62U, 0xD628FDU, 0x9F131CU, 0x8582C3U, 0xCCF9DAU, 0xF36A29U, 0xB8B2F4U, 0x618157U, + 0x6A020AU, 0x335999U, 0x79E864U, 0x4272BFU, 0x03259AU, 0x189C40U, 0x51CFB5U, 0x0A752EU, 0x216463U, 0x79BF90U, + 0x721C0DU, 0xAB47FEU, 0xE4D727U, 0xFDEC28U, 0x963FD9U, 0x8DA646U, 0xC594B7U, 0x9E4FE8U, 0x977E60U, 0xECA597U, + 0xAF264EU, 0xB61C79U, 0xFDCDA0U, 0x65D64FU, 0x2E61DCU, 0x553881U, 0x5CAA72U, 0x0351FBU, 0x0A400CU, 0x51FB55U, + 0x3BB9CAU, 0x22223AU, 0x6993B5U, 0x30C8C4U, 0x3B5B1BU, 0xE02B82U, 0xC1B075U, 0x9B23BCU, 0xD25A8BU, 0xC9C852U, + 0x82A3A9U, 0xBB303CU, 0xF42977U, 0xADDA82U, 0xA64418U, 0xFC55E5U, 0xB5AEE6U, 0x0EBD3BU, 0x4765C8U, 0x4CD655U, + 0x17DD2EU, 0x562EEBU, 0x6C3770U, 0x25A585U, 0x3E5EDEU, 0x754F6FU, 0x2C94A1U, 0x23A758U, 0x5A3F4FU, 0xD07C96U, + 0x8BC761U, 0xC254E8U, 0xD92C97U, 0xB0BF06U, 0xEBE0D9U, 0xE25138U, 0xB8CAA7U, 0xBB98DEU, 0xE22109U, 0x896291U, + 0x10F172U, 0x5BCB2FU, 0x401A94U, 0x0CA141U, 0x77B2BAU, 0x7E6BBFU, 0x255964U, 0x6E82D9U, 0x77130AU, 0x3C3877U, + 0x04EAF4U, 0x4FD129U, 0x9C40DBU, 0x959BC6U, 0xCEAC2DU, 0xE774FCU, 0xBC6763U, 0xF6DC12U, 0xEB8DCDU, 0xA00664U, + 0xF9F4B3U, 0xD2EF4AU, 0x895E5DU, 0x800584U, 0x5A972BU, 0x132EFBU, 0x287D84U, 0x63E615U, 0x7297CEU, 0x391D23U, + 0x608E30U, 0x6AF5CDU, 0x11641EU, 0x5C5E93U, 0x4789E0U, 0x0E903DU, 0x956386U, 0xFEF053U, 0xB6E879U, 0xAD0BACU, + 0xE41077U, 0xFF83CAU, 0xB47A99U, 0xCD6870U, 0xCE93E7U, 0x96823EU, 0x9D1941U, 0xC4EBD0U, 0x2BF23FU, 0x3031EEU, + 0x790A71U, 0x229909U, 0x2AC1CEU, 0x717677U, 0x5AEDA0U, 0x039C99U, 0x480646U, 0x515587U, 0x1AEC3CU, 0x296F69U, + 0xE13492U, 0xBA8607U, 0xB39FCCU, 0xEC4CB1U, 0xA77723U, 0x9EA7DEU, 0xD51C0DU, 0xCD0F00U, 0x86D4FBU, 0xDDF56EU, + 0xF46F95U, 0x2FBCD4U, 0x268D6BU, 0x7D52B2U, 0x374165U, 0x26F9DCU, 0x4D2A9BU, 0x141163U, 0x1FD2FCU, 0x40CA2DU, + 0x497952U, 0x3322D3U, 0x7AB32CU, 0xE108F5U, 0xAA5AE2U, 0xB3E31BU, 0xF8B098U, 0x812B65U, 0x8B8936U, 0xD0D08AU, + 0xD94341U, 0x8A7894U, 0xE3A9AFU, 0xF8377AU, 0xB74481U, 0x6FDD0CU, 0x64EE5FU, 0x3D35A2U, 0x163731U, 0x5F8ECCU, + 0x045DC7U, 0x0F4616U, 0x57B6E8U, 0x7CAD79U, 0x253E86U, 0x6EC7CFU, 0x7DD478U, 0xB426A1U, 0xCF2D76U, 0xC3BC5FU, + 0x984780U, 0x935571U, 0xCACCEEU, 0x81BBBFU, 0xB82054U, 0xF371C0U, 0xE9CB3BU, 0xA05826U, 0xFB33F5U, 0x52A218U, + 0x09B88BU, 0x424BF6U, 0x53D22DU, 0x198198U, 0x043A53U, 0x6F2A06U, 0x34F1BDU, 0x3DC260U, 0x664982U, 0x6FB81BU, + 0x15A24CU, 0xDE71F5U, 0xC7482AU, 0x8CDFCBU, 0x9505D4U, 0xDE3405U, 0xA5EFFAU, 0xA4FC63U, 0xFE5704U, 0xB387DDU, + 0xA8BC6AU, 0xC32FB2U, 0x5A7EE5U, 0x11C44CU, 0x489797U, 0x420E62U, 0x19BD79U, 0x30E6BCU, 0x6B6407U, 0x225DDAU, + 0x398EA9U, 0x703534U, 0x0A64F7U, 0x09FA0AU, 0xD4C910U, 0xDF10E5U, 0x86833EU, 0xCDB99BU, 0xE67A40U, 0xBE631BU, + 0xB590AEU, 0xEC8B75U, 0xA73BD0U, 0x9CE08BU, 0xD5F35EU, 0x8E0AE5U, 0x061828U, 0x5D835AU, 0x5660C7U, 0x277914U, + 0x68CAE9U, 0x7190E2U, 0x3A0113U, 0x20FECCU, 0x49ED7DU, 0x127522U, 0x1B06ABU, 0x40855CU, 0x8B9E85U, 0x926FB2U, + 0xF8F56AU, 0xE186A5U, 0xAA1F14U, 0xF10CCBU, 0xF0F7BAU, 0x8F6735U, 0x867CECU, 0xDC9F1FU, 0x978402U, 0x8E54F1U, + 0x45EF3CU, 0x7CFC8FU, 0x3705D2U, 0x6C1248U, 0x64C8BDU, 0x3FF976U, 0x566243U, 0x4DA198U, 0x069B45U, 0x1F0AF6U, + 0x5851BBU, 0x00E248U, 0xAB3BD1U, 0xF2090EU, 0xF9926FU, 0xA2C3F1U, 0xEB7800U, 0xD07B9FU, 0x98A1E6U, 0xC31021U, + 0xC84BB8U, 0x91D84FU, 0x9AEC96U, 0x6337A9U, 0x288468U, 0x369FB3U, 0x774E06U, 0x6C645DU, 0x05B7A9U, 0x4E2E22U, + 0x551DFFU, 0x1CC78CU, 0x47D611U, 0x4F2DF2U, 0x343E6FU, 0xBF8514U, 0xE655C1U, 0xAD5E5AU, 0xB4EDBFU, 0xDFB4E4U, + 0xC1265DU, 0x80DD8BU, 0xDBC852U, 0xD25375U, 0x8920ACU, 0xA2BA53U, 0xFB0BC2U, 0x31401DU, 0x28D33CU, 0x63AAE3U, + 0x18381AU, 0x11238DU, 0x4AD2E4U, 0x434933U, 0x195BABU, 0x56A058U, 0x6FB105U, 0x2C5AAEU, 0x35C97BU, 0xFED9A0U, + 0xA52295U, 0x8D314EU, 0xD6ECA3U, 0x9F5E30U, 0x84456DU, 0xCFB6DEU, 0xD6AF03U, 0xBD2CE9U, 0xE556FCU, 0xEEC707U, + 0xB71CD6U, 0x382F59U, 0x43B720U, 0x02E4F7U, 0x195F4EU, 0x51CC99U, 0x0AA550U, 0x013767U, 0x786CBEU, 0x73DD01U, + 0x2AC6D1U, 0x61159EU, 0x7BA92FU, 0x92BAF4U, 0x896109U, 0xC0521AU, 0x9F9AF7U, 0x942924U, 0xC532B9U, 0xEFE3C2U, + 0xA6D807U, 0xFD0ABCU, 0xF69369U, 0xAFA033U, 0x44738EU, 0x5D694DU, 0x17C8F0U, 0x0C93A3U, 0x45207AU, 0x1EF9C5U, + 0x37EB04U, 0x6850FBU, 0x6305EAU, 0x3B9E15U, 0x782DC4U, 0x41774BU, 0x8AF633U, 0xD18DE4U, 0xD81E5DU, 0x83A69AU, + 0x8AF583U, 0xF06E7CU, 0xBB5FADU, 0xA28416U, 0xE99653U, 0xF06D88U, 0x9FEC35U, 0xC4F7E6U, 0x4C059AU, 0x1F1C19U, + 0x56EFC4U, 0x4D743FU, 0x24612AU, 0x3F9BD1U, 0x748814U, 0x2C13AFU, 0x27F276U, 0x5EE861U, 0x553B88U, 0x0E0A5FU, + 0xC791E6U, 0xD8E2B0U, 0x907A69U, 0xABE9C6U, 0xE09217U, 0xB10168U, 0xBA48F9U, 0xE3FA26U, 0x8861CFU, 0x9230D8U, + 0xDB8B21U, 0xC099B2U, 0x09644FU, 0x52F704U, 0x79AC90U, 0x201F6BU, 0x2E17BEU, 0x77C495U, 0x3CFF48U, 0x172E9BU, + 0x4E9426U, 0x0D8775U, 0x145E98U, 0x5E6D03U, 0xC5F6D6U, 0xAC242DU, 0xF70D3CU, 0xFEDED2U, 0xA5C543U, 0xAE74BCU, + 0xD62EE5U, 0x9D9D72U, 0x80029BU, 0xCB534CU, 0x90E175U, 0x19BAAAU, 0x6A3B6BU, 0x6280D4U, 0x39D385U, 0x724B7AU, + 0x6B78E2U, 0x00A321U, 0x19101CU, 0x5248CFU, 0x0ADB30U, 0x01F0A9U, 0x5A21CEU, 0xB73A17U, 0xACC880U, 0xE55179U, + 0xFE42A6U, 0xB4B987U, 0xC5AF58U, 0xCE1688U, 0x97C533U, 0x9CCE76U, 0xC73F8DU, 0x8E2510U, 0xB4B6C3U, 0x7D4FFEU, + 0x665C3DU, 0x2DC7C0U, 0x70B55BU, 0x5B2C2EU, 0x025FF5U, 0x49D470U, 0x53448AU, 0x1A3FD7U, 0x09AC64U, 0x60BDBDU, + 0x3B467AU, 0xB0D043U, 0xE98B9CU, 0xE33A2DU, 0x9A21E2U, 0xD1C3B3U, 0xCA5A0CU, 0x8709DDU, 0xDCB222U, 0xF5A3AAU, + 0xBF79DDU, 0xA44A04U, 0xEDD193U, 0x3E006AU, 0x373B21U, 0x4CF994U, 0x47C04FU, 0x1F53DAU, 0x5488A1U, 0x4DB86CU, + 0x2623DFU, 0x7D7402U, 0x70CF50U, 0x2B9EFDU, 0x232426U, 0xF8A7D3U, 0x91FEC8U, 0x8A4D39U, 0xC117F6U, 0xD0866FU, + 0x9B3D18U, 0xE36EC1U, 0xE8F576U, 0xB3C5BFU, 0xBA1629U, 0xE1BD50U, 0xA8EC8FU, 0x17763EU, 0x5D45F1U, 0x049CA0U, + 0x0F8F1FU, 0x5630C6U, 0x7DE225U, 0x26FB38U, 0x6F08CBU, 0x7D0316U, 0x34B28DU, 0x2F68E9U, 0xC47B72U, 0x9DC287U, + 0x96915CU, 0xCF0B41U, 0x85F8A2U, 0xBAE17FU, 0xF372CCU, 0xE81991U, 0xA1894AU, 0xFAF2EBU, 0xF16134U, 0x89F845U, + 0x0A8ADBU, 0x53153AU, 0x1806E5U, 0x03FF7CU, 0x6A7C0BU, 0x312692U, 0x399775U, 0x628CACU, 0x6D7FB3U, 0x34EE42U, + 0x5FF49DU, 0x56073CU, 0x8D1C67U, 0x87CDBBU, 0xDEE708U, 0xB574D5U, 0xA4ADB6U, 0xEF9E2BU, 0xF605D0U, 0xBD7545U, + 0xE6EE0EU, 0xCE39FBU, 0x950260U, 0xD8929DU, 0x43D9CEU, 0x086A47U, 0x31B3B1U, 0x7AA068U, 0x221ADFU, 0x294B86U, + 0x72F049U, 0x73E3F8U, 0x083927U, 0x418856U, 0x5AC3C9U, 0x105020U, 0xC969B7U, 0xE2BBEEU, 0xBF2019U, 0xB41181U, + 0xEFCA6AU, 0xA6FD3FU, 0xBC27A4U, 0xD53651U, 0xCE9D9AU, 0x854EA7U, 0xDC5E74U, 0xDFE5A9U, 0x26B61AU, 0x6C0D57U, + 0x77DCECU, 0x3EC639U, 0x2575C3U, 0x682CD6U, 0x13AF1DU, 0x1855ECU, 0x404473U, 0x4BDF8AU, 0x12ACDDU, 0xF93754U, + 0xE207A3U, 0xABD87AU, 0xF04B45U, 0xF03284U, 0xABB05BU, 0x80ABEBU, 0xD95AB4U, 0x92C10DU, 0x8FD2CEU, 0xC42833U, + 0xEC3920U, 0x37C2FDU, 0x7C5106U, 0x654883U, 0x2EAAF8U, 0x37B12DU, 0x5C20B6U, 0x065B42U, 0x07C909U, 0x5C12B4U, + 0x152367U, 0x2EB4FAU, 0x65CF19U, 0xFC5F40U, 0xB294FFU, 0xEBA72EU, 0xE03ED1U, 0x9B6CD0U, 0x92D70FU, 0xC944F6U, + 0x801D60U, 0x9AAE19U, 0xF1F4DEU, 0xA85547U, 0xAB4EB8U, 0x729DE9U, 0x792456U, 0x223697U, 0x4BED0CU, 0x55DE71U, + 0x1C03A2U, 0x07910FU, 0x4CAADCU, 0x356BA0U, 0x3E5033U, 0x67C3EEU, 0x2D9B05U, 0xB62810U, 0xFFF3EBU, 0xC4E03EU, + 0x8558A5U, 0xDE0B48U, 0xD5905BU, 0x8D71A2U, 0xA26A75U, 0xFBD8ECU, 0xB08982U, 0xAB1253U, 0xE2A1ECU, 0x79FB3FU, + 0x116E52U, 0x4A15C9U, 0x43861CU, 0x188FE7U, 0x537DF2U, 0x62E619U, 0x29D7C0U, 0x310C57U, 0x7A1F2EU, 0x25E5B8U, + 0xAC7451U, 0xC76F86U, 0xDE9C9FU, 0x959460U, 0xCF27B1U, 0xC6FC1EU, 0xBDEDCFU, 0xF416B0U, 0xEF0429U, 0xA49FEEU, + 0xBDEA17U, 0xFF7104U, 0x06A3F8U, 0x0D8A63U, 0x5219A6U, 0x5B62DDU, 0x00F348U, 0x6969B3U, 0x731A6EU, 0x38816DU, + 0x61D090U, 0x6A6343U, 0x33F9FEU, 0x18B8A5U, 0xC30340U, 0x8B10DAU, 0x98E80BU, 0xD1FB74U, 0xEA20F5U, 0xA5930AU, + 0xFC8E93U, 0xF75CC4U, 0xAF673DU, 0xA4E6BAU, 0xDF3D43U, 0x960F9CU, 0x0DD68DU, 0x44E572U, 0x1F7EB2U, 0x35AD09U, + 0x6C9554U, 0x6746A7U, 0x365D3AU, 0x7DFCF9U, 0x64A6C4U, 0x0B351FU, 0x118CEAU, 0x58DF61U, 0x836434U, 0x8A36CFU, + 0xF1AB5BU, 0xBA18A0U, 0xA343EDU, 0xE8C27EU, 0xF0F887U, 0xBB2B50U, 0xC03A69U, 0xC9C1A6U, 0x9A5317U, 0x9368C8U, + 0x5CB919U, 0x26A226U, 0x2F01EFU, 0x74D919U, 0x3DCA80U, 0x2631D7U, 0x6D223EU, 0x54BAA1U, 0x1E4950U, 0x47520BU, + 0x4CA79EU, 0x97BC75U, 0xBE3EA8U, 0xED479BU, 0xA4D446U, 0xBA4FF5U, 0xF13C39U, 0xE8A46AU, 0x83D7D7U, 0xDA4C0CU, + 0xD1DDF9U, 0x8AA7F2U, 0xC22427U, 0x793DDCU, 0x30CE45U, 0x2B5522U, 0x6007FBU, 0x39BE6CU, 0x32AD95U, 0x42560BU, + 0x4D426AU, 0x16D1B5U, 0x5F3A04U, 0x442BDBU, 0x2DF082U, 0xF6C225U, 0xFE59FCU, 0xA5880FU, 0xAEB312U, 0xF761C9U, + 0x9C582CU, 0x85CBB7U, 0xCE00C3U, 0xD43118U, 0x9DAB9DU, 0xEAF866U, 0xE3437BU, 0x381288U, 0x738955U, 0x6A3BF6U, + 0x2066ABU, 0x19D570U, 0x52DEC1U, 0x090E1EU, 0x00B5FFU, 0x5BE6E1U, 0x727D38U, 0x284CCFU, 0x639656U, 0xFA8531U, + 0xBD3CA8U, 0xD4EF77U, 0xCFC586U, 0x841489U, 0x9C0F78U, 0xD7BCA7U, 0x8E671EU, 0xA5774DU, 0xFE8481U, 0xF79F32U, + 0xAC0AEFU, 0x65F09CU, 0x5FF301U, 0x144ACAU, 0x0D193FU, 0x468224U, 0x13F0D1U, 0x18694AU, 0x63FA87U, 0x2B81F4U, + 0x30106DU, 0x790A9BU, 0xE2E952U, 0x8970CDU, 0xD003BCU, 0xDB9963U, 0x838AD2U, 0x88731DU, 0xD1E064U, 0xBAFFF3U, + 0xA10F2AU, 0xEC049DU, 0xBFD7D4U, 0xB7EE2BU, 0x4C7CBBU, 0x478760U, 0x1E9415U, 0x554D9EU, 0x4C7E6BU, 0x07E4B0U, + 0x3D35ADU, 0x741E4EU, 0x2F8D93U, 0x26FC20U, 0x7D667DU, 0x16B586U, 0x8B8E02U, 0xC91FD9U, 0xD0456CU, 0x9BF237U, + 0xC0EBCEU, 0xE92849U, 0xB29390U, 0xBBC3E7U, 0xE1787EU, 0xAA6B81U, 0x93B040U, 0xD8005FU, 0x411BAEU, 0x0AC870U, + 0x51F1D1U, 0x5D328EU, 0x362837U, 0x6799E0U, 0x6C4239U, 0x37711AU, 0x3EABC7U, 0x45BA3CU, 0x0D01A9U, 0x16D6F2U, + 0xDDCF17U, 0xC46D8CU, 0x8F3670U, 0xF6A723U, 0xFD5CBCU, 0xA74F5DU, 0xEAF582U, 0xF1A43BU, 0x903768U, 0x8B0CC5U, + 0xC0DC16U, 0x9957CBU, 0x1324F0U, 0x4ABD25U, 0x61AECEU, 0x38545AU, 0x73C701U, 0x68FEF4U, 0x212D6FU, 0x5B3382U, + 0x52C2D1U, 0x09494CU, 0x065ABFU, 0xDFA126U, 0x9CB149U, 0xA56A98U, 0xEE5927U, 0xF4C0F6U, 0xBD33B8U, 0xE62901U, + 0xCFB8D6U, 0x94D32FU, 0x9F40B8U, 0xC69AF1U, 0x8CAB0EU, 0x15309FU, 0x7E6360U, 0x21DA31U, 0x2848BAU, 0x733747U, + 0x72A6D4U, 0x08EDA8U, 0x435F7BU, 0x5A4CD6U, 0x119505U, 0x082658U, 0x433DE3U, 0xB8ED26U, 0xB0D6DDU, 0xEB05C8U, + 0xA2BC13U, 0xA9BEEAU, 0xD6656DU, 0xDF5614U, 0x848F82U, 0xC41C5BU, 0xDF26A4U, 0x94F7A5U, 0xADCC5AU, 0x665B8BU, + 0x3F1234U, 0x34A0EDU, 0x6E7BAAU, 0x076813U, 0x1CD1C4U, 0x55833DU, 0x4E1836U, 0x03A9E2U, 0x58F219U, 0x72418CU, + 0x2B09F7U, 0xA89A72U, 0xF1A1A9U, 0xBA7254U, 0x81EA47U, 0xC899BAU, 0xD20279U, 0x9B13C4U, 0xC0E09FU, 0xCB7E4BU, + 0xB25FF0U, 0xF98431U, 0xE4974EU, 0x2E6CD7U, 0x35FC00U, 0x5CE7A9U, 0x07147EU, 0x060D07U, 0x5D9F98U, 0x56E449U, + 0x0E65A6U, 0x659EB7U, 0x7C8D49U, 0x371790U, 0x6C6623U, 0xE5FD6EU, 0x9E6EBDU, 0x921600U, 0xC985D3U, 0x82DAEEU, + 0x9B7B25U, 0xD0E0F0U, 0xE1924BU, 0xAA091EU, 0xF158F5U, 0xF9E369U, 0x22F1BAU, 0x4B28C7U, 0x509B54U, 0x1B80BDU, + 0x024162U, 0x497B53U, 0x01A88CU, 0x3E1B5DU, 0x7502F2U, 0x6CD12BU, 0x27EB1CU, 0x7E7AC5U, 0xDDA113U, 0x8596BAU, + 0xCE5EEDU, 0xD54D14U, 0x9CF68BU, 0x87A54AU, 0xEE1C31U, 0xB58EA4U, 0xBFD55FU, 0xE66482U, 0xE93FA1U, 0x90AD7CU, + 0x5B04EFU, 0x405713U, 0x09CC48U, 0x13BFEDU, 0x522736U, 0x2914E3U, 0x22CFD8U, 0x7B5E05U, 0x3061E6U, 0x29B37FU, + 0x43BAA8U, 0x5849D1U, 0x91D25EU, 0xCEE0AFU, 0xC73971U, 0x9C2A40U, 0xB7919FU, 0xEF401EU, 0xA452E1U, 0xB5B9B8U, + 0xFEA80FU, 0x8533D6U, 0x8C4115U, 0xD7DA28U, 0x5F6BF3U, 0x043006U, 0x4FA39DU, 0x76DBD9U, 0x394C22U, 0x20C7BFU, + 0x6BB64CU, 0x312C41U, 0x187FB2U, 0x43C46FU, 0x0A55F4U, 0x192E81U, 0xD2BC4AU, 0xCBA5FBU, 0xA15624U, 0xF85DFDU, + 0xF38ECBU, 0xBA3602U, 0xA125F5U, 0xCEFE6CU, 0x97CF3BU, 0x9D55C2U, 0xC4A64DU, 0x4FBFBCU, 0x1468A3U, 0x7D4352U, + 0x6ED19DU, 0x270804U, 0x7D3B76U, 0x76A0ABU, 0x0FF018U, 0x0443D5U, 0x5D188EU, 0x16A93BU, 0x0932E0U, 0xC07015U, + 0xFACB1EU, 0xB39AC3U, 0xE80170U, 0xE3B3ADU, 0xBAEA5EU, 0xD17956U, 0xC042A9U, 0x8A9378U, 0x912DE7U, 0xD86E86U, + 0x83F559U, 0x2AC4E8U, 0x711F37U, 0x7A0D6EU, 0x26B4C9U, 0x6D6710U, 0x547CE7U, 0x1F8CFEU, 0x449720U, 0x4D3483U, + 0x16EF5AU, 0x1EFE2DU, 0x6D44B4U, 0xA6174BU, 0xBF8E8AU, 0xF4FD95U, 0xED6764U, 0x86D6BBU, 0xDC8912U, 0xD10A45U, + 0x8A799CU, 0x83E12AU, 0xF872F3U, 0xB10954U, 0xAA980DU, 0x6083D6U, 0x397163U, 0x3AE8B8U, 0x439BDDU, 0x481046U, + 0x1302BBU, 0x5AFB68U, 0x50E875U, 0x297396U, 0x26824AU, 0x7D98F1U, 0x344BA4U, 0xAF726FU, 0xE6F5DAU, 0x9C0F01U, + 0x971C38U, 0xCE85EFU, 0xC5F626U, 0x946D91U, 0xFFBDC8U, 0xE48637U, 0xAC15A6U, 0xB74C48U, 0x7EEE99U, 0x21B586U, + 0x0A0677U, 0x539FA8U, 0x18CC01U, 0x007652U, 0x4B67CFU, 0x70B43CU, 0x390FF1U, 0x625ECAU, 0x6BD01FU, 0x38E3C4U, + 0xB23870U, 0xCB893BU, 0x8093C6U, 0x994055U, 0xD679A8U, 0x8DAAFBU, 0xA4B176U, 0xFE018DU, 0xF7CA5CU, 0xACD963U, + 0xE762B2U, 0xFE323DU, 0x1589C4U, 0x0C5A92U, 0x4F432BU, 0x17F0ECU, 0x1CAA35U, 0x673B82U, 0x6E54DBU, 0x31C724U, + 0x785CA5U, 0x632C5AU, 0x29B70BU, 0x508490U, 0xDB5D6DU, 0x82CFEEU, 0x89B492U, 0xD22541U, 0xBB2EDCU, 0xA1DD27U, + 0xE04F62U, 0xFB56D9U, 0xB0A50CU, 0xF9BED7U, 0xC24EFAU, 0x8F5529U, 0x55C6D0U, 0x5E3B47U, 0x07383FU, 0x2CA2F0U, + 0x75D161U, 0x3E489EU, 0x25BB0FU, 0x6DA170U, 0x7630B1U, 0x174B2EU, 0x4CD8D7U, 0x470180U, 0x9E2339U, 0xD5B8FEU, + 0xE9EB27U, 0xA2521DU, 0xB941C8U, 0xF0BB23U, 0xAB2AB6U, 0xA271CDU, 0xD9C250U, 0xD3DC83U, 0x8A1D6EU, 0x41A67DU, + 0x58B580U, 0x3B6C5BU, 0x205ECEU, 0x6985A5U, 0x333471U, 0x3E27CAU, 0x65FD13U, 0x0CCE44U, 0x1747EDU, 0x5C143AU, + 0x45AF83U, 0x8F7F54U, 0xF6643DU, 0xFDD7A2U, 0xA68E73U, 0xAF3D8CU, 0xF4E79DU, 0xB5E073U, 0x8F59AAU, 0xC40A3DU, + 0x999044U, 0x922197U, 0xCB7A3AU, 0x60E9E1U, 0x3B90B4U, 0x73020FU, 0x6839DAU, 0x21FA71U, 0x3A632CU, 0x5151DFU, + 0x088A43U, 0x039B80U, 0x4260FDU, 0x18F36EU, 0x33EB97U, 0xEE1848U, 0xE503C9U, 0xBCA4B6U, 0xF7FC67U, 0xEC6FD8U, + 0x849C01U, 0x9F8506U, 0xD616FFU, 0x8D6C29U, 0x86FD90U, 0xFF26CFU, 0xF4150EU, 0x2C9EB1U, 0x6FEE60U, 0x74751BU, + 0x39E696U, 0x429F65U, 0x4B0DB8U, 0x1056ABU, 0x1AE756U, 0x43FC9DU, 0x282F29U, 0x318172U, 0x7A90E7U, 0xE36B1CU, + 0xA878D9U, 0xF2A0E2U, 0xDB133FU, 0x8008ECU, 0xC1D955U, 0xDAE292U, 0x9570EBU, 0xAC9B74U, 0xE68A85U, 0xBF514BU, + 0x34635AU, 0x6FFAA5U, 0x66A93CU, 0x1D12CBU, 0x54C382U, 0x4ED915U, 0x056AECU, 0x5C2D37U, 0x779402U, 0x2607C9U, + 0x2D5D1CU, 0x72ECA6U, 0xBAB7FBU, 0xA12408U, 0xC89C85U, 0xD3CF76U, 0x98542BU, 0xC177B8U, 0xCAAE45U, 0xB29CCEU, + 0xB9473BU, 0xE2D660U, 0xABEDF1U, 0xA03F1EU, 0x7926C4U, 0x1A95F1U, 0x044E2AU, 0x4D49FFU, 0x56B154U, 0x1FA209U, + 0x6419FAU, 0x6F4867U, 0x36D394U, 0x3C2199U, 0x653842U, 0x2EABB7U, 0x95C02CU, 0xDC525CU, 0x87CB93U, 0x8EB80AU, + 0xD423FDU, 0xF152A4U, 0xAAC003U, 0xE15BDAU, 0xF82A0DU, 0xB3B134U, 0xAAB3EBU, 0xC14C0AU, 0x1B5D95U, 0x128664U, + 0x49357AU, 0x002DA3U, 0x3BDE40U, 0x70C5DDU, 0x6954AEU, 0x23AE73U, 0x76ADE8U, 0x7D760DU, 0x064756U, 0x0FDCE3U, + 0xD40E38U, 0x9D37F5U, 0x87E4C6U, 0xECDF1AU, 0xB54EA9U, 0xBE1470U, 0xE7B71FU, 0xEC288EU, 0xB77951U, 0xDFC3A0U, + 0xC490BFU, 0x89095EU, 0x1ABA81U, 0x51E118U, 0x2853EFU, 0x234AB6U, 0x7B8910U, 0x703AC9U, 0x2B6216U, 0x62F127U, + 0x59CAECU, 0x101B59U, 0x4B2082U, 0xC1F2FFU, 0x88696CU, 0xB358A1U, 0xFA9752U, 0xAD844FU, 0xA63CB4U, 0xFFEF60U, + 0xF5F4EBU, 0x8C059EU, 0xC71F45U, 0xDCACF0U, 0x95772BU, 0x4E6622U, 0x67CDD5U, 0x3D9F0CU, 0x3406BBU, 0x6F75F2U, + 0x24EE0DU, 0x3D7E9CU, 0x520542U, 0x4396B3U, 0x09ADBCU, 0x527C45U, 0x5BF292U, 0xA0810BU, 0xE91878U, 0xF20BB5U, + 0xB9F10EU, 0xA1E0DBU, 0xEA33C0U, 0x938835U, 0x989BFEU, 0xC36342U, 0xCA6011U, 0x95FBECU, 0xFD0A6FU, 0x6E11B2U, + 0x25C3C1U, 0x3CFA5CU, 0x7769A7U, 0x0EB266U, 0x058079U, 0x5E1988U, 0x167E17U, 0x0DE5EFU, 0x44B428U, 0x7F0E31U, + 0x349DC6U, 0xEDC41FU, 0xE277A0U, 0xBA6DE1U, 0x99BE1EU, 0xC2178FU, 0x8B4450U, 0x90FF39U, 0xD9EFAAU, 0x823457U, + 0xA88785U, 0xF1DE98U, 0xFA4D73U, 0x3377E6U, 0x68A41DU, 0x43AD48U, 0x1A1AD3U, 0x14C836U, 0x4DF1EDU, 0x0622D0U, + 0x173903U, 0x7C88FEU, 0x27527DU, 0x2E41A5U, 0xF4FADAU, 0xFDBB4BU, 0x8601A4U, 0xCDD235U, 0xD4CB6AU, 0x9F7893U, + 0x862304U, 0xCCB3EDU, 0xB388BAU, 0xBA5B23U, 0xE1C0DCU, 0xE8A00DU, 0x3B3F27U, 0x500CF2U, 0x48D509U, 0x034694U, + 0x5A3CC7U, 0x51AD6AU, 0x0AB6B9U, 0x234544U, 0x785E57U, 0x30DE9AU, 0x2B2561U, 0x6036F4U, 0xDDCF8FU, 0x96DD5BU, + 0xCF46D0U, 0xCCB729U, 0x96A4FEU, 0xDF3FC7U, 0xE44D10U, 0xADC4F9U, 0xB61366U, 0xFD2837U, 0xA4B888U, 0x8EC359U, + 0x5750B6U, 0x5C49AFU, 0x07BA79U, 0x4A2080U, 0x517307U, 0x38DA7EU, 0x62C9ADU, 0x611230U, 0x38A2CBU, 0x33F98EU, + 0x4A4A35U, 0x0153E0U, 0x98815BU, 0xD23A06U, 0xD929C5U, 0x80E079U, 0xEBD7AAU, 0xF20D57U, 0xBD1C4CU, 0xE6A78DU, + 0xEF7472U, 0x954CE3U, 0x9CDF9CU, 0xCF8455U, 0x0437C2U, 0x1DFE3BU, 0x56EC6CU, 0x6F57D5U, 0x25061BU, 0x7E95AAU, + 0x772FF5U, 0x2C7C24U, 0x05C59BU, 0x5A965AU, 0x111D21U, 0x892DBCU, 0xC2F26FU, 0x9B6192U, 0xB81891U, 0xE38B6EU, + 0xEA91B6U, 0xB16221U, 0xF9FB48U, 0xC2C8DFU, 0x890226U, 0x9013F9U, 0xDBE848U, 0x02FB07U, 0x0D62D6U, 0x77906DU, + 0x3E8BB0U, 0x2538C3U, 0x6C614EU, 0x77F39CU, 0x141861U, 0x4D0D7AU, 0x47968FU, 0x1EE544U, 0x157DD1U, 0xCEEEAAU, + 0xA7953FU, 0xBC06D4U, 0xF57E09U, 0xABED3AU, 0xA2EEE3U, 0xD91734U, 0xD2849CU, 0x8BDEC3U, 0xC06F32U, 0xD174ADU, + 0x9BA77CU, 0x201C93U, 0x690C8AU, 0x22F77DU, 0x3BF4E4U, 0x702933U, 0x4B9B5AU, 0x0380C1U, 0x585134U, 0x556AEEU, + 0x0EF9CBU, 0x45A310U, 0x7C12CDU, 0xB7C97EU, 0xAFEA23U, 0xEC72C0U, 0xB7215DU, 0x9E9A8EU, 0xC50BF3U, 0xCC5068U, + 0x97E28DU, 0xDDB916U, 0xC42846U, 0xAF93B9U, 0xF2D020U, 0x796CC7U, 0x223F9EU, 0x2BA429U, 0x5095F0U, 0x18473FU, + 0x03DC8EU, 0x40EFD1U, 0x593620U, 0x1225BFU, 0x6BCF76U, 0x605E40U, 0xBA6599U, 0xF3B66AU, 0xE8AEF7U, 0x851DBCU, + 0x9EC649U, 0xD5D5D2U, 0x8C2C27U, 0x863E7CU, 0xDFB5D9U, 0xF4C002U, 0xA55BDFU, 0xEEE9ECU, 0x75B020U, 0x3C23D3U, + 0x06584AU, 0x4FCB15U, 0x1453A4U, 0x1F306BU, 0x42AB9AU, 0x09FA05U, 0x30415CU, 0x7A53ABU, 0x61AA22U, 0x2839D5U, + 0xF3228CU, 0xDAD032U, 0x89C9E3U, 0x820A2CU, 0xCAB91DU, 0x91A4C6U, 0x985673U, 0xE34DB8U, 0xA8DCE5U, 0xB52756U, + 0xFE358BU, 0xE6EE78U, 0x0DDF75U, 0x5654AFU, 0x5F075AU, 0x04BFC1U, 0x0D2CB4U, 0x56577FU, 0x34C6C2U, 0x2D9D11U, + 0x662F48U, 0x3FB4FFU, 0x34E536U, 0x4F4E89U, 0xC61C58U, 0x988107U, 0xD132A7U, 0xCA6978U, 0x81D881U, 0xB8C296U, + 0xF3114FU, 0xAA2AA8U, 0xA0FB31U, 0xF37142U, 0xDA429FU, 0x819B24U, 0x4888E1U, 0x5333FAU, 0x1AE30FU, 0x40D0D5U, + 0x6F0B68U, 0x36182BU, 0x3DA0F6U, 0x646345U, 0x2F7898U, 0x14CDF3U, 0x5C9666U, 0xC704BDU, 0x8E7F4CU, 0xDDEED3U, + 0xD655BAU, 0xAF062DU, 0xE49EF5U, 0xFDFD02U, 0xB7661BU, 0xA8F7F4U, 0xC18D25U, 0x9A1E9AU, 0x9305CBU, 0x48F414U, + 0x43EFB5U, 0x1B1D6AU, 0x708413U, 0x698780U, 0x2A7C4DU, 0x6168BFU, 0x78F3A2U, 0x130059U, 0x0B1BCCU, 0x40CA07U, + 0x1FF072U, 0x9663E9U, 0xCD9A14U, 0xE499C7U, 0xBF0AEAU, 0xF57239U, 0xECE1E4U, 0xA7BA5FU, 0xDE098FU, 0xD591E0U, + 0x86E271U, 0x8F79AEU, 0xD52817U, 0x1C8350U, 0x2711A9U, 0x684C3EU, 0x71FFE7U, 0x3AE490U, 0x633619U, 0x498FE6U, + 0x10DC37U, 0x1B670DU, 0x4066D8U, 0x09BC13U, 0x328FAEU, 0xFB16FDU, 0xA9C500U, 0xA2FF93U, 0xFB2C5EU, 0xF03565U, + 0x8D86B0U, 0xC65D4BU, 0xDD7DCEU, 0x95E615U, 0x8EB169U, 0xE708FAU, 0xBCDB03U, 0x37C1C4U, 0x6E72FDU, 0x25232AU, + 0x3DB8F3U, 0x5ECA4CU, 0x45430DU, 0x0CF0B2U, 0x57AB61U, 0x5E3ABCU, 0x210087U, 0x2BD343U, 0xF248B8U, 0xB9392DU, + 0xA0A376U, 0xEBB09FU, 0x905908U, 0x99CAF1U, 0xD3F5A6U, 0xCA251FU, 0x813ED0U, 0xF2CD01U, 0xFBD63EU, 0xA046EEU, + 0x29BD51U, 0x76AE98U, 0x3C274FU, 0x055476U, 0x4ECEA5U, 0x573F58U, 0x1C24DBU, 0x47B786U, 0x6ECC7DU, 0x345CE8U, + 0x7D0703U, 0x66B456U, 0xAF3DECU, 0xBC6F31U, 0xD7D0A2U, 0x8EC1CFU, 0x80321CU, 0xD9A9A1U, 0xD2FB7AU, 0xA9422BU, + 0xE05184U, 0xFB8A55U, 0xB22AAAU, 0xE831B3U, 0x63E264U, 0x1ADB8CU, 0x11081BU, 0x4832E2U, 0x0BA1BDU, 0x10781CU, + 0x784BC3U, 0x679052U, 0x2E902DU, 0x752BFCU, 0x7EFC57U, 0x27C58AU, 0xCC57D9U, 0xD40C64U, 0x9FBFB6U, 0x84665BU, + 0xCD7540U, 0xB6CF95U, 0xBF8E6EU, 0xE415FBU, 0xE62690U, 0xBFFE0DU, 0xF04DFEU, 0xC91623U, 0x028520U, 0x19BCD9U, + 0x506E06U, 0x0AEDB6U, 0x23D4E9U, 0x780728U, 0x331997U, 0x3AE84EU, 0x6173B9U, 0x6840A0U, 0x129B67U, 0x598B9EU, + 0xC87009U, 0x877370U, 0x9EEAEBU, 0xF5190EU, 0xAC03D4U, 0xA690C1U, 0xFDE93AU, 0xB47AF7U, 0xAF8044U, 0xC69119U, + 0xDD0ACAU, 0x166977U, 0x4EF0BCU, 0x45E2C9U, 0x3C1D52U, 0x3F8E87U, 0x64D73CU, 0x296574U, 0x327E83U, 0x7A8F1AU, + 0x0114EDU, 0x0A1734U, 0x53EF3BU, 0xD8FCCAU, 0x812715U, 0xEA94A4U, 0xF185EBU, 0xB95F3AU, 0xA26C85U, 0xEBB55CU, + 0xB0862AU, 0x930CA3U, 0xCEDD70U, 0x85E6CDU, 0x9D7196U, 0x562A63U, 0x6D9AE8U, 0x24411DU, 0x7F5246U, 0x76EBDBU, + 0x2DB928U, 0x2712E5U, 0x5E83D7U, 0x15D80AU, 0x0C6BD1U, 0x473170U, 0x54A02FU, 0xB99B9EU, 0xE34841U, 0xEAD090U, + 0xB1E33FU, 0xFA2866U, 0xE31991U, 0x88C208U, 0x8154FFU, 0xDB6FB7U, 0x92BE28U, 0x89A5D9U, 0xE25606U, 0x7BCE37U, + 0x30FDFCU, 0x693E21U, 0x6F2792U, 0x3495CFU, 0x5DCE6CU, 0x465DB1U, 0x0FA46AU, 0x14B75FU, 0x5F2D85U, 0x275C60U, + 0x2CC7FBU, 0xF56486U, 0xFE3C55U, 0xA5ABC8U, 0xECD02BU, 0xD743F2U, 0x9FDAC5U, 0xC0A81CU, 0xC33383U, 0x9A2262U, + 0xB1D93DU, 0xE8DA8DU, 0x232252U, 0x39B19BU, 0x70AAACU, 0x6B5975U, 0x024182U, 0x59921BU, 0x522974U, 0x0B38A5U, + 0x01F33EU, 0x78C1CBU, 0x3758D0U, 0x242F3DU, 0xEDB4EFU, 0xB66672U, 0xBF5701U, 0xC5CCDCU, 0xCE9F77U, 0x9726A2U, + 0xDCA4F9U, 0xC5FF4CU, 0xAE4E87U, 0xB51576U, 0xFDA7E9U, 0xA63C90U, 0x2F6D47U, 0x74D6CFU, 0x1BC538U, 0x0A1D21U, + 0x413EDEU, 0x58E50FU, 0x127480U, 0x6B4BF1U, 0x60992EU, 0x2BA297U, 0x327340U, 0x79E809U, 0xA0DABAU, 0x8A0367U, + 0xD1009DU, 0x98BB98U, 0x836A63U, 0xCE70F6U, 0xF5832DU, 0xB69850U, 0xEE29D3U, 0xE5732EU, 0xBCE0FDU, 0xB75962U, + 0x4C1A13U, 0x0589DCU, 0x1EF165U, 0x5666B7U, 0x4DFDEAU, 0x268E59U, 0x7F1794U, 0x74256FU, 0x29FE7AU, 0x626F81U, + 0x700404U, 0x1996DFU, 0x828FA2U, 0xCB7C31U, 0x9067CCU, 0x9B940EU, 0xE20C17U, 0xE81FE8U, 0xB1E439U, 0xFAF586U, + 0xE17FC7U, 0xA88C18U, 0xD397A1U, 0x5E4276U, 0x04794FU, 0x0FEB88U, 0x5E9271U, 0x3501E6U, 0x2C9A3EU, 0x67EA51U, + 0x7C61C0U, 0x34323BU, 0x6F89EEU, 0x4618F5U, 0x1D4218U, 0xD4F1CBU, 0xCFE876U, 0x841B25U, 0xB888F8U, 0xF3D053U, + 0xAA7386U, 0xA168BCU, 0xF2BB69U, 0xFB8792U, 0x80541FU, 0xC8CF4CU, 0xC3FEB5U, 0x182522U, 0x1117CBU, 0x6A9E9CU, + 0x234D05U, 0x3876FAU, 0x72A42BU, 0x2FBD84U, 0x240ED4U, 0x5DD54BU, 0x56C4B2U, 0x057E65U, 0x4C2D4CU, 0xD7849BU, + 0xBFD762U, 0xA44DF9U, 0xEFFEACU, 0xB6A347U, 0xBD30D2U, 0xC40B29U, 0x8FDB74U, 0x9540E6U, 0xDC330BU, 0xC3BAD8U, + 0x8A89E5U, 0x71533EU, 0x7AC0BBU, 0x2BB140U, 0x212A11U, 0x7838AEU, 0x13C17FU, 0x08D290U, 0x414989U, 0x1AA85EU, + 0x13B2A6U, 0x494131U, 0x625AD8U, 0xBBCF87U, 0xF43516U, 0xED26E9U, 0xA6BF78U, 0x9FCC17U, 0xDD57CEU, 0x86B75DU, + 0x8FACA0U, 0xD43FE3U, 0x9D445EU, 0xA6D584U, 0x6DCF71U, 0x753C6AU, 0x3EA5AFU, 0x67F654U, 0x4C4CC9U, 0x134DBAU, + 0x1AB667U, 0x4125E4U, 0x097E19U, 0x12CA0AU, 0x71D1F3U, 0x78022CU, 0xA33BACU, 0xEAA953U, 0xF17A02U, 0x9B53BDU, + 0xC28074U, 0xC9BB83U, 0x90299AU, 0x9BF04DU, 0xC4C3B4U, 0xAD583BU, 0xB7084AU, 0xFEB3D1U, 0x656004U, 0x2E69FEU, + 0x5FDAF3U, 0x548000U, 0x0D37DDU, 0x472E4EU, 0x5CFD33U, 0x1546F8U, 0x2E164DU, 0x678D96U, 0x3CBEE3U, 0x337768U, + 0xEBE5BDU, 0xC09E47U, 0x990D5EU, 0xD234A9U, 0xC9E720U, 0x887DF7U, 0xD34C0EU, 0xFB9711U, 0xA084E0U, 0xA96C2FU, + 0xF27FBEU, 0xB9E4C1U, 0x001318U, 0x4B02AFU, 0x56B867U, 0x1CEB30U, 0x477289U, 0x4E8152U, 0x358AF7U, 0x3C1AACU, + 0x676159U, 0x24F2C2U, 0x3C6917U, 0x57197CU, 0x8E82E1U, 0x85F112U, 0xDE68CFU, 0xD77BDDU, 0x8C9130U, 0xE400EBU, + 0xFB5B5EU, 0xB0E885U, 0xA9F6D4U, 0xE2277BU, 0x9B9CA2U, 0x108F15U, 0x5A764CU, 0x0B64ABU, 0x00AF32U, 0x599CEDU, + 0x72059DU, 0x29D702U, 0x60ECE3U, 0x7A7D3CU, 0x330625U, 0x0C95D6U, 0x474D0BU, 0x9E7EA8U, 0x95FDF5U, 0xCCA666U, + 0x86179BU, 0xBD8D40U, 0xFCDA65U, 0xE763BFU, 0xAE304AU, 0xF58AD1U, 0xDE9B9CU, 0x86406FU, 0x8DE3F2U, 0x54B801U, + 0x1B28D8U, 0x0213D7U, 0x69C026U, 0x7259B9U, 0x3A6B48U, 0x61B017U, 0x68819FU, 0x135A68U, 0x50D9B1U, 0x49E386U, + 0x02325FU, 0x9A29B0U, 0xD19E23U, 0xAAC77EU, 0xA3558DU, 0xFCAE04U, 0xF5BFF3U, 0xAE04AAU, 0xC44635U, 0xDDDDC5U, + 0x966C4AU, 0xCF373BU, 0xC4A4E4U, 0x1FD47DU, 0x3E4F8AU, 0x64DC43U, 0x2DA574U, 0x3637ADU, 0x7D5C56U, 0x44CFC3U, + 0x0BD688U, 0x52257DU, 0x59BBE7U, 0x03AA1AU, 0x4A5119U, 0xF142C4U, 0xB89A37U, 0xB329AAU, 0xE822D1U, 0xA9D114U, + 0x93C88FU, 0xDA5A7AU, 0xC1A121U, 0x8AB090U, 0xD36B5EU, 0xDC58A7U, 0xA5C0B0U, 0x2F8369U, 0x74389EU, 0x3DAB17U, + 0x26D368U, 0x4F40F9U, 0x141F26U, 0x1DAEC7U, 0x473558U, 0x446721U, 0x1DDEF6U, 0x769D6EU, 0xEF0E8DU, 0xA434D0U, + 0xBFE56BU, 0xF35EBEU, 0x884D45U, 0x819440U, 0xDAA69BU, 0x917D26U, 0x88ECF5U, 0xC3C788U, 0xFB150BU, 0xB02ED6U, + 0x63BF24U, 0x6A6439U, 0x3153D2U, 0x188B03U, 0x43989CU, 0x0923EDU, 0x147232U, 0x5FF99BU, 0x060B4CU, 0x2D10B5U, + 0x76A1A2U, 0x7FFA7BU, 0xA568D4U, 0xECD104U, 0xD7827BU, 0x9C19EAU, 0x8D6831U, 0xC6E2DCU, 0x9F71CFU, 0x950A32U, + 0xEE9BE1U, 0xA3A16CU, 0xB8761FU, 0xF16FC2U, 0x6A9C79U, 0x010FACU, 0x491786U, 0x52F453U, 0x1BEF88U, 0x007C35U, + 0x4B8566U, 0x32978FU, 0x316C18U, 0x697DC1U, 0x62E6BEU, 0x3B142FU, 0xD40DC0U, 0xCFCE11U, 0x86F58EU, 0xDD66F6U, + 0xD53E31U, 0x8E8988U, 0xA5125FU, 0xFC6366U, 0xB7F9B9U, 0xAEAA78U, 0xE513C3U, 0xD69096U, 0x1ECB6DU, 0x4579F8U, + 0x4C6033U, 0x13B34EU, 0x5888DCU, 0x615821U, 0x2AE3F2U, 0x32F0FFU, 0x792B04U, 0x220A91U, 0x0B906AU, 0xD0432BU, + 0xD97294U, 0x82AD4DU, 0xC8BE9AU, 0xD90623U, 0xB2D564U, 0xEBEE9CU, 0xE02D03U, 0xBF35D2U, 0xB686ADU, 0xCCDD2CU, + 0x854CD3U, 0x1EF70AU, 0x55A51DU, 0x4C1CE4U, 0x074F67U, 0x7ED49AU, 0x7476C9U, 0x2F2F75U, 0x26BCBEU, 0x75876BU, + 0x1C5650U, 0x07C885U, 0x48BB7EU, 0x9022F3U, 0x9B11A0U, 0xC2CA5DU, 0xE9C8CEU, 0xA07133U, 0xFBA238U, 0xF0B9E9U, + 0xA84917U, 0x835286U, 0xDAC179U, 0x913830U, 0x822B87U, 0x4BD95EU, 0x30D289U, 0x3C43A0U, 0x67B87FU, 0x6CAA8EU, + 0x353311U, 0x7E4440U, 0x47DFABU, 0x0C8E3FU, 0x1634C4U, 0x5FA7D9U, 0x04CC0AU, 0xAD5DE7U, 0xF64774U, 0xBDB409U, + 0xAC2DD2U, 0xE67E67U, 0xFBC5ACU, 0x90D5F9U, 0xCB0E42U, 0xC23D9FU, 0x99B67DU, 0x9047E4U, 0xEA5DB3U, 0x218E0AU, + 0x38B7D5U, 0x732034U, 0x6AFA2BU, 0x21CBFAU, 0x5A1005U, 0x5B039CU, 0x01A8FBU, 0x4C7822U, 0x574395U, 0x3CD04DU, + 0xA5811AU, 0xEE3BB3U, 0xB76868U, 0xBDF19DU, 0xE64286U, 0xCF1943U, 0x949BF8U, 0xDDA225U, 0xC67156U, 0x8FCACBU, + 0xF59B08U, 0xF605F5U, 0x2B36EFU, 0x20EF1AU, 0x797CC1U, 0x324664U, 0x1985BFU, 0x419CE4U, 0x4A6F51U, 0x13748AU, + 0x58C42FU, 0x631F74U, 0x2A0CA1U, 0x71F51AU, 0xF9E7D7U, 0xA27CA5U, 0xA99F38U, 0xD886EBU, 0x973516U, 0x8E6F1DU, + 0xC5FEECU, 0xDF0133U, 0xB61282U, 0xED8ADDU, 0xE4F954U, 0xBF7AA3U, 0x74617AU, 0x6D904DU, 0x070A95U, 0x1E795AU, + 0x55E0EBU, 0x0EF334U, 0x0F0845U, 0x7098CAU, 0x798313U, 0x2360E0U, 0x687BFDU, 0x71AB0EU, 0xBA10C3U, 0x830370U, + 0xC8FA2DU, 0x93EDB7U, 0x9B3742U, 0xC00689U, 0xA99DBCU, 0xB25E67U, 0xF964BAU, 0xE0F509U, 0xA7AE44U, 0xFF1DB7U, + 0x54C42EU, 0x0DF6F1U, 0x066D90U, 0x5D3C0EU, 0x1487FFU, 0x2F8460U, 0x675E19U, 0x3CEFDEU, 0x37B447U, 0x6E27B0U, + 0x651369U, 0x9CC856U, 0xD77B97U, 0xC9604CU, 0x88B1F9U, 0x939BA2U, 0xFA4856U, 0xB1D1DDU, 0xAAE200U, 0xE33873U, + 0xB829EEU, 0xB0D20DU, 0xCBC190U, 0x407AEBU, 0x19AA3EU, 0x52A1A5U, 0x4B1240U, 0x204B1BU, 0x3ED9A2U, 0x7F2274U, + 0x2437ADU, 0x2DAC8AU, 0x76DF53U, 0x5D45ACU, 0x04F43DU, 0xCEBFE2U, 0xD72CC3U, 0x9C551CU, 0xE7C7E5U, 0xEEDC72U, + 0xB52D1BU, 0xBCB6CCU, 0xE6A454U, 0xA95FA7U, 0x904EFAU, 0xD3A551U, 0xCA3684U, 0x01265FU, 0x5ADD6AU, 0x72CEB1U, + 0x29135CU, 0x60A1CFU, 0x7BBA92U, 0x304921U, 0x2950FCU, 0x42D316U, 0x1AA903U, 0x1138F8U, 0x48E329U, 0xC7D0A6U, + 0xBC48DFU, 0xFD1B08U, 0xE6A0B1U, 0xAE3366U, 0xF55AAFU, 0xFEC898U, 0x879341U, 0x8C22FEU, 0xD5392EU, 0x9EEA61U, + 0x8456D0U, 0x6D450BU, 0x769EF6U, 0x3FADE5U, 0x606508U, 0x6BD6DBU, 0x3ACD46U, 0x101C3DU, 0x5927F8U, 0x02F543U, + 0x096C96U, 0x505FCCU, 0xBB8C71U, 0xA296B2U, 0xE8370FU, 0xF36C5CU, 0xBADF85U, 0xE1063AU, 0xC814FBU, 0x97AF04U, + 0x9CFA15U, 0xC461EAU, 0x87D23BU, 0xBE88B4U, 0x7509CCU, 0x2E721BU, 0x27E1A2U, 0x7C5965U, 0x750A7CU, 0x0F9183U, + 0x44A052U, 0x5D7BE9U, 0x1669ACU, 0x0F9277U, 0x6013CAU, 0x3B0819U, 0xB3FA65U, 0xE0E3E6U, 0xA9103BU, 0xB28BC0U, + 0xDB9ED5U, 0xC0642EU, 0x8B77EBU, 0xD3EC50U, 0xD80D89U, 0xA1179EU, 0xAAC477U, 0xF1F5A0U, 0x386E19U, 0x271D4FU, + 0x6F8596U, 0x541639U, 0x1F6DE8U, 0x4EFE97U, 0x45B706U, 0x1C05D9U, 0x779E30U, 0x6DCF27U, 0x2474DEU, 0x3F664DU, + 0xF69BB0U, 0xAD08FBU, 0x86536FU, 0xDFE094U, 0xD1E841U, 0x883B6AU, 0xC300B7U, 0xE8D164U, 0xB16BD9U, 0xF2788AU, + 0xEBA167U, 0xA192FCU, 0x3A0929U, 0x53DBD2U, 0x08F2C3U, 0x01212DU, 0x5A3ABCU, 0x518B43U, 0x29D11AU, 0x62628DU, + 0x7FFD64U, 0x34ACB3U, 0x6F1E8AU, 0xE64555U, 0x95C494U, 0x9D7F2BU, 0xC62C7AU, 0x8DB485U, 0x94871DU, 0xFF5CDEU, + 0xE6EFE3U, 0xADB730U, 0xF524CFU, 0xFE0F56U, 0xA5DE31U, 0x48C5E8U, 0x53377FU, 0x1AAE86U, 0x01BD59U, 0x4B4678U, + 0x3A50A7U, 0x31E977U, 0x683ACCU, 0x633189U, 0x38C072U, 0x71DAEFU, 0x4B493CU, 0x82B001U, 0x99A3C2U, 0xD2383FU, + 0x8F4AA4U, 0xA4D3D1U, 0xFDA00AU, 0xB62B8FU, 0xACBB75U, 0xE5C028U, 0xF6539BU, 0x9F4242U, 0xC4B985U, 0x4F2FBCU, + 0x167463U, 0x1CC5D2U, 0x65DE1DU, 0x2E3C4CU, 0x35A5F3U, 0x78F622U, 0x234DDDU, 0x0A5C55U, 0x408622U, 0x5BB5FBU, + 0x122E6CU, 0xC1FF95U, 0xC8C4DEU, 0xB3066BU, 0xB83FB0U, 0xE0AC25U, 0xAB775EU, 0xB24793U, 0xD9DC20U, 0x828BFDU, + 0x8F30AFU, 0xD46102U, 0xDCDBD9U, 0x07582CU, 0x6E0137U, 0x75B2C6U, 0x3EE809U, 0x2F7990U, 0x64C2E7U, 0x1C913EU, + 0x170A89U, 0x4C3A40U, 0x45E9D6U, 0x1E42AFU, 0x571370U, 0xE889C1U, 0xA2BA0EU, 0xFB635FU, 0xF070E0U, 0xA9CF39U, + 0x821DDAU, 0xD904C7U, 0x90F734U, 0x82FCE9U, 0xCB4D72U, 0xD09716U, 0x3B848DU, 0x623D78U, 0x696EA3U, 0x30F4BEU, + 0x7A075DU, 0x451E80U, 0x0C8D33U, 0x17E66EU, 0x5E76B5U, 0x050D14U, 0x0E9ECBU, 0x7607BAU, 0xF57524U, 0xACEAC5U, + 0xE7F91AU, 0xFC0083U, 0x9583F4U, 0xCED96DU, 0xC6688AU, 0x9D7353U, 0x92804CU, 0xCB11BDU, 0xA00B62U, 0xA9F8C3U, + 0x72E398U, 0x783244U, 0x2118F7U, 0x4A8B2AU, 0x5B5249U, 0x1061D4U, 0x09FA2FU, 0x428ABAU, 0x1911F1U, 0x31C604U, + 0x6AFD9FU, 0x276D62U, 0xBC2631U, 0xF795B8U, 0xCE4C4EU, 0x855F97U, 0xDDE520U, 0xD6B479U, 0x8D0FB6U, 0x8C1C07U, + 0xF7C6D8U, 0xBE77A9U, 0xA53C36U, 0xEFAFDFU, 0x369648U, 0x1D4411U, 0x40DFE6U, 0x4BEE7EU, 0x103595U, 0x5902C0U, + 0x43D85BU, 0x2AC9AEU, 0x316265U, 0x7AB158U, 0x23A18BU, 0x201A56U, 0xD949E5U, 0x93F2A8U, 0x882313U, 0xC139C6U, + 0xDA8A3CU, 0x97D329U, 0xEC50E2U, 0xE7AA13U, 0xBFBB8CU, 0xB42075U, 0xED5322U, 0x06C8ABU, 0x1DF85CU, 0x542785U, + 0x0FB4BAU, 0x0FCD7BU, 0x544FA4U, 0x7F5414U, 0x26A54BU, 0x6D3EF2U, 0x702D31U, 0x3BD7CCU, 0x13C6DFU, 0xC83D02U, + 0x83AEF9U, 0x9AB77CU, 0xD15507U, 0xC84ED2U, 0xA3DF49U, 0xF9A4BDU, 0xF836F6U, 0xA3ED4BU, 0xEADC98U, 0xD14B05U, + 0x9A30E6U, 0x03A0BFU, 0x4D6B00U, 0x1458D1U, 0x1FC12EU, 0x64932FU, 0x6D28F0U, 0x36BB09U, 0x7FE29FU, 0x6551E6U, + 0x0E0B21U, 0x57AAB8U, 0x54B147U, 0x8D6216U, 0x86DBA9U, 0xDDC968U, 0xB412F3U, 0xAA218EU, 0xE3FC5DU, 0xF86EF0U, + 0xB35523U, 0xCA945FU, 0xC1AFCCU, 0x983C11U, 0xD264FAU, 0x49D7EFU, 0x000C14U, 0x3B1FC1U, 0x7AA75AU, 0x21F4B7U, + 0x2A6FA4U, 0x728E5DU, 0x5D958AU, 0x042713U, 0x4F767DU, 0x54EDACU, 0x1D5E13U, 0x8604C0U, 0xEE91ADU, 0xB5EA36U, + 0xBC79E3U, 0xE77018U, 0xAC820DU, 0x9D19E6U, 0xD6283FU, 0xCEF3A8U, 0x85E0D1U, 0xDA1A47U, 0x538BAEU, 0x389079U, + 0x216360U, 0x6A6B9FU, 0x30D84EU, 0x3903E1U, 0x421230U, 0x0BE94FU, 0x10FBD6U, 0x5B6011U, 0x4215E8U, 0x008EFBU, + 0xF95C07U, 0xF2759CU, 0xADE659U, 0xA49D22U, 0xFF0CB7U, 0x96964CU, 0x8CE591U, 0xC77E92U, 0x9E2F6FU, 0x959CBCU, + 0xCC0601U, 0xE7475AU, 0x3CFCBFU, 0x74EF25U, 0x6717F4U, 0x2E048BU, 0x15DF0AU, 0x5A6CF5U, 0x03716CU, 0x08A33BU, + 0x5098C2U, 0x5B1945U, 0x20C2BCU, 0x69F063U, 0xF22972U, 0xBB1A8DU, 0xE0814DU, 0xCA52F6U, 0x936AABU, 0x98B958U, + 0xC9A2C5U, 0x820306U, 0x9B593BU, 0xF4CAE0U, 0xEE7315U, 0xA7209EU, 0x7C9BCBU, 0x75C930U, 0x0E54A4U, 0x45E75FU, + 0x5CBC12U, 0x173D81U, 0x0F0778U, 0x44D4AFU, 0x3FC596U, 0x363E59U, 0x65ACE8U, 0x6C9737U, 0xA346E6U, 0xD95DD9U, + 0xD0FE10U, 0x8B26E6U, 0xC2357FU, 0xD9CE28U, 0x92DDC1U, 0xAB455EU, 0xE1B6AFU, 0xB8ADF4U, 0xB35861U, 0x68438AU, + 0x41C157U, 0x12B864U, 0x5B2BB9U, 0x45B00AU, 0x0EC3C6U, 0x175B95U, 0x7C2828U, 0x25B3F3U, 0x2E2206U, 0x75580DU, + 0x3DDBD8U, 0x86C223U, 0xCF31BAU, 0xD4AADDU, 0x9FF804U, 0xC64193U, 0xCD526AU, 0xBDA9F4U, 0xB2BD95U, 0xE92E4AU, + 0xA0C5FBU, 0xBBD424U, 0xD20F7DU, 0x093DDAU, 0x01A603U, 0x5A77F0U, 0x514CEDU, 0x089E36U, 0x63A7D3U, 0x7A3448U, + 0x31FF3CU, 0x2BCEE7U, 0x625462U, 0x150799U, 0x1CBC84U, 0xC7ED77U, 0x8C76AAU, 0x95C409U, 0xDF9954U, 0xE62A8FU, + 0xAD213EU, 0xF6F1E1U, 0xFF4A00U, 0xA4191EU, 0x8D82C7U, 0xD7B330U, 0x9C69A9U, 0x057ACEU, 0x42C357U, 0x2B1088U, + 0x303A79U, 0x7BEB76U, 0x63F087U, 0x284358U, 0x7198E1U, 0x5A88B2U, 0x017B7EU, 0x0860CDU, 0x53F510U, 0x9A0F63U, + 0xA00CFEU, 0xEBB535U, 0xF2E6C0U, 0xB97DDBU, 0xEC0F2EU, 0xE796B5U, 0x9C0578U, 0xD47E0BU, 0xCFEF92U, 0x86F564U, + 0x1D16ADU, 0x768F32U, 0x2FFC43U, 0x24669CU, 0x7C752DU, 0x778CE2U, 0x2E1F9BU, 0x45000CU, 0x5EF0D5U, 0x13FB62U, + 0x40282BU, 0x4811D4U, 0xB38344U, 0xB8789FU, 0xE16BEAU, 0xAAB261U, 0xB38194U, 0xF81B4FU, 0xC2CA52U, 0x8BE1B1U, + 0xD0726CU, 0xD903DFU, 0x829982U, 0xE94A79U, 0x7471FDU, 0x36E026U, 0x2FBA93U, 0x640DC8U, 0x3F1431U, 0x16D7B6U, + 0x4D6C6FU, 0x443C18U, 0x1E8781U, 0x55947EU, 0x6C4FBFU, 0x27FFA0U, 0xBEE451U, 0xF5378FU, 0xAE0E2EU, 0xA2CD71U, + 0xC9D7C8U, 0x98661FU, 0x93BDC6U, 0xC88EE5U, 0xC15438U, 0xBA45C3U, 0xF2FE56U, 0xE9290DU, 0x2230E8U, 0x3B9273U, + 0x70C98FU, 0x0958DCU, 0x02A343U, 0x58B0A2U, 0x150A7DU, 0x0E5BC4U, 0x6FC897U, 0x74F33AU, 0x3F23E9U, 0x66A834U, + 0xECDB0FU, 0xB542DAU, 0x9E5131U, 0xC7ABA5U, 0x8C38FEU, 0x97010BU, 0xDED290U, 0xA4CC7DU, 0xAD3D2EU, 0xF6B6B3U, + 0xF9A540U, 0x205ED9U, 0x634EB6U, 0x5A9567U, 0x11A6D8U, 0x0B3F09U }; + + const uint32_t DMR_A_TABLE[] = { + 0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U, 32U, 36U, 40U, 44U, + 48U, 52U, 56U, 60U, 64U, 68U, 1U, 5U, 9U, 13U, 17U, 21U }; + const uint32_t DMR_B_TABLE[] = { + 25U, 29U, 33U, 37U, 41U, 45U, 49U, 53U, 57U, 61U, 65U, 69U, + 2U, 6U, 10U, 14U, 18U, 22U, 26U, 30U, 34U, 38U, 42U, 46U }; + const uint32_t DMR_C_TABLE[] = { + 50U, 54U, 58U, 62U, 66U, 70U, 3U, 7U, 11U, 15U, 19U, 23U, + 27U, 31U, 35U, 39U, 43U, 47U, 51U, 55U, 59U, 63U, 67U, 71U }; + + const uint32_t IMBE_INTERLEAVE[] = { + 0, 7, 12, 19, 24, 31, 36, 43, 48, 55, 60, 67, 72, 79, 84, 91, 96, 103, 108, 115, 120, 127, 132, 139, + 1, 6, 13, 18, 25, 30, 37, 42, 49, 54, 61, 66, 73, 78, 85, 90, 97, 102, 109, 114, 121, 126, 133, 138, + 2, 9, 14, 21, 26, 33, 38, 45, 50, 57, 62, 69, 74, 81, 86, 93, 98, 105, 110, 117, 122, 129, 134, 141, + 3, 8, 15, 20, 27, 32, 39, 44, 51, 56, 63, 68, 75, 80, 87, 92, 99, 104, 111, 116, 123, 128, 135, 140, + 4, 11, 16, 23, 28, 35, 40, 47, 52, 59, 64, 71, 76, 83, 88, 95, 100, 107, 112, 119, 124, 131, 136, 143, + 5, 10, 17, 22, 29, 34, 41, 46, 53, 58, 65, 70, 77, 82, 89, 94, 101, 106, 113, 118, 125, 130, 137, 142 }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements routines to regenerate DMR/IMBE data using forward error + // correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API AMBEFEC { + public: + /// Initializes a new instance of the AMBEFEC class. + AMBEFEC(); + /// Finalizes a instance of the AMBEFEC class. + ~AMBEFEC(); + + /// Regenerates the DMR AMBE FEC for the input bytes. + uint32_t regenerateDMR(uint8_t* bytes) const; + /// Returns the number of errors on the DMR BER input bytes. + uint32_t measureDMRBER(const uint8_t* bytes) const; + + /// Regenerates the P25 IMBE FEC for the input bytes. + uint32_t regenerateIMBE(uint8_t* bytes) const; + /// Returns the number of errors on the P25 BER input bytes. + uint32_t measureP25BER(const uint8_t* bytes) const; + + private: + /// + uint32_t regenerate(uint32_t& a, uint32_t& b, uint32_t& c, bool b23) const; + }; +} // namespace edac + +#endif // __AMBE_FEC_H__ diff --git a/edac/BCH.cpp b/edac/BCH.cpp new file mode 100644 index 00000000..4ae14974 --- /dev/null +++ b/edac/BCH.cpp @@ -0,0 +1,178 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* File: bch3.c +* Title: Encoder/decoder for binary BCH codes in C (Version 3.1) +* Author: Robert Morelos-Zaragoza +* Date: August 1994 +* Revised: June 13, 1997 +* +* =============== Encoder/Decoder for binary BCH codes in C ================= +* +* Version 1: Original program. The user provides the generator polynomial +* of the code (cumbersome!). +* Version 2: Computes the generator polynomial of the code. +* Version 3: No need to input the coefficients of a primitive polynomial of +* degree m, used to construct the Galois Field GF(2**m). The +* program now works for any binary BCH code of length such that: +* 2**(m-1) - 1 < length <= 2**m - 1 +* +* Note: You may have to change the size of the arrays to make it work. +* +* The encoding and decoding methods used in this program are based on the +* book "Error Control Coding: Fundamentals and Applications", by Lin and +* Costello, Prentice Hall, 1983. +* +* Thanks to Patrick Boyle (pboyle@era.com) for his observation that 'bch2.c' +* did not work for lengths other than 2**m-1 which led to this new version. +* Portions of this program are from 'rs.c', a Reed-Solomon encoder/decoder +* in C, written by Simon Rockliff (simon@augean.ua.oz.au) on 21/9/89. The +* previous version of the BCH encoder/decoder in C, 'bch2.c', was written by +* Robert Morelos-Zaragoza (robert@spectra.eng.hawaii.edu) on 5/19/92. +* +* NOTE: +* The author is not responsible for any malfunctioning of +* this program, nor for any damage caused by it. Please include the +* original program along with these comments in any redistribution. +* +* For more information, suggestions, or other ideas on implementing error +* correcting codes, please contact me at: +* +* Robert Morelos-Zaragoza +* 5120 Woodway, Suite 7036 +* Houston, Texas 77056 +* +* email: r.morelos-zaragoza@ieee.org +* +* COPYRIGHT NOTICE: This computer program is free for non-commercial purposes. +* You may implement this program for any non-commercial application. You may +* also implement this program for commercial purposes, provided that you +* obtain my written permission. Any modification of this program is covered +* by this copyright. +* +* == Copyright (c) 1994-7, Robert Morelos-Zaragoza. All rights reserved. == +* +* m = order of the Galois field GF(2**m) +* n = 2**m - 1 = size of the multiplicative group of GF(2**m) +* length = length of the BCH code +* t = error correcting capability (max. no. of errors the code corrects) +* d = 2*t + 1 = designed min. distance = no. of consecutive roots of g(x) + 1 +* k = n - deg(g(x)) = dimension (no. of information bits/codeword) of the code +* p[] = coefficients of a primitive polynomial used to generate GF(2**m) +* g[] = coefficients of the generator polynomial, g(x) +* alpha_to [] = log table of GF(2**m) +* index_of[] = antilog table of GF(2**m) +* data[] = information bits = coefficients of data polynomial, i(x) +* bb[] = coefficients of redundancy polynomial x^(length-k) i(x) modulo g(x) +* numerr = number of errors +* errpos[] = error positions +* recd[] = coefficients of the received polynomial +* decerror = number of decoding errors (in _message_ positions) +* +*/ +#include "Defines.h" +#include "edac/BCH.h" + +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const int length = 63; +const int k = 16; + +const int g[] = { + 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1 +}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the BCH class. +/// +BCH::BCH() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the BCH class. +/// +BCH::~BCH() +{ + /* stub */ +} + +/// +/// Encodes input data with BCH. +/// +/// P25 NID data to encode with BCH. +void BCH::encode(uint8_t* nid) +{ + assert(nid != NULL); + + int data[16]; + for (int i = 0; i < 16; i++) + data[i] = READ_BIT(nid, i) ? 1 : 0; + + int bb[63]; + encode(data, bb); + + for (int i = 0; i < (length - k); i++) { + bool b = bb[i] == 1; + WRITE_BIT(nid, i + 16U, b); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// Compute redundacy bb[], the coefficients of b(x). The redundancy +/// polynomial b(x) is the remainder after dividing x^(length-k)*data(x) +/// by the generator polynomial g(x). +/// +/// +/// +void BCH::encode(const int* data, int* bb) +{ + for (int i = 0; i < length - k; i++) + bb[i] = 0; + + for (int i = k - 1; i >= 0; i--) { + int feedback = data[i] ^ bb[length - k - 1]; + if (feedback != 0) { + for (int j = length - k - 1; j > 0; j--) + if (g[j] != 0) + bb[j] = bb[j - 1] ^ feedback; + else + bb[j] = bb[j - 1]; + bb[0] = g[0] && feedback; + } + else { + for (int j = length - k - 1; j > 0; j--) + bb[j] = bb[j - 1]; + bb[0] = 0; + } + } +} diff --git a/edac/BCH.h b/edac/BCH.h new file mode 100644 index 00000000..e0e53100 --- /dev/null +++ b/edac/BCH.h @@ -0,0 +1,59 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__BCH_H__) +#define __BCH_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Bose–Chaudhuri–Hocquenghem codes for protecting P25 NID + // data. + // --------------------------------------------------------------------------- + + class HOST_SW_API BCH { + public: + /// Initializes a new instance of the BCH class. + BCH(); + /// Finalizes a instance of the BCH class. + ~BCH(); + + /// Encodes input data with BCH. + void encode(uint8_t* data); + + private: + /// + void encode(const int* data, int* bb); + }; +} // namespace edac + +#endif // __BCH_H__ diff --git a/edac/BPTC19696.cpp b/edac/BPTC19696.cpp new file mode 100644 index 00000000..dd2e06d5 --- /dev/null +++ b/edac/BPTC19696.cpp @@ -0,0 +1,401 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2012 by Ian Wraith +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/BPTC19696.h" +#include "edac/Hamming.h" +#include "Utils.h" + +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the BPTC19696 class. +/// +BPTC19696::BPTC19696() : + m_rawData(NULL), + m_deInterData(NULL) +{ + m_rawData = new bool[196]; + m_deInterData = new bool[196]; +} + +/// +/// Finalizes a instance of the BPTC19696 class. +/// +BPTC19696::~BPTC19696() +{ + delete[] m_rawData; + delete[] m_deInterData; +} + +/// +/// Decode BPTC (196,96) FEC. +/// +/// Input data to decode. +/// Decoded data. +void BPTC19696::decode(const uint8_t* in, uint8_t* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Get the raw binary + decodeExtractBinary(in); + + // Deinterleave + decodeDeInterleave(); + + // Error check + decodeErrorCheck(); + + // Extract Data + decodeExtractData(out); +} + +/// +/// Encode BPTC (196,96) FEC. +/// +/// Input data to encode. +/// Encoded data. +void BPTC19696::encode(const uint8_t* in, uint8_t* out) +{ + assert(in != NULL); + assert(out != NULL); + + // Extract Data + encodeExtractData(in); + + // Error check + encodeErrorCheck(); + + // Deinterleave + encodeInterleave(); + + // Get the raw binary + encodeExtractBinary(out); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +void BPTC19696::decodeExtractBinary(const uint8_t* in) +{ + // First block + Utils::byteToBitsBE(in[0U], m_rawData + 0U); + Utils::byteToBitsBE(in[1U], m_rawData + 8U); + Utils::byteToBitsBE(in[2U], m_rawData + 16U); + Utils::byteToBitsBE(in[3U], m_rawData + 24U); + Utils::byteToBitsBE(in[4U], m_rawData + 32U); + Utils::byteToBitsBE(in[5U], m_rawData + 40U); + Utils::byteToBitsBE(in[6U], m_rawData + 48U); + Utils::byteToBitsBE(in[7U], m_rawData + 56U); + Utils::byteToBitsBE(in[8U], m_rawData + 64U); + Utils::byteToBitsBE(in[9U], m_rawData + 72U); + Utils::byteToBitsBE(in[10U], m_rawData + 80U); + Utils::byteToBitsBE(in[11U], m_rawData + 88U); + Utils::byteToBitsBE(in[12U], m_rawData + 96U); + + // Handle the two bits + bool bits[8U]; + Utils::byteToBitsBE(in[20U], bits); + m_rawData[98U] = bits[6U]; + m_rawData[99U] = bits[7U]; + + // Second block + Utils::byteToBitsBE(in[21U], m_rawData + 100U); + Utils::byteToBitsBE(in[22U], m_rawData + 108U); + Utils::byteToBitsBE(in[23U], m_rawData + 116U); + Utils::byteToBitsBE(in[24U], m_rawData + 124U); + Utils::byteToBitsBE(in[25U], m_rawData + 132U); + Utils::byteToBitsBE(in[26U], m_rawData + 140U); + Utils::byteToBitsBE(in[27U], m_rawData + 148U); + Utils::byteToBitsBE(in[28U], m_rawData + 156U); + Utils::byteToBitsBE(in[29U], m_rawData + 164U); + Utils::byteToBitsBE(in[30U], m_rawData + 172U); + Utils::byteToBitsBE(in[31U], m_rawData + 180U); + Utils::byteToBitsBE(in[32U], m_rawData + 188U); +} + +/// +/// +/// +void BPTC19696::decodeDeInterleave() +{ + for (uint32_t i = 0U; i < 196U; i++) + m_deInterData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (uint32_t a = 0U; a < 196U; a++) { + // Calculate the interleave sequence + uint32_t interleaveSequence = (a * 181U) % 196U; + // Shuffle the data + m_deInterData[a] = m_rawData[interleaveSequence]; + } +} + +/// +/// +/// +void BPTC19696::decodeErrorCheck() +{ + bool fixing; + uint32_t count = 0U; + do { + fixing = false; + + // Run through each of the 15 columns + bool col[13U]; + for (uint32_t c = 0U; c < 15U; c++) { + uint32_t pos = c + 1U; + for (uint32_t a = 0U; a < 13U; a++) { + col[a] = m_deInterData[pos]; + pos = pos + 15U; + } + + if (Hamming::decode1393(col)) { + uint32_t pos = c + 1U; + for (uint32_t a = 0U; a < 13U; a++) { + m_deInterData[pos] = col[a]; + pos = pos + 15U; + } + + fixing = true; + } + } + + // Run through each of the 9 rows containing data + for (uint32_t r = 0U; r < 9U; r++) { + uint32_t pos = (r * 15U) + 1U; + if (Hamming::decode15113_2(m_deInterData + pos)) + fixing = true; + } + + count++; + } while (fixing && count < 5U); +} + +/// +/// +/// +/// +void BPTC19696::decodeExtractData(uint8_t* data) const +{ + bool bData[96U]; + uint32_t pos = 0U; + for (uint32_t a = 4U; a <= 11U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 16U; a <= 26U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 31U; a <= 41U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 46U; a <= 56U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 61U; a <= 71U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 76U; a <= 86U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 91U; a <= 101U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 106U; a <= 116U; a++, pos++) + bData[pos] = m_deInterData[a]; + + for (uint32_t a = 121U; a <= 131U; a++, pos++) + bData[pos] = m_deInterData[a]; + + Utils::bitsToByteBE(bData + 0U, data[0U]); + Utils::bitsToByteBE(bData + 8U, data[1U]); + Utils::bitsToByteBE(bData + 16U, data[2U]); + Utils::bitsToByteBE(bData + 24U, data[3U]); + Utils::bitsToByteBE(bData + 32U, data[4U]); + Utils::bitsToByteBE(bData + 40U, data[5U]); + Utils::bitsToByteBE(bData + 48U, data[6U]); + Utils::bitsToByteBE(bData + 56U, data[7U]); + Utils::bitsToByteBE(bData + 64U, data[8U]); + Utils::bitsToByteBE(bData + 72U, data[9U]); + Utils::bitsToByteBE(bData + 80U, data[10U]); + Utils::bitsToByteBE(bData + 88U, data[11U]); +} + +/// +/// +/// +/// +void BPTC19696::encodeExtractData(const uint8_t* in) const +{ + bool bData[96U]; + Utils::byteToBitsBE(in[0U], bData + 0U); + Utils::byteToBitsBE(in[1U], bData + 8U); + Utils::byteToBitsBE(in[2U], bData + 16U); + Utils::byteToBitsBE(in[3U], bData + 24U); + Utils::byteToBitsBE(in[4U], bData + 32U); + Utils::byteToBitsBE(in[5U], bData + 40U); + Utils::byteToBitsBE(in[6U], bData + 48U); + Utils::byteToBitsBE(in[7U], bData + 56U); + Utils::byteToBitsBE(in[8U], bData + 64U); + Utils::byteToBitsBE(in[9U], bData + 72U); + Utils::byteToBitsBE(in[10U], bData + 80U); + Utils::byteToBitsBE(in[11U], bData + 88U); + + for (uint32_t i = 0U; i < 196U; i++) + m_deInterData[i] = false; + + uint32_t pos = 0U; + for (uint32_t a = 4U; a <= 11U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 16U; a <= 26U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 31U; a <= 41U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 46U; a <= 56U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 61U; a <= 71U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 76U; a <= 86U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 91U; a <= 101U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 106U; a <= 116U; a++, pos++) + m_deInterData[a] = bData[pos]; + + for (uint32_t a = 121U; a <= 131U; a++, pos++) + m_deInterData[a] = bData[pos]; +} + +/// +/// +/// +void BPTC19696::encodeErrorCheck() +{ + // Run through each of the 9 rows containing data + for (uint32_t r = 0U; r < 9U; r++) { + uint32_t pos = (r * 15U) + 1U; + Hamming::encode15113_2(m_deInterData + pos); + } + + // Run through each of the 15 columns + bool col[13U]; + for (uint32_t c = 0U; c < 15U; c++) { + uint32_t pos = c + 1U; + for (uint32_t a = 0U; a < 13U; a++) { + col[a] = m_deInterData[pos]; + pos = pos + 15U; + } + + Hamming::encode1393(col); + + pos = c + 1U; + for (uint32_t a = 0U; a < 13U; a++) { + m_deInterData[pos] = col[a]; + pos = pos + 15U; + } + } +} + +/// +/// +/// +void BPTC19696::encodeInterleave() +{ + for (uint32_t i = 0U; i < 196U; i++) + m_rawData[i] = false; + + // The first bit is R(3) which is not used so can be ignored + for (uint32_t a = 0U; a < 196U; a++) { + // Calculate the interleave sequence + uint32_t interleaveSequence = (a * 181U) % 196U; + // Unshuffle the data + m_rawData[interleaveSequence] = m_deInterData[a]; + } +} + +/// +/// +/// +/// +void BPTC19696::encodeExtractBinary(uint8_t* data) +{ + // First block + Utils::bitsToByteBE(m_rawData + 0U, data[0U]); + Utils::bitsToByteBE(m_rawData + 8U, data[1U]); + Utils::bitsToByteBE(m_rawData + 16U, data[2U]); + Utils::bitsToByteBE(m_rawData + 24U, data[3U]); + Utils::bitsToByteBE(m_rawData + 32U, data[4U]); + Utils::bitsToByteBE(m_rawData + 40U, data[5U]); + Utils::bitsToByteBE(m_rawData + 48U, data[6U]); + Utils::bitsToByteBE(m_rawData + 56U, data[7U]); + Utils::bitsToByteBE(m_rawData + 64U, data[8U]); + Utils::bitsToByteBE(m_rawData + 72U, data[9U]); + Utils::bitsToByteBE(m_rawData + 80U, data[10U]); + Utils::bitsToByteBE(m_rawData + 88U, data[11U]); + + // Handle the two bits + uint8_t byte; + Utils::bitsToByteBE(m_rawData + 96U, byte); + data[12U] = (data[12U] & 0x3FU) | ((byte >> 0) & 0xC0U); + data[20U] = (data[20U] & 0xFCU) | ((byte >> 4) & 0x03U); + + // Second block + Utils::bitsToByteBE(m_rawData + 100U, data[21U]); + Utils::bitsToByteBE(m_rawData + 108U, data[22U]); + Utils::bitsToByteBE(m_rawData + 116U, data[23U]); + Utils::bitsToByteBE(m_rawData + 124U, data[24U]); + Utils::bitsToByteBE(m_rawData + 132U, data[25U]); + Utils::bitsToByteBE(m_rawData + 140U, data[26U]); + Utils::bitsToByteBE(m_rawData + 148U, data[27U]); + Utils::bitsToByteBE(m_rawData + 156U, data[28U]); + Utils::bitsToByteBE(m_rawData + 164U, data[29U]); + Utils::bitsToByteBE(m_rawData + 172U, data[30U]); + Utils::bitsToByteBE(m_rawData + 180U, data[31U]); + Utils::bitsToByteBE(m_rawData + 188U, data[32U]); +} diff --git a/edac/BPTC19696.h b/edac/BPTC19696.h new file mode 100644 index 00000000..71ec50f7 --- /dev/null +++ b/edac/BPTC19696.h @@ -0,0 +1,78 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__BPTC19696_H__) +#define __BPTC19696_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Block Product Turbo Code (196,96) FEC. + // --------------------------------------------------------------------------- + + class HOST_SW_API BPTC19696 { + public: + /// Initializes a new instance of the BPTC19696 class. + BPTC19696(); + /// Finalizes a instance of the BPTC19696 class. + ~BPTC19696(); + + /// Decode BPTC (196,96) FEC. + void decode(const uint8_t* in, uint8_t* out); + /// Encode BPTC (196,96) FEC. + void encode(const uint8_t* in, uint8_t* out); + + private: + bool* m_rawData; + bool* m_deInterData; + + /// + void decodeExtractBinary(const uint8_t* in); + /// + void decodeErrorCheck(); + /// + void decodeDeInterleave(); + /// + void decodeExtractData(uint8_t* data) const; + + /// + void encodeExtractData(const uint8_t* in) const; + /// + void encodeInterleave(); + /// + void encodeErrorCheck(); + /// + void encodeExtractBinary(uint8_t* data); + }; +} // namespace edac + +#endif // __BPTC19696_H__ diff --git a/edac/CRC.cpp b/edac/CRC.cpp new file mode 100644 index 00000000..44564f63 --- /dev/null +++ b/edac/CRC.cpp @@ -0,0 +1,442 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/CRC.h" +#include "Utils.h" + +using namespace edac; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t CRC8_TABLE[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, + 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, + 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, + 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, + 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, + 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, + 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, + 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, + 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, + 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, + 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, + 0xFA, 0xFD, 0xF4, 0xF3, 0x01 }; + +const uint16_t CRC9_TABLE[] = { + 0x000, 0x259, 0x2B2, 0x0EB, 0x23D, 0x064, 0x08F, 0x2D6, + 0x27A, 0x023, 0x0C8, 0x291, 0x047, 0x21E, 0x2F5, 0x0AC, + 0x0F4, 0x2AD, 0x246, 0x01F, 0x2C9, 0x090, 0x07B, 0x222, + 0x28E, 0x0D7, 0x03C, 0x265, 0x0B3, 0x2EA, 0x201, 0x058, + 0x2B1, 0x0E8, 0x003, 0x25A, 0x08C, 0x2D5, 0x23E, 0x067, + 0x0CB, 0x292, 0x279, 0x020, 0x2F6, 0x0AF, 0x044, 0x21D, + 0x245, 0x01C, 0x0F7, 0x2AE, 0x078, 0x221, 0x2CA, 0x093, + 0x03F, 0x266, 0x28D, 0x0D4, 0x202, 0x05B, 0x0B0, 0x2E9, + 0x03B, 0x262, 0x289, 0x0D0, 0x206, 0x05F, 0x0B4, 0x2ED, + 0x241, 0x018, 0x0F3, 0x2AA, 0x07C, 0x225, 0x2CE, 0x097, + 0x0CF, 0x296, 0x27D, 0x024, 0x2F2, 0x0AB, 0x040, 0x219, + 0x2B5, 0x0EC, 0x007, 0x25E, 0x088, 0x2D1, 0x23A, 0x063, + 0x28A, 0x0D3, 0x038, 0x261, 0x0B7, 0x2EE, 0x205, 0x05C, + 0x0F0, 0x2A9, 0x242, 0x01B, 0x2CD, 0x094, 0x07F, 0x226, + 0x27E, 0x027, 0x0CC, 0x295, 0x043, 0x21A, 0x2F1, 0x0A8, + 0x004, 0x25D, 0x2B6, 0x0EF, 0x239, 0x060, 0x08B, 0x2D2, + 0x276, 0x02F, 0x0C4, 0x29D, 0x04B, 0x212, 0x2F9, 0x0A0, + 0x00C, 0x255, 0x2BE, 0x0E7, 0x231, 0x068, 0x083, 0x2DA, + 0x282, 0x0DB, 0x030, 0x269, 0x0BF, 0x2E6, 0x20D, 0x054, + 0x0F8, 0x2A1, 0x24A, 0x013, 0x2C5, 0x09C, 0x077, 0x22E, + 0x0C7, 0x29E, 0x275, 0x02C, 0x2FA, 0x0A3, 0x048, 0x211, + 0x2BD, 0x0E4, 0x00F, 0x256, 0x080, 0x2D9, 0x232, 0x06B, + 0x033, 0x26A, 0x281, 0x0D8, 0x20E, 0x057, 0x0BC, 0x2E5, + 0x249, 0x010, 0x0FB, 0x2A2, 0x074, 0x22D, 0x2C6, 0x09F, + 0x24D, 0x014, 0x0FF, 0x2A6, 0x070, 0x229, 0x2C2, 0x09B, + 0x037, 0x26E, 0x285, 0x0DC, 0x20A, 0x053, 0x0B8, 0x2E1, + 0x2B9, 0x0E0, 0x00B, 0x252, 0x084, 0x2DD, 0x236, 0x06F, + 0x0C3, 0x29A, 0x271, 0x028, 0x2FE, 0x0A7, 0x04C, 0x215, + 0x0FC, 0x2A5, 0x24E, 0x017, 0x2C1, 0x098, 0x073, 0x22A, + 0x286, 0x0DF, 0x034, 0x26D, 0x0BB, 0x2E2, 0x209, 0x050, + 0x008, 0x251, 0x2BA, 0x0E3, 0x235, 0x06C, 0x087, 0x2DE, + 0x272, 0x02B, 0x0C0, 0x299, 0x04F, 0x216, 0x2FD, 0x0A4 }; + +const uint16_t CCITT16_TABLE1[] = { + 0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU, + 0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U, + 0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU, + 0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U, + 0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU, + 0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U, + 0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU, + 0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U, + 0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU, + 0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U, + 0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU, + 0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U, + 0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U, + 0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U, + 0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U, + 0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U, + 0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U, + 0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU, + 0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U, + 0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU, + 0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U, + 0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU, + 0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U, + 0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU, + 0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U, + 0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU, + 0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U, + 0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU, + 0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U, + 0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U, + 0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U, + 0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U }; + +const uint16_t CCITT16_TABLE2[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; + +const uint32_t CRC32_TABLE[] = { + 0x00000000, 0x04C11DB5, 0x09823B6A, 0x0D4326DF, 0x130476D4, 0x17C56B61, 0x1A864DBE, 0x1E47500B, + 0x2608EDA8, 0x22C9F01D, 0x2F8AD6C2, 0x2B4BCB77, 0x350C9B7C, 0x31CD86C9, 0x3C8EA016, 0x384FBDA3, + 0x4C11DB50, 0x48D0C6E5, 0x4593E03A, 0x4152FD8F, 0x5F15AD84, 0x5BD4B031, 0x569796EE, 0x52568B5B, + 0x6A1936F8, 0x6ED82B4D, 0x639B0D92, 0x675A1027, 0x791D402C, 0x7DDC5D99, 0x709F7B46, 0x745E66F3, + 0x9823B6A0, 0x9CE2AB15, 0x91A18DCA, 0x9560907F, 0x8B27C074, 0x8FE6DDC1, 0x82A5FB1E, 0x8664E6AB, + 0xBE2B5B08, 0xBAEA46BD, 0xB7A96062, 0xB3687DD7, 0xAD2F2DDC, 0xA9EE3069, 0xA4AD16B6, 0xA06C0B03, + 0xD4326DF0, 0xD0F37045, 0xDDB0569A, 0xD9714B2F, 0xC7361B24, 0xC3F70691, 0xCEB4204E, 0xCA753DFB, + 0xF23A8058, 0xF6FB9DED, 0xFBB8BB32, 0xFF79A687, 0xE13EF68C, 0xE5FFEB39, 0xE8BCCDE6, 0xEC7DD053, + 0x348670F5, 0x30476D40, 0x3D044B9F, 0x39C5562A, 0x27820621, 0x23431B94, 0x2E003D4B, 0x2AC120FE, + 0x128E9D5D, 0x164F80E8, 0x1B0CA637, 0x1FCDBB82, 0x018AEB89, 0x054BF63C, 0x0808D0E3, 0x0CC9CD56, + 0x7897ABA5, 0x7C56B610, 0x711590CF, 0x75D48D7A, 0x6B93DD71, 0x6F52C0C4, 0x6211E61B, 0x66D0FBAE, + 0x5E9F460D, 0x5A5E5BB8, 0x571D7D67, 0x53DC60D2, 0x4D9B30D9, 0x495A2D6C, 0x44190BB3, 0x40D81606, + 0xACA5C655, 0xA864DBE0, 0xA527FD3F, 0xA1E6E08A, 0xBFA1B081, 0xBB60AD34, 0xB6238BEB, 0xB2E2965E, + 0x8AAD2BFD, 0x8E6C3648, 0x832F1097, 0x87EE0D22, 0x99A95D29, 0x9D68409C, 0x902B6643, 0x94EA7BF6, + 0xE0B41D05, 0xE47500B0, 0xE936266F, 0xEDF73BDA, 0xF3B06BD1, 0xF7717664, 0xFA3250BB, 0xFEF34D0E, + 0xC6BCF0AD, 0xC27DED18, 0xCF3ECBC7, 0xCBFFD672, 0xD5B88679, 0xD1799BCC, 0xDC3ABD13, 0xD8FBA0A6, + 0x690CE1EA, 0x6DCDFC5F, 0x608EDA80, 0x644FC735, 0x7A08973E, 0x7EC98A8B, 0x738AAC54, 0x774BB1E1, + 0x4F040C42, 0x4BC511F7, 0x46863728, 0x42472A9D, 0x5C007A96, 0x58C16723, 0x558241FC, 0x51435C49, + 0x251D3ABA, 0x21DC270F, 0x2C9F01D0, 0x285E1C65, 0x36194C6E, 0x32D851DB, 0x3F9B7704, 0x3B5A6AB1, + 0x0315D712, 0x07D4CAA7, 0x0A97EC78, 0x0E56F1CD, 0x1011A1C6, 0x14D0BC73, 0x19939AAC, 0x1D528719, + 0xF12F574A, 0xF5EE4AFF, 0xF8AD6C20, 0xFC6C7195, 0xE22B219E, 0xE6EA3C2B, 0xEBA91AF4, 0xEF680741, + 0xD727BAE2, 0xD3E6A757, 0xDEA58188, 0xDA649C3D, 0xC423CC36, 0xC0E2D183, 0xCDA1F75C, 0xC960EAE9, + 0xBD3E8C1A, 0xB9FF91AF, 0xB4BCB770, 0xB07DAAC5, 0xAE3AFACE, 0xAAFBE77B, 0xA7B8C1A4, 0xA379DC11, + 0x9B3661B2, 0x9FF77C07, 0x92B45AD8, 0x9675476D, 0x88321766, 0x8CF30AD3, 0x81B02C0C, 0x857131B9, + 0x5D8A911F, 0x594B8CAA, 0x5408AA75, 0x50C9B7C0, 0x4E8EE7CB, 0x4A4FFA7E, 0x470CDCA1, 0x43CDC114, + 0x7B827CB7, 0x7F436102, 0x720047DD, 0x76C15A68, 0x68860A63, 0x6C4717D6, 0x61043109, 0x65C52CBC, + 0x119B4A4F, 0x155A57FA, 0x18197125, 0x1CD86C90, 0x029F3C9B, 0x065E212E, 0x0B1D07F1, 0x0FDC1A44, + 0x3793A7E7, 0x3352BA52, 0x3E119C8D, 0x3AD08138, 0x2497D133, 0x2056CC86, 0x2D15EA59, 0x29D4F7EC, + 0xC5A927BF, 0xC1683A0A, 0xCC2B1CD5, 0xC8EA0160, 0xD6AD516B, 0xD26C4CDE, 0xDF2F6A01, 0xDBEE77B4, + 0xE3A1CA17, 0xE760D7A2, 0xEA23F17D, 0xEEE2ECC8, 0xF0A5BCC3, 0xF464A176, 0xF92787A9, 0xFDE69A1C, + 0x89B8FCEF, 0x8D79E15A, 0x803AC785, 0x84FBDA30, 0x9ABC8A3B, 0x9E7D978E, 0x933EB151, 0x97FFACE4, + 0xAFB01147, 0xAB710CF2, 0xA6322A2D, 0xA2F33798, 0xBCB46793, 0xB8757A26, 0xB5365CF9, 0xB1F7414C }; + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Check 5-bit CRC. +/// +/// Boolean bit array. +/// Computed CRC to check. +/// True, if CRC is valid, otherwise false. +bool CRC::checkFiveBit(bool* in, uint32_t tcrc) +{ + assert(in != NULL); + + uint32_t crc; + encodeFiveBit(in, crc); + + return crc == tcrc; +} + +/// +/// Encode 5-bit CRC. +/// +/// Boolean bit array. +/// Computed CRC. +void CRC::encodeFiveBit(const bool* in, uint32_t& tcrc) +{ + assert(in != NULL); + + unsigned short total = 0U; + for (uint32_t i = 0U; i < 72U; i += 8U) { + uint8_t c; + Utils::bitsToByteBE(in + i, c); + total += c; + } + + total %= 31U; + + tcrc = total; +} + +/// +/// Check 16-bit CRC-CCITT. +/// +/// This uses polynomial 0x1021. +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCCITT162(const uint8_t *in, uint32_t length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0U; + + for (unsigned i = 0U; i < (length - 2U); i++) + crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]]; + + crc16 = ~crc16; + + return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U]; +} + +/// +/// Encode 16-bit CRC-CCITT. +/// +/// This uses polynomial 0x1021. +/// Input byte array. +/// Length of byte array. +void CRC::addCCITT162(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0U; + + for (unsigned i = 0U; i < (length - 2U); i++) + crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]]; + + crc16 = ~crc16; + + in[length - 1U] = crc8[0U]; + in[length - 2U] = crc8[1U]; +} + +/// +/// Check 16-bit CRC-CCITT. +/// +/// This uses polynomial 0x1189. +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCCITT161(const uint8_t *in, uint32_t length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + + for (uint32_t i = 0U; i < (length - 2U); i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]]; + + crc16 = ~crc16; + + return crc8[0U] == in[length - 2U] && crc8[1U] == in[length - 1U]; +} + +/// +/// Encode 16-bit CRC-CCITT. +/// +/// This uses polynomial 0x1189. +/// Input byte array. +/// Length of byte array. +void CRC::addCCITT161(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + assert(length > 2U); + + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + + for (uint32_t i = 0U; i < (length - 2U); i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]]; + + crc16 = ~crc16; + + in[length - 2U] = crc8[0U]; + in[length - 1U] = crc8[1U]; +} + +/// +/// Check 32-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// True, if CRC is valid, otherwise false. +bool CRC::checkCRC32(const uint8_t *in, uint32_t length) +{ + assert(in != NULL); + assert(length > 4U); + + union { + uint32_t crc32; + uint8_t crc8[4U]; + }; + + crc32 = 0xFFFFFFFFU; + + for (uint32_t i = 0U; i < (length - 4U); i++) + crc32 = (crc32 << 8) ^ CRC32_TABLE[((crc32 >> 24) ^ in[i]) & 0xFF]; + + crc32 = ~crc32; + + return crc8[0U] == in[length - 4U] && crc8[1U] == in[length - 3U] && crc8[2U] == in[length - 2U] && crc8[3U] == in[length - 1U]; +} + +/// +/// Encode 32-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +void CRC::addCRC32(uint8_t* in, uint32_t length) +{ + assert(in != NULL); + assert(length > 4U); + + union { + uint32_t crc32; + uint8_t crc8[4U]; + }; + + crc32 = 0xFFFFFFFFU; + + for (uint32_t i = 0U; i < (length - 4U); i++) + crc32 = (crc32 << 8) ^ CRC32_TABLE[((crc32 >> 24) ^ in[i]) & 0xFF]; + + crc32 = ~crc32; + + in[length - 4U] = crc8[0U]; + in[length - 3U] = crc8[1U]; + in[length - 2U] = crc8[2U]; + in[length - 1U] = crc8[3U]; +} + +/// +/// Generate 8-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// Calculated 8-bit CRC value. +uint8_t CRC::crc8(const uint8_t *in, uint32_t length) +{ + assert(in != NULL); + + uint8_t crc = 0U; + + for (uint32_t i = 0U; i < length; i++) + crc = CRC8_TABLE[crc ^ in[i]]; + + return crc; +} + +/// +/// Generate 9-bit CRC. +/// +/// Input byte array. +/// Length of byte array. +/// Calculated 9-bit CRC value. +uint16_t CRC::crc9(const uint8_t* in, uint32_t length) +{ + assert(in != NULL); + + uint16_t crc = 0U; + + for (uint32_t i = 0U; i < length; i++) + crc = CRC9_TABLE[(crc ^ in[i]) & 0xFF]; + + return crc; +} diff --git a/edac/CRC.h b/edac/CRC.h new file mode 100644 index 00000000..eae429cc --- /dev/null +++ b/edac/CRC.h @@ -0,0 +1,72 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CRC_H__) +#define __CRC_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements various Cyclic Redundancy Check routines. + // --------------------------------------------------------------------------- + + class HOST_SW_API CRC { + public: + /// Check 5-bit CRC. + static bool checkFiveBit(bool* in, uint32_t tcrc); + /// Encode 5-bit CRC. + static void encodeFiveBit(const bool* in, uint32_t& tcrc); + + /// Check 16-bit CRC-CCITT. + static bool checkCCITT162(const uint8_t* in, uint32_t length); + /// Encode 16-bit CRC-CCITT. + static void addCCITT162(uint8_t* in, uint32_t length); + + /// Check 16-bit CRC-CCITT. + static bool checkCCITT161(const uint8_t* in, uint32_t length); + /// Encode 16-bit CRC-CCITT. + static void addCCITT161(uint8_t* in, uint32_t length); + + /// Check 32-bit CRC. + static bool checkCRC32(const uint8_t* in, uint32_t length); + /// Encode 32-bit CRC. + static void addCRC32(uint8_t* in, uint32_t length); + + /// Generate 8-bit CRC. + static uint8_t crc8(const uint8_t* in, uint32_t length); + /// Generate 9-bit CRC. + static uint16_t crc9(const uint8_t* in, uint32_t length); + }; +} // namespace edac + +#endif // __CRC_H__ diff --git a/edac/Golay2087.cpp b/edac/Golay2087.cpp new file mode 100644 index 00000000..82c5c99d --- /dev/null +++ b/edac/Golay2087.cpp @@ -0,0 +1,300 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/Golay2087.h" + +using namespace edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t ENCODING_TABLE_2087[] = { + 0x0000U, 0xB08EU, 0xE093U, 0x501DU, 0x70A9U, 0xC027U, 0x903AU, 0x20B4U, 0x60DCU, 0xD052U, 0x804FU, 0x30C1U, + 0x1075U, 0xA0FBU, 0xF0E6U, 0x4068U, 0x7036U, 0xC0B8U, 0x90A5U, 0x202BU, 0x009FU, 0xB011U, 0xE00CU, 0x5082U, + 0x10EAU, 0xA064U, 0xF079U, 0x40F7U, 0x6043U, 0xD0CDU, 0x80D0U, 0x305EU, 0xD06CU, 0x60E2U, 0x30FFU, 0x8071U, + 0xA0C5U, 0x104BU, 0x4056U, 0xF0D8U, 0xB0B0U, 0x003EU, 0x5023U, 0xE0ADU, 0xC019U, 0x7097U, 0x208AU, 0x9004U, + 0xA05AU, 0x10D4U, 0x40C9U, 0xF047U, 0xD0F3U, 0x607DU, 0x3060U, 0x80EEU, 0xC086U, 0x7008U, 0x2015U, 0x909BU, + 0xB02FU, 0x00A1U, 0x50BCU, 0xE032U, 0x90D9U, 0x2057U, 0x704AU, 0xC0C4U, 0xE070U, 0x50FEU, 0x00E3U, 0xB06DU, + 0xF005U, 0x408BU, 0x1096U, 0xA018U, 0x80ACU, 0x3022U, 0x603FU, 0xD0B1U, 0xE0EFU, 0x5061U, 0x007CU, 0xB0F2U, + 0x9046U, 0x20C8U, 0x70D5U, 0xC05BU, 0x8033U, 0x30BDU, 0x60A0U, 0xD02EU, 0xF09AU, 0x4014U, 0x1009U, 0xA087U, + 0x40B5U, 0xF03BU, 0xA026U, 0x10A8U, 0x301CU, 0x8092U, 0xD08FU, 0x6001U, 0x2069U, 0x90E7U, 0xC0FAU, 0x7074U, + 0x50C0U, 0xE04EU, 0xB053U, 0x00DDU, 0x3083U, 0x800DU, 0xD010U, 0x609EU, 0x402AU, 0xF0A4U, 0xA0B9U, 0x1037U, + 0x505FU, 0xE0D1U, 0xB0CCU, 0x0042U, 0x20F6U, 0x9078U, 0xC065U, 0x70EBU, 0xA03DU, 0x10B3U, 0x40AEU, 0xF020U, + 0xD094U, 0x601AU, 0x3007U, 0x8089U, 0xC0E1U, 0x706FU, 0x2072U, 0x90FCU, 0xB048U, 0x00C6U, 0x50DBU, 0xE055U, + 0xD00BU, 0x6085U, 0x3098U, 0x8016U, 0xA0A2U, 0x102CU, 0x4031U, 0xF0BFU, 0xB0D7U, 0x0059U, 0x5044U, 0xE0CAU, + 0xC07EU, 0x70F0U, 0x20EDU, 0x9063U, 0x7051U, 0xC0DFU, 0x90C2U, 0x204CU, 0x00F8U, 0xB076U, 0xE06BU, 0x50E5U, + 0x108DU, 0xA003U, 0xF01EU, 0x4090U, 0x6024U, 0xD0AAU, 0x80B7U, 0x3039U, 0x0067U, 0xB0E9U, 0xE0F4U, 0x507AU, + 0x70CEU, 0xC040U, 0x905DU, 0x20D3U, 0x60BBU, 0xD035U, 0x8028U, 0x30A6U, 0x1012U, 0xA09CU, 0xF081U, 0x400FU, + 0x30E4U, 0x806AU, 0xD077U, 0x60F9U, 0x404DU, 0xF0C3U, 0xA0DEU, 0x1050U, 0x5038U, 0xE0B6U, 0xB0ABU, 0x0025U, + 0x2091U, 0x901FU, 0xC002U, 0x708CU, 0x40D2U, 0xF05CU, 0xA041U, 0x10CFU, 0x307BU, 0x80F5U, 0xD0E8U, 0x6066U, + 0x200EU, 0x9080U, 0xC09DU, 0x7013U, 0x50A7U, 0xE029U, 0xB034U, 0x00BAU, 0xE088U, 0x5006U, 0x001BU, 0xB095U, + 0x9021U, 0x20AFU, 0x70B2U, 0xC03CU, 0x8054U, 0x30DAU, 0x60C7U, 0xD049U, 0xF0FDU, 0x4073U, 0x106EU, 0xA0E0U, + 0x90BEU, 0x2030U, 0x702DU, 0xC0A3U, 0xE017U, 0x5099U, 0x0084U, 0xB00AU, 0xF062U, 0x40ECU, 0x10F1U, 0xA07FU, + 0x80CBU, 0x3045U, 0x6058U, 0xD0D6U }; + +const uint32_t DECODING_TABLE_1987[] = { + 0x00000U, 0x00001U, 0x00002U, 0x00003U, 0x00004U, 0x00005U, 0x00006U, 0x00007U, 0x00008U, 0x00009U, 0x0000AU, 0x0000BU, 0x0000CU, + 0x0000DU, 0x0000EU, 0x24020U, 0x00010U, 0x00011U, 0x00012U, 0x00013U, 0x00014U, 0x00015U, 0x00016U, 0x00017U, 0x00018U, 0x00019U, + 0x0001AU, 0x0001BU, 0x0001CU, 0x0001DU, 0x48040U, 0x01480U, 0x00020U, 0x00021U, 0x00022U, 0x00023U, 0x00024U, 0x00025U, 0x00026U, + 0x24008U, 0x00028U, 0x00029U, 0x0002AU, 0x24004U, 0x0002CU, 0x24002U, 0x24001U, 0x24000U, 0x00030U, 0x00031U, 0x00032U, 0x08180U, + 0x00034U, 0x00C40U, 0x00036U, 0x00C42U, 0x00038U, 0x43000U, 0x0003AU, 0x43002U, 0x02902U, 0x24012U, 0x02900U, 0x24010U, 0x00040U, + 0x00041U, 0x00042U, 0x00043U, 0x00044U, 0x00045U, 0x00046U, 0x00047U, 0x00048U, 0x00049U, 0x0004AU, 0x02500U, 0x0004CU, 0x0004DU, + 0x48010U, 0x48011U, 0x00050U, 0x00051U, 0x00052U, 0x21200U, 0x00054U, 0x00C20U, 0x48008U, 0x48009U, 0x00058U, 0x00059U, 0x48004U, + 0x48005U, 0x48002U, 0x48003U, 0x48000U, 0x48001U, 0x00060U, 0x00061U, 0x00062U, 0x00063U, 0x00064U, 0x00C10U, 0x10300U, 0x0B000U, + 0x00068U, 0x00069U, 0x01880U, 0x01881U, 0x40181U, 0x40180U, 0x24041U, 0x24040U, 0x00070U, 0x00C04U, 0x00072U, 0x00C06U, 0x00C01U, + 0x00C00U, 0x00C03U, 0x00C02U, 0x05204U, 0x00C0CU, 0x48024U, 0x48025U, 0x05200U, 0x00C08U, 0x48020U, 0x48021U, 0x00080U, 0x00081U, + 0x00082U, 0x00083U, 0x00084U, 0x00085U, 0x00086U, 0x00087U, 0x00088U, 0x00089U, 0x0008AU, 0x50200U, 0x0008CU, 0x0A800U, 0x01411U, + 0x01410U, 0x00090U, 0x00091U, 0x00092U, 0x08120U, 0x00094U, 0x00095U, 0x04A00U, 0x01408U, 0x00098U, 0x00099U, 0x01405U, 0x01404U, + 0x01403U, 0x01402U, 0x01401U, 0x01400U, 0x000A0U, 0x000A1U, 0x000A2U, 0x08110U, 0x000A4U, 0x000A5U, 0x42400U, 0x42401U, 0x000A8U, + 0x000A9U, 0x01840U, 0x01841U, 0x40141U, 0x40140U, 0x24081U, 0x24080U, 0x000B0U, 0x08102U, 0x08101U, 0x08100U, 0x000B4U, 0x08106U, + 0x08105U, 0x08104U, 0x20A01U, 0x20A00U, 0x08109U, 0x08108U, 0x01423U, 0x01422U, 0x01421U, 0x01420U, 0x000C0U, 0x000C1U, 0x000C2U, + 0x000C3U, 0x000C4U, 0x000C5U, 0x000C6U, 0x000C7U, 0x000C8U, 0x000C9U, 0x01820U, 0x01821U, 0x20600U, 0x40120U, 0x16000U, 0x16001U, + 0x000D0U, 0x000D1U, 0x42801U, 0x42800U, 0x03100U, 0x18200U, 0x03102U, 0x18202U, 0x000D8U, 0x000D9U, 0x48084U, 0x01444U, 0x48082U, + 0x01442U, 0x48080U, 0x01440U, 0x000E0U, 0x32000U, 0x01808U, 0x04600U, 0x40109U, 0x40108U, 0x0180CU, 0x4010AU, 0x01802U, 0x40104U, + 0x01800U, 0x01801U, 0x40101U, 0x40100U, 0x01804U, 0x40102U, 0x0A408U, 0x08142U, 0x08141U, 0x08140U, 0x00C81U, 0x00C80U, 0x00C83U, + 0x00C82U, 0x0A400U, 0x0A401U, 0x01810U, 0x01811U, 0x40111U, 0x40110U, 0x01814U, 0x40112U, 0x00100U, 0x00101U, 0x00102U, 0x00103U, + 0x00104U, 0x00105U, 0x00106U, 0x41800U, 0x00108U, 0x00109U, 0x0010AU, 0x02440U, 0x0010CU, 0x0010DU, 0x0010EU, 0x02444U, 0x00110U, + 0x00111U, 0x00112U, 0x080A0U, 0x00114U, 0x00115U, 0x00116U, 0x080A4U, 0x00118U, 0x00119U, 0x15000U, 0x15001U, 0x02822U, 0x02823U, + 0x02820U, 0x02821U, 0x00120U, 0x00121U, 0x00122U, 0x08090U, 0x00124U, 0x00125U, 0x10240U, 0x10241U, 0x00128U, 0x00129U, 0x0012AU, + 0x24104U, 0x09400U, 0x400C0U, 0x02810U, 0x24100U, 0x00130U, 0x08082U, 0x08081U, 0x08080U, 0x31001U, 0x31000U, 0x02808U, 0x08084U, + 0x02806U, 0x0808AU, 0x02804U, 0x08088U, 0x02802U, 0x02803U, 0x02800U, 0x02801U, 0x00140U, 0x00141U, 0x00142U, 0x02408U, 0x00144U, + 0x00145U, 0x10220U, 0x10221U, 0x00148U, 0x02402U, 0x02401U, 0x02400U, 0x400A1U, 0x400A0U, 0x02405U, 0x02404U, 0x00150U, 0x00151U, + 0x00152U, 0x02418U, 0x03080U, 0x03081U, 0x03082U, 0x03083U, 0x09801U, 0x09800U, 0x02411U, 0x02410U, 0x48102U, 0x09804U, 0x48100U, + 0x48101U, 0x00160U, 0x00161U, 0x10204U, 0x10205U, 0x10202U, 0x40088U, 0x10200U, 0x10201U, 0x40085U, 0x40084U, 0x02421U, 0x02420U, + 0x40081U, 0x40080U, 0x10208U, 0x40082U, 0x41402U, 0x080C2U, 0x41400U, 0x080C0U, 0x00D01U, 0x00D00U, 0x10210U, 0x10211U, 0x40095U, + 0x40094U, 0x02844U, 0x080C8U, 0x40091U, 0x40090U, 0x02840U, 0x02841U, 0x00180U, 0x00181U, 0x00182U, 0x08030U, 0x00184U, 0x14400U, + 0x22201U, 0x22200U, 0x00188U, 0x00189U, 0x0018AU, 0x08038U, 0x40061U, 0x40060U, 0x40063U, 0x40062U, 0x00190U, 0x08022U, 0x08021U, + 0x08020U, 0x03040U, 0x03041U, 0x08025U, 0x08024U, 0x40C00U, 0x40C01U, 0x08029U, 0x08028U, 0x2C000U, 0x2C001U, 0x01501U, 0x01500U, + 0x001A0U, 0x08012U, 0x08011U, 0x08010U, 0x40049U, 0x40048U, 0x08015U, 0x08014U, 0x06200U, 0x40044U, 0x30400U, 0x08018U, 0x40041U, + 0x40040U, 0x40043U, 0x40042U, 0x08003U, 0x08002U, 0x08001U, 0x08000U, 0x08007U, 0x08006U, 0x08005U, 0x08004U, 0x0800BU, 0x0800AU, + 0x08009U, 0x08008U, 0x40051U, 0x40050U, 0x02880U, 0x0800CU, 0x001C0U, 0x001C1U, 0x64000U, 0x64001U, 0x03010U, 0x40028U, 0x08C00U, + 0x08C01U, 0x40025U, 0x40024U, 0x02481U, 0x02480U, 0x40021U, 0x40020U, 0x40023U, 0x40022U, 0x03004U, 0x03005U, 0x08061U, 0x08060U, + 0x03000U, 0x03001U, 0x03002U, 0x03003U, 0x0300CU, 0x40034U, 0x30805U, 0x30804U, 0x03008U, 0x40030U, 0x30801U, 0x30800U, 0x4000DU, + 0x4000CU, 0x08051U, 0x08050U, 0x40009U, 0x40008U, 0x10280U, 0x4000AU, 0x40005U, 0x40004U, 0x01900U, 0x40006U, 0x40001U, 0x40000U, + 0x40003U, 0x40002U, 0x14800U, 0x08042U, 0x08041U, 0x08040U, 0x03020U, 0x40018U, 0x08045U, 0x08044U, 0x40015U, 0x40014U, 0x08049U, + 0x08048U, 0x40011U, 0x40010U, 0x40013U, 0x40012U, 0x00200U, 0x00201U, 0x00202U, 0x00203U, 0x00204U, 0x00205U, 0x00206U, 0x00207U, + 0x00208U, 0x00209U, 0x0020AU, 0x50080U, 0x0020CU, 0x0020DU, 0x0020EU, 0x50084U, 0x00210U, 0x00211U, 0x00212U, 0x21040U, 0x00214U, + 0x00215U, 0x04880U, 0x04881U, 0x00218U, 0x00219U, 0x0E001U, 0x0E000U, 0x0021CU, 0x0021DU, 0x04888U, 0x0E004U, 0x00220U, 0x00221U, + 0x00222U, 0x00223U, 0x00224U, 0x00225U, 0x10140U, 0x10141U, 0x00228U, 0x00229U, 0x0022AU, 0x24204U, 0x12401U, 0x12400U, 0x24201U, + 0x24200U, 0x00230U, 0x00231U, 0x00232U, 0x21060U, 0x2A000U, 0x2A001U, 0x2A002U, 0x2A003U, 0x20881U, 0x20880U, 0x20883U, 0x20882U, + 0x05040U, 0x05041U, 0x05042U, 0x24210U, 0x00240U, 0x00241U, 0x00242U, 0x21010U, 0x00244U, 0x46000U, 0x10120U, 0x10121U, 0x00248U, + 0x00249U, 0x0024AU, 0x21018U, 0x20480U, 0x20481U, 0x20482U, 0x20483U, 0x00250U, 0x21002U, 0x21001U, 0x21000U, 0x18081U, 0x18080U, + 0x21005U, 0x21004U, 0x12800U, 0x12801U, 0x21009U, 0x21008U, 0x05020U, 0x05021U, 0x48200U, 0x48201U, 0x00260U, 0x00261U, 0x10104U, + 0x04480U, 0x10102U, 0x10103U, 0x10100U, 0x10101U, 0x62002U, 0x62003U, 0x62000U, 0x62001U, 0x05010U, 0x05011U, 0x10108U, 0x10109U, + 0x0500CU, 0x21022U, 0x21021U, 0x21020U, 0x05008U, 0x00E00U, 0x10110U, 0x10111U, 0x05004U, 0x05005U, 0x05006U, 0x21028U, 0x05000U, + 0x05001U, 0x05002U, 0x05003U, 0x00280U, 0x00281U, 0x00282U, 0x50008U, 0x00284U, 0x00285U, 0x04810U, 0x22100U, 0x00288U, 0x50002U, + 0x50001U, 0x50000U, 0x20440U, 0x20441U, 0x50005U, 0x50004U, 0x00290U, 0x00291U, 0x04804U, 0x04805U, 0x04802U, 0x18040U, 0x04800U, + 0x04801U, 0x20821U, 0x20820U, 0x50011U, 0x50010U, 0x0480AU, 0x01602U, 0x04808U, 0x01600U, 0x002A0U, 0x002A1U, 0x04441U, 0x04440U, + 0x002A4U, 0x002A5U, 0x04830U, 0x04444U, 0x06100U, 0x20810U, 0x50021U, 0x50020U, 0x06104U, 0x20814U, 0x50025U, 0x50024U, 0x20809U, + 0x20808U, 0x13000U, 0x08300U, 0x04822U, 0x2080CU, 0x04820U, 0x04821U, 0x20801U, 0x20800U, 0x20803U, 0x20802U, 0x20805U, 0x20804U, + 0x04828U, 0x20806U, 0x002C0U, 0x002C1U, 0x04421U, 0x04420U, 0x20408U, 0x18010U, 0x2040AU, 0x18012U, 0x20404U, 0x20405U, 0x50041U, + 0x50040U, 0x20400U, 0x20401U, 0x20402U, 0x20403U, 0x18005U, 0x18004U, 0x21081U, 0x21080U, 0x18001U, 0x18000U, 0x04840U, 0x18002U, + 0x20414U, 0x1800CU, 0x21089U, 0x21088U, 0x20410U, 0x18008U, 0x20412U, 0x1800AU, 0x04403U, 0x04402U, 0x04401U, 0x04400U, 0x10182U, + 0x04406U, 0x10180U, 0x04404U, 0x01A02U, 0x0440AU, 0x01A00U, 0x04408U, 0x20420U, 0x40300U, 0x20422U, 0x40302U, 0x04413U, 0x04412U, + 0x04411U, 0x04410U, 0x18021U, 0x18020U, 0x10190U, 0x18022U, 0x20841U, 0x20840U, 0x01A10U, 0x20842U, 0x05080U, 0x05081U, 0x05082U, + 0x05083U, 0x00300U, 0x00301U, 0x00302U, 0x00303U, 0x00304U, 0x00305U, 0x10060U, 0x22080U, 0x00308U, 0x00309U, 0x28800U, 0x28801U, + 0x44402U, 0x44403U, 0x44400U, 0x44401U, 0x00310U, 0x00311U, 0x10C01U, 0x10C00U, 0x00314U, 0x00315U, 0x10070U, 0x10C04U, 0x00318U, + 0x00319U, 0x28810U, 0x10C08U, 0x44412U, 0x00000U, 0x44410U, 0x44411U, 0x00320U, 0x60400U, 0x10044U, 0x10045U, 0x10042U, 0x0C800U, + 0x10040U, 0x10041U, 0x06080U, 0x06081U, 0x06082U, 0x06083U, 0x1004AU, 0x0C808U, 0x10048U, 0x10049U, 0x58008U, 0x08282U, 0x08281U, + 0x08280U, 0x10052U, 0x0C810U, 0x10050U, 0x10051U, 0x58000U, 0x58001U, 0x58002U, 0x08288U, 0x02A02U, 0x02A03U, 0x02A00U, 0x02A01U, + 0x00340U, 0x00341U, 0x10024U, 0x10025U, 0x10022U, 0x10023U, 0x10020U, 0x10021U, 0x34001U, 0x34000U, 0x02601U, 0x02600U, 0x1002AU, + 0x34004U, 0x10028U, 0x10029U, 0x0C400U, 0x0C401U, 0x21101U, 0x21100U, 0x60800U, 0x60801U, 0x10030U, 0x10031U, 0x0C408U, 0x34010U, + 0x21109U, 0x21108U, 0x60808U, 0x60809U, 0x10038U, 0x28420U, 0x10006U, 0x10007U, 0x10004U, 0x10005U, 0x10002U, 0x10003U, 0x10000U, + 0x10001U, 0x1000EU, 0x40284U, 0x1000CU, 0x1000DU, 0x1000AU, 0x40280U, 0x10008U, 0x10009U, 0x10016U, 0x10017U, 0x10014U, 0x10015U, + 0x10012U, 0x10013U, 0x10010U, 0x10011U, 0x05104U, 0x44802U, 0x44801U, 0x44800U, 0x05100U, 0x05101U, 0x10018U, 0x28400U, 0x00380U, + 0x00381U, 0x22005U, 0x22004U, 0x22003U, 0x22002U, 0x22001U, 0x22000U, 0x06020U, 0x06021U, 0x50101U, 0x50100U, 0x11800U, 0x11801U, + 0x22009U, 0x22008U, 0x45001U, 0x45000U, 0x08221U, 0x08220U, 0x04902U, 0x22012U, 0x04900U, 0x22010U, 0x06030U, 0x45008U, 0x08229U, + 0x08228U, 0x11810U, 0x11811U, 0x04908U, 0x22018U, 0x06008U, 0x06009U, 0x08211U, 0x08210U, 0x100C2U, 0x22022U, 0x100C0U, 0x22020U, + 0x06000U, 0x06001U, 0x06002U, 0x06003U, 0x06004U, 0x40240U, 0x06006U, 0x40242U, 0x08203U, 0x08202U, 0x08201U, 0x08200U, 0x08207U, + 0x08206U, 0x08205U, 0x08204U, 0x06010U, 0x20900U, 0x08209U, 0x08208U, 0x61002U, 0x20904U, 0x61000U, 0x61001U, 0x29020U, 0x29021U, + 0x100A4U, 0x22044U, 0x100A2U, 0x22042U, 0x100A0U, 0x22040U, 0x20504U, 0x40224U, 0x0D005U, 0x0D004U, 0x20500U, 0x40220U, 0x0D001U, + 0x0D000U, 0x03204U, 0x18104U, 0x08261U, 0x08260U, 0x03200U, 0x18100U, 0x03202U, 0x18102U, 0x11421U, 0x11420U, 0x00000U, 0x11422U, + 0x03208U, 0x18108U, 0x0D011U, 0x0D010U, 0x29000U, 0x29001U, 0x10084U, 0x04500U, 0x10082U, 0x40208U, 0x10080U, 0x10081U, 0x06040U, + 0x40204U, 0x06042U, 0x40206U, 0x40201U, 0x40200U, 0x10088U, 0x40202U, 0x29010U, 0x08242U, 0x08241U, 0x08240U, 0x10092U, 0x40218U, + 0x10090U, 0x10091U, 0x11401U, 0x11400U, 0x11403U, 0x11402U, 0x40211U, 0x40210U, 0x10098U, 0x40212U, 0x00400U, 0x00401U, 0x00402U, + 0x00403U, 0x00404U, 0x00405U, 0x00406U, 0x00407U, 0x00408U, 0x00409U, 0x0040AU, 0x02140U, 0x0040CU, 0x0040DU, 0x01091U, 0x01090U, + 0x00410U, 0x00411U, 0x00412U, 0x00413U, 0x00414U, 0x00860U, 0x01089U, 0x01088U, 0x00418U, 0x38000U, 0x01085U, 0x01084U, 0x01083U, + 0x01082U, 0x01081U, 0x01080U, 0x00420U, 0x00421U, 0x00422U, 0x00423U, 0x00424U, 0x00850U, 0x42080U, 0x42081U, 0x00428U, 0x00429U, + 0x48801U, 0x48800U, 0x09100U, 0x12200U, 0x24401U, 0x24400U, 0x00430U, 0x00844U, 0x00432U, 0x00846U, 0x00841U, 0x00840U, 0x1C000U, + 0x00842U, 0x00438U, 0x0084CU, 0x010A5U, 0x010A4U, 0x00849U, 0x00848U, 0x010A1U, 0x010A0U, 0x00440U, 0x00441U, 0x00442U, 0x02108U, + 0x00444U, 0x00830U, 0x70001U, 0x70000U, 0x00448U, 0x02102U, 0x02101U, 0x02100U, 0x20280U, 0x20281U, 0x02105U, 0x02104U, 0x00450U, + 0x00824U, 0x00452U, 0x00826U, 0x00821U, 0x00820U, 0x00823U, 0x00822U, 0x24802U, 0x02112U, 0x24800U, 0x02110U, 0x00829U, 0x00828U, + 0x48400U, 0x010C0U, 0x00460U, 0x00814U, 0x04281U, 0x04280U, 0x00811U, 0x00810U, 0x00813U, 0x00812U, 0x54000U, 0x54001U, 0x02121U, + 0x02120U, 0x00819U, 0x00818U, 0x0081BU, 0x0081AU, 0x00805U, 0x00804U, 0x41100U, 0x00806U, 0x00801U, 0x00800U, 0x00803U, 0x00802U, + 0x0A080U, 0x0080CU, 0x0A082U, 0x0080EU, 0x00809U, 0x00808U, 0x0080BU, 0x0080AU, 0x00480U, 0x00481U, 0x00482U, 0x00483U, 0x00484U, + 0x14100U, 0x42020U, 0x01018U, 0x00488U, 0x00489U, 0x01015U, 0x01014U, 0x20240U, 0x01012U, 0x01011U, 0x01010U, 0x00490U, 0x00491U, + 0x0100DU, 0x0100CU, 0x0100BU, 0x0100AU, 0x01009U, 0x01008U, 0x40900U, 0x01006U, 0x01005U, 0x01004U, 0x01003U, 0x01002U, 0x01001U, + 0x01000U, 0x004A0U, 0x004A1U, 0x42004U, 0x04240U, 0x42002U, 0x42003U, 0x42000U, 0x42001U, 0x30102U, 0x30103U, 0x30100U, 0x30101U, + 0x4200AU, 0x01032U, 0x42008U, 0x01030U, 0x25000U, 0x25001U, 0x08501U, 0x08500U, 0x008C1U, 0x008C0U, 0x42010U, 0x01028U, 0x0A040U, + 0x0A041U, 0x01025U, 0x01024U, 0x01023U, 0x01022U, 0x01021U, 0x01020U, 0x004C0U, 0x49000U, 0x04221U, 0x04220U, 0x20208U, 0x20209U, + 0x08900U, 0x08901U, 0x20204U, 0x20205U, 0x02181U, 0x02180U, 0x20200U, 0x20201U, 0x20202U, 0x01050U, 0x0A028U, 0x008A4U, 0x0104DU, + 0x0104CU, 0x008A1U, 0x008A0U, 0x01049U, 0x01048U, 0x0A020U, 0x0A021U, 0x01045U, 0x01044U, 0x20210U, 0x01042U, 0x01041U, 0x01040U, + 0x04203U, 0x04202U, 0x04201U, 0x04200U, 0x00891U, 0x00890U, 0x42040U, 0x04204U, 0x0A010U, 0x0A011U, 0x01C00U, 0x04208U, 0x20220U, + 0x40500U, 0x20222U, 0x40502U, 0x0A008U, 0x00884U, 0x04211U, 0x04210U, 0x00881U, 0x00880U, 0x00883U, 0x00882U, 0x0A000U, 0x0A001U, + 0x0A002U, 0x0A003U, 0x0A004U, 0x00888U, 0x01061U, 0x01060U, 0x00500U, 0x00501U, 0x00502U, 0x02048U, 0x00504U, 0x14080U, 0x00506U, + 0x14082U, 0x00508U, 0x02042U, 0x02041U, 0x02040U, 0x09020U, 0x09021U, 0x44200U, 0x02044U, 0x00510U, 0x00511U, 0x10A01U, 0x10A00U, + 0x4A001U, 0x4A000U, 0x4A003U, 0x4A002U, 0x40880U, 0x40881U, 0x02051U, 0x02050U, 0x40884U, 0x01182U, 0x01181U, 0x01180U, 0x00520U, + 0x60200U, 0x00522U, 0x60202U, 0x09008U, 0x09009U, 0x0900AU, 0x0900BU, 0x09004U, 0x09005U, 0x30080U, 0x02060U, 0x09000U, 0x09001U, + 0x09002U, 0x09003U, 0x41042U, 0x08482U, 0x41040U, 0x08480U, 0x00941U, 0x00940U, 0x41044U, 0x00942U, 0x09014U, 0x09015U, 0x02C04U, + 0x08488U, 0x09010U, 0x09011U, 0x02C00U, 0x02C01U, 0x00540U, 0x0200AU, 0x02009U, 0x02008U, 0x08882U, 0x0200EU, 0x08880U, 0x0200CU, + 0x02003U, 0x02002U, 0x02001U, 0x02000U, 0x02007U, 0x02006U, 0x02005U, 0x02004U, 0x0C200U, 0x0C201U, 0x41020U, 0x02018U, 0x00921U, + 0x00920U, 0x41024U, 0x00922U, 0x02013U, 0x02012U, 0x02011U, 0x02010U, 0x02017U, 0x02016U, 0x02015U, 0x02014U, 0x41012U, 0x0202AU, + 0x41010U, 0x02028U, 0x26000U, 0x00910U, 0x10600U, 0x10601U, 0x02023U, 0x02022U, 0x02021U, 0x02020U, 0x09040U, 0x40480U, 0x02025U, + 0x02024U, 0x41002U, 0x00904U, 0x41000U, 0x41001U, 0x00901U, 0x00900U, 0x41004U, 0x00902U, 0x4100AU, 0x02032U, 0x41008U, 0x02030U, + 0x00909U, 0x00908U, 0x28201U, 0x28200U, 0x00580U, 0x14004U, 0x00582U, 0x14006U, 0x14001U, 0x14000U, 0x08840U, 0x14002U, 0x40810U, + 0x40811U, 0x30020U, 0x020C0U, 0x14009U, 0x14008U, 0x01111U, 0x01110U, 0x40808U, 0x40809U, 0x08421U, 0x08420U, 0x14011U, 0x14010U, + 0x01109U, 0x01108U, 0x40800U, 0x40801U, 0x40802U, 0x01104U, 0x40804U, 0x01102U, 0x01101U, 0x01100U, 0x03801U, 0x03800U, 0x30008U, + 0x08410U, 0x14021U, 0x14020U, 0x42100U, 0x42101U, 0x30002U, 0x30003U, 0x30000U, 0x30001U, 0x09080U, 0x40440U, 0x30004U, 0x30005U, + 0x08403U, 0x08402U, 0x08401U, 0x08400U, 0x08407U, 0x08406U, 0x08405U, 0x08404U, 0x40820U, 0x40821U, 0x30010U, 0x08408U, 0x40824U, + 0x01122U, 0x01121U, 0x01120U, 0x08806U, 0x0208AU, 0x08804U, 0x02088U, 0x08802U, 0x14040U, 0x08800U, 0x08801U, 0x02083U, 0x02082U, + 0x02081U, 0x02080U, 0x20300U, 0x40420U, 0x08808U, 0x02084U, 0x03404U, 0x03405U, 0x08814U, 0x02098U, 0x03400U, 0x03401U, 0x08810U, + 0x08811U, 0x40840U, 0x40841U, 0x02091U, 0x02090U, 0x40844U, 0x01142U, 0x01141U, 0x01140U, 0x04303U, 0x04302U, 0x04301U, 0x04300U, + 0x40409U, 0x40408U, 0x08820U, 0x08821U, 0x40405U, 0x40404U, 0x30040U, 0x020A0U, 0x40401U, 0x40400U, 0x40403U, 0x40402U, 0x41082U, + 0x08442U, 0x41080U, 0x08440U, 0x00981U, 0x00980U, 0x41084U, 0x00982U, 0x0A100U, 0x11200U, 0x0A102U, 0x11202U, 0x40411U, 0x40410U, + 0x40413U, 0x40412U, 0x00600U, 0x00601U, 0x00602U, 0x00603U, 0x00604U, 0x00605U, 0x00606U, 0x00607U, 0x00608U, 0x05800U, 0x0060AU, + 0x05802U, 0x200C0U, 0x12020U, 0x44100U, 0x44101U, 0x00610U, 0x00611U, 0x10901U, 0x10900U, 0x51000U, 0x51001U, 0x51002U, 0x10904U, + 0x00618U, 0x05810U, 0x01285U, 0x01284U, 0x51008U, 0x01282U, 0x01281U, 0x01280U, 0x00620U, 0x60100U, 0x040C1U, 0x040C0U, 0x12009U, + 0x12008U, 0x21800U, 0x21801U, 0x12005U, 0x12004U, 0x12007U, 0x12006U, 0x12001U, 0x12000U, 0x12003U, 0x12002U, 0x00630U, 0x00A44U, + 0x040D1U, 0x040D0U, 0x00A41U, 0x00A40U, 0x21810U, 0x00A42U, 0x12015U, 0x12014U, 0x00000U, 0x12016U, 0x12011U, 0x12010U, 0x12013U, + 0x12012U, 0x00640U, 0x00641U, 0x040A1U, 0x040A0U, 0x20088U, 0x20089U, 0x2008AU, 0x040A4U, 0x20084U, 0x20085U, 0x19000U, 0x02300U, + 0x20080U, 0x20081U, 0x20082U, 0x20083U, 0x0C100U, 0x0C101U, 0x21401U, 0x21400U, 0x00A21U, 0x00A20U, 0x00A23U, 0x00A22U, 0x20094U, + 0x20095U, 0x19010U, 0x21408U, 0x20090U, 0x20091U, 0x20092U, 0x28120U, 0x04083U, 0x04082U, 0x04081U, 0x04080U, 0x00A11U, 0x00A10U, + 0x10500U, 0x04084U, 0x200A4U, 0x0408AU, 0x04089U, 0x04088U, 0x200A0U, 0x12040U, 0x200A2U, 0x12042U, 0x00A05U, 0x00A04U, 0x04091U, + 0x04090U, 0x00A01U, 0x00A00U, 0x00A03U, 0x00A02U, 0x05404U, 0x00A0CU, 0x28105U, 0x28104U, 0x05400U, 0x00A08U, 0x28101U, 0x28100U, + 0x00680U, 0x00681U, 0x04061U, 0x04060U, 0x20048U, 0x20049U, 0x2004AU, 0x04064U, 0x20044U, 0x20045U, 0x50401U, 0x50400U, 0x20040U, + 0x20041U, 0x20042U, 0x01210U, 0x68002U, 0x68003U, 0x68000U, 0x68001U, 0x04C02U, 0x0120AU, 0x04C00U, 0x01208U, 0x20054U, 0x01206U, + 0x01205U, 0x01204U, 0x20050U, 0x01202U, 0x01201U, 0x01200U, 0x18800U, 0x04042U, 0x04041U, 0x04040U, 0x42202U, 0x04046U, 0x42200U, + 0x04044U, 0x20064U, 0x0404AU, 0x04049U, 0x04048U, 0x20060U, 0x12080U, 0x20062U, 0x12082U, 0x18810U, 0x04052U, 0x04051U, 0x04050U, + 0x4C009U, 0x4C008U, 0x42210U, 0x04054U, 0x20C01U, 0x20C00U, 0x20C03U, 0x20C02U, 0x4C001U, 0x4C000U, 0x01221U, 0x01220U, 0x2000CU, + 0x04022U, 0x04021U, 0x04020U, 0x20008U, 0x20009U, 0x2000AU, 0x04024U, 0x20004U, 0x20005U, 0x20006U, 0x04028U, 0x20000U, 0x20001U, + 0x20002U, 0x20003U, 0x2001CU, 0x04032U, 0x04031U, 0x04030U, 0x20018U, 0x18400U, 0x2001AU, 0x18402U, 0x20014U, 0x20015U, 0x20016U, + 0x01244U, 0x20010U, 0x20011U, 0x20012U, 0x01240U, 0x04003U, 0x04002U, 0x04001U, 0x04000U, 0x20028U, 0x04006U, 0x04005U, 0x04004U, + 0x20024U, 0x0400AU, 0x04009U, 0x04008U, 0x20020U, 0x20021U, 0x20022U, 0x0400CU, 0x04013U, 0x04012U, 0x04011U, 0x04010U, 0x00A81U, + 0x00A80U, 0x04015U, 0x04014U, 0x0A200U, 0x11100U, 0x04019U, 0x04018U, 0x20030U, 0x20031U, 0x50800U, 0x50801U, 0x00700U, 0x60020U, + 0x10811U, 0x10810U, 0x4400AU, 0x60024U, 0x44008U, 0x44009U, 0x44006U, 0x02242U, 0x44004U, 0x02240U, 0x44002U, 0x44003U, 0x44000U, + 0x44001U, 0x0C040U, 0x10802U, 0x10801U, 0x10800U, 0x0C044U, 0x10806U, 0x10805U, 0x10804U, 0x23000U, 0x23001U, 0x10809U, 0x10808U, + 0x44012U, 0x44013U, 0x44010U, 0x44011U, 0x60001U, 0x60000U, 0x60003U, 0x60002U, 0x60005U, 0x60004U, 0x10440U, 0x10441U, 0x60009U, + 0x60008U, 0x44024U, 0x6000AU, 0x09200U, 0x12100U, 0x44020U, 0x44021U, 0x60011U, 0x60010U, 0x10821U, 0x10820U, 0x07003U, 0x07002U, + 0x07001U, 0x07000U, 0x23020U, 0x60018U, 0x28045U, 0x28044U, 0x09210U, 0x28042U, 0x28041U, 0x28040U, 0x0C010U, 0x0C011U, 0x02209U, + 0x02208U, 0x10422U, 0x10423U, 0x10420U, 0x10421U, 0x02203U, 0x02202U, 0x02201U, 0x02200U, 0x20180U, 0x20181U, 0x44040U, 0x02204U, + 0x0C000U, 0x0C001U, 0x0C002U, 0x10840U, 0x0C004U, 0x0C005U, 0x0C006U, 0x10844U, 0x0C008U, 0x0C009U, 0x02211U, 0x02210U, 0x0C00CU, + 0x28022U, 0x28021U, 0x28020U, 0x60041U, 0x60040U, 0x10404U, 0x04180U, 0x10402U, 0x10403U, 0x10400U, 0x10401U, 0x02223U, 0x02222U, + 0x02221U, 0x02220U, 0x1040AU, 0x28012U, 0x10408U, 0x28010U, 0x0C020U, 0x0C021U, 0x41200U, 0x41201U, 0x00B01U, 0x00B00U, 0x10410U, + 0x28008U, 0x11081U, 0x11080U, 0x28005U, 0x28004U, 0x28003U, 0x28002U, 0x28001U, 0x28000U, 0x52040U, 0x14204U, 0x22405U, 0x22404U, + 0x14201U, 0x14200U, 0x22401U, 0x22400U, 0x20144U, 0x20145U, 0x44084U, 0x022C0U, 0x20140U, 0x20141U, 0x44080U, 0x44081U, 0x40A08U, + 0x10882U, 0x10881U, 0x10880U, 0x14211U, 0x14210U, 0x1A008U, 0x10884U, 0x40A00U, 0x40A01U, 0x40A02U, 0x01304U, 0x1A002U, 0x01302U, + 0x1A000U, 0x01300U, 0x60081U, 0x60080U, 0x04141U, 0x04140U, 0x60085U, 0x60084U, 0x104C0U, 0x04144U, 0x06400U, 0x06401U, 0x30200U, + 0x30201U, 0x06404U, 0x40640U, 0x30204U, 0x30205U, 0x08603U, 0x08602U, 0x08601U, 0x08600U, 0x00000U, 0x08606U, 0x08605U, 0x08604U, + 0x11041U, 0x11040U, 0x30210U, 0x11042U, 0x11045U, 0x11044U, 0x1A020U, 0x01320U, 0x52000U, 0x52001U, 0x04121U, 0x04120U, 0x20108U, + 0x20109U, 0x08A00U, 0x08A01U, 0x20104U, 0x20105U, 0x02281U, 0x02280U, 0x20100U, 0x20101U, 0x20102U, 0x20103U, 0x0C080U, 0x0C081U, + 0x0C082U, 0x04130U, 0x0C084U, 0x06808U, 0x08A10U, 0x08A11U, 0x11021U, 0x11020U, 0x11023U, 0x11022U, 0x20110U, 0x06800U, 0x20112U, + 0x06802U, 0x04103U, 0x04102U, 0x04101U, 0x04100U, 0x10482U, 0x04106U, 0x10480U, 0x04104U, 0x11011U, 0x11010U, 0x04109U, 0x04108U, + 0x20120U, 0x40600U, 0x20122U, 0x40602U, 0x11009U, 0x11008U, 0x22800U, 0x04110U, 0x1100DU, 0x1100CU, 0x22804U, 0x04114U, 0x11001U, + 0x11000U, 0x11003U, 0x11002U, 0x11005U, 0x11004U, 0x28081U, 0x28080U }; + +#define X18 0x00040000 /* vector representation of X^{18} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK8 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Decode Golay (20,8,7) FEC. +/// +/// Golay FEC encoded data byte array +/// +uint8_t Golay2087::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint32_t code = (data[0U] << 11) + (data[1U] << 3) + (data[2U] >> 5); + uint32_t syndrome = getSyndrome1987(code); + uint32_t error_pattern = DECODING_TABLE_1987[syndrome]; + + if (error_pattern != 0x00U) + code ^= error_pattern; + + return code >> 11; +} + +/// +/// Encode Golay (20,8,7) FEC. +/// +/// Data to encode with Golay FEC. +void Golay2087::encode(uint8_t* data) +{ + assert(data != NULL); + + uint32_t value = data[0U]; + + uint32_t cksum = ENCODING_TABLE_2087[value]; + + data[1U] = cksum & 0xFFU; + data[2U] = cksum >> 8; +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// Compute the syndrome corresponding to the given pattern, i.e., the +/// remainder after dividing the pattern (when considering it as the vector +/// representation of a polynomial) by the generator polynomial, GENPOL. +/// In the program this pattern has several meanings: (1) pattern = infomation +/// bits, when constructing the encoding table; (2) pattern = error pattern, +/// when constructing the decoding table; and (3) pattern = received vector, to +/// obtain its syndrome in decoding. +/// +/// +/// +uint32_t Golay2087::getSyndrome1987(uint32_t pattern) +{ + uint32_t aux = X18; + + if (pattern >= X11) { + while (pattern & MASK8) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} diff --git a/edac/Golay2087.h b/edac/Golay2087.h new file mode 100644 index 00000000..5974ca8b --- /dev/null +++ b/edac/Golay2087.h @@ -0,0 +1,55 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__GOLAY2087_H__) +#define __GOLAY2087_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Golay (20,8,7) forward error correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API Golay2087 { + public: + /// Decode Golay (20,8,7) FEC. + static uint8_t decode(const uint8_t* data); + /// Encode Golay (20,8,7) FEC. + static void encode(uint8_t* data); + + private: + /// + static uint32_t getSyndrome1987(uint32_t pattern); + }; +} // namespace edac + +#endif // __GOLAY2087_H__ diff --git a/edac/Golay24128.cpp b/edac/Golay24128.cpp new file mode 100644 index 00000000..91a25d78 --- /dev/null +++ b/edac/Golay24128.cpp @@ -0,0 +1,1303 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2010,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2002 by Robert H. Morelos-Zaragoza. All rights reserved. +* Copyright (C) 2017 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/Golay24128.h" + +using namespace edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t ENCODING_TABLE_23127[] = { + 0x000000U, 0x0018EAU, 0x00293EU, 0x0031D4U, 0x004A96U, 0x00527CU, 0x0063A8U, 0x007B42U, 0x008DC6U, 0x00952CU, + 0x00A4F8U, 0x00BC12U, 0x00C750U, 0x00DFBAU, 0x00EE6EU, 0x00F684U, 0x010366U, 0x011B8CU, 0x012A58U, 0x0132B2U, + 0x0149F0U, 0x01511AU, 0x0160CEU, 0x017824U, 0x018EA0U, 0x01964AU, 0x01A79EU, 0x01BF74U, 0x01C436U, 0x01DCDCU, + 0x01ED08U, 0x01F5E2U, 0x0206CCU, 0x021E26U, 0x022FF2U, 0x023718U, 0x024C5AU, 0x0254B0U, 0x026564U, 0x027D8EU, + 0x028B0AU, 0x0293E0U, 0x02A234U, 0x02BADEU, 0x02C19CU, 0x02D976U, 0x02E8A2U, 0x02F048U, 0x0305AAU, 0x031D40U, + 0x032C94U, 0x03347EU, 0x034F3CU, 0x0357D6U, 0x036602U, 0x037EE8U, 0x03886CU, 0x039086U, 0x03A152U, 0x03B9B8U, + 0x03C2FAU, 0x03DA10U, 0x03EBC4U, 0x03F32EU, 0x040D98U, 0x041572U, 0x0424A6U, 0x043C4CU, 0x04470EU, 0x045FE4U, + 0x046E30U, 0x0476DAU, 0x04805EU, 0x0498B4U, 0x04A960U, 0x04B18AU, 0x04CAC8U, 0x04D222U, 0x04E3F6U, 0x04FB1CU, + 0x050EFEU, 0x051614U, 0x0527C0U, 0x053F2AU, 0x054468U, 0x055C82U, 0x056D56U, 0x0575BCU, 0x058338U, 0x059BD2U, + 0x05AA06U, 0x05B2ECU, 0x05C9AEU, 0x05D144U, 0x05E090U, 0x05F87AU, 0x060B54U, 0x0613BEU, 0x06226AU, 0x063A80U, + 0x0641C2U, 0x065928U, 0x0668FCU, 0x067016U, 0x068692U, 0x069E78U, 0x06AFACU, 0x06B746U, 0x06CC04U, 0x06D4EEU, + 0x06E53AU, 0x06FDD0U, 0x070832U, 0x0710D8U, 0x07210CU, 0x0739E6U, 0x0742A4U, 0x075A4EU, 0x076B9AU, 0x077370U, + 0x0785F4U, 0x079D1EU, 0x07ACCAU, 0x07B420U, 0x07CF62U, 0x07D788U, 0x07E65CU, 0x07FEB6U, 0x0803DAU, 0x081B30U, + 0x082AE4U, 0x08320EU, 0x08494CU, 0x0851A6U, 0x086072U, 0x087898U, 0x088E1CU, 0x0896F6U, 0x08A722U, 0x08BFC8U, + 0x08C48AU, 0x08DC60U, 0x08EDB4U, 0x08F55EU, 0x0900BCU, 0x091856U, 0x092982U, 0x093168U, 0x094A2AU, 0x0952C0U, + 0x096314U, 0x097BFEU, 0x098D7AU, 0x099590U, 0x09A444U, 0x09BCAEU, 0x09C7ECU, 0x09DF06U, 0x09EED2U, 0x09F638U, + 0x0A0516U, 0x0A1DFCU, 0x0A2C28U, 0x0A34C2U, 0x0A4F80U, 0x0A576AU, 0x0A66BEU, 0x0A7E54U, 0x0A88D0U, 0x0A903AU, + 0x0AA1EEU, 0x0AB904U, 0x0AC246U, 0x0ADAACU, 0x0AEB78U, 0x0AF392U, 0x0B0670U, 0x0B1E9AU, 0x0B2F4EU, 0x0B37A4U, + 0x0B4CE6U, 0x0B540CU, 0x0B65D8U, 0x0B7D32U, 0x0B8BB6U, 0x0B935CU, 0x0BA288U, 0x0BBA62U, 0x0BC120U, 0x0BD9CAU, + 0x0BE81EU, 0x0BF0F4U, 0x0C0E42U, 0x0C16A8U, 0x0C277CU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3EU, 0x0C6DEAU, 0x0C7500U, + 0x0C8384U, 0x0C9B6EU, 0x0CAABAU, 0x0CB250U, 0x0CC912U, 0x0CD1F8U, 0x0CE02CU, 0x0CF8C6U, 0x0D0D24U, 0x0D15CEU, + 0x0D241AU, 0x0D3CF0U, 0x0D47B2U, 0x0D5F58U, 0x0D6E8CU, 0x0D7666U, 0x0D80E2U, 0x0D9808U, 0x0DA9DCU, 0x0DB136U, + 0x0DCA74U, 0x0DD29EU, 0x0DE34AU, 0x0DFBA0U, 0x0E088EU, 0x0E1064U, 0x0E21B0U, 0x0E395AU, 0x0E4218U, 0x0E5AF2U, + 0x0E6B26U, 0x0E73CCU, 0x0E8548U, 0x0E9DA2U, 0x0EAC76U, 0x0EB49CU, 0x0ECFDEU, 0x0ED734U, 0x0EE6E0U, 0x0EFE0AU, + 0x0F0BE8U, 0x0F1302U, 0x0F22D6U, 0x0F3A3CU, 0x0F417EU, 0x0F5994U, 0x0F6840U, 0x0F70AAU, 0x0F862EU, 0x0F9EC4U, + 0x0FAF10U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD452U, 0x0FE586U, 0x0FFD6CU, 0x1007B4U, 0x101F5EU, 0x102E8AU, 0x103660U, + 0x104D22U, 0x1055C8U, 0x10641CU, 0x107CF6U, 0x108A72U, 0x109298U, 0x10A34CU, 0x10BBA6U, 0x10C0E4U, 0x10D80EU, + 0x10E9DAU, 0x10F130U, 0x1104D2U, 0x111C38U, 0x112DECU, 0x113506U, 0x114E44U, 0x1156AEU, 0x11677AU, 0x117F90U, + 0x118914U, 0x1191FEU, 0x11A02AU, 0x11B8C0U, 0x11C382U, 0x11DB68U, 0x11EABCU, 0x11F256U, 0x120178U, 0x121992U, + 0x122846U, 0x1230ACU, 0x124BEEU, 0x125304U, 0x1262D0U, 0x127A3AU, 0x128CBEU, 0x129454U, 0x12A580U, 0x12BD6AU, + 0x12C628U, 0x12DEC2U, 0x12EF16U, 0x12F7FCU, 0x13021EU, 0x131AF4U, 0x132B20U, 0x1333CAU, 0x134888U, 0x135062U, + 0x1361B6U, 0x13795CU, 0x138FD8U, 0x139732U, 0x13A6E6U, 0x13BE0CU, 0x13C54EU, 0x13DDA4U, 0x13EC70U, 0x13F49AU, + 0x140A2CU, 0x1412C6U, 0x142312U, 0x143BF8U, 0x1440BAU, 0x145850U, 0x146984U, 0x14716EU, 0x1487EAU, 0x149F00U, + 0x14AED4U, 0x14B63EU, 0x14CD7CU, 0x14D596U, 0x14E442U, 0x14FCA8U, 0x15094AU, 0x1511A0U, 0x152074U, 0x15389EU, + 0x1543DCU, 0x155B36U, 0x156AE2U, 0x157208U, 0x15848CU, 0x159C66U, 0x15ADB2U, 0x15B558U, 0x15CE1AU, 0x15D6F0U, + 0x15E724U, 0x15FFCEU, 0x160CE0U, 0x16140AU, 0x1625DEU, 0x163D34U, 0x164676U, 0x165E9CU, 0x166F48U, 0x1677A2U, + 0x168126U, 0x1699CCU, 0x16A818U, 0x16B0F2U, 0x16CBB0U, 0x16D35AU, 0x16E28EU, 0x16FA64U, 0x170F86U, 0x17176CU, + 0x1726B8U, 0x173E52U, 0x174510U, 0x175DFAU, 0x176C2EU, 0x1774C4U, 0x178240U, 0x179AAAU, 0x17AB7EU, 0x17B394U, + 0x17C8D6U, 0x17D03CU, 0x17E1E8U, 0x17F902U, 0x18046EU, 0x181C84U, 0x182D50U, 0x1835BAU, 0x184EF8U, 0x185612U, + 0x1867C6U, 0x187F2CU, 0x1889A8U, 0x189142U, 0x18A096U, 0x18B87CU, 0x18C33EU, 0x18DBD4U, 0x18EA00U, 0x18F2EAU, + 0x190708U, 0x191FE2U, 0x192E36U, 0x1936DCU, 0x194D9EU, 0x195574U, 0x1964A0U, 0x197C4AU, 0x198ACEU, 0x199224U, + 0x19A3F0U, 0x19BB1AU, 0x19C058U, 0x19D8B2U, 0x19E966U, 0x19F18CU, 0x1A02A2U, 0x1A1A48U, 0x1A2B9CU, 0x1A3376U, + 0x1A4834U, 0x1A50DEU, 0x1A610AU, 0x1A79E0U, 0x1A8F64U, 0x1A978EU, 0x1AA65AU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD18U, + 0x1AECCCU, 0x1AF426U, 0x1B01C4U, 0x1B192EU, 0x1B28FAU, 0x1B3010U, 0x1B4B52U, 0x1B53B8U, 0x1B626CU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E8U, 0x1BA53CU, 0x1BBDD6U, 0x1BC694U, 0x1BDE7EU, 0x1BEFAAU, 0x1BF740U, 0x1C09F6U, 0x1C111CU, + 0x1C20C8U, 0x1C3822U, 0x1C4360U, 0x1C5B8AU, 0x1C6A5EU, 0x1C72B4U, 0x1C8430U, 0x1C9CDAU, 0x1CAD0EU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64CU, 0x1CE798U, 0x1CFF72U, 0x1D0A90U, 0x1D127AU, 0x1D23AEU, 0x1D3B44U, 0x1D4006U, 0x1D58ECU, + 0x1D6938U, 0x1D71D2U, 0x1D8756U, 0x1D9FBCU, 0x1DAE68U, 0x1DB682U, 0x1DCDC0U, 0x1DD52AU, 0x1DE4FEU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D0U, 0x1E2604U, 0x1E3EEEU, 0x1E45ACU, 0x1E5D46U, 0x1E6C92U, 0x1E7478U, 0x1E82FCU, 0x1E9A16U, + 0x1EABC2U, 0x1EB328U, 0x1EC86AU, 0x1ED080U, 0x1EE154U, 0x1EF9BEU, 0x1F0C5CU, 0x1F14B6U, 0x1F2562U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E20U, 0x1F6FF4U, 0x1F771EU, 0x1F819AU, 0x1F9970U, 0x1FA8A4U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E6U, + 0x1FE232U, 0x1FFAD8U, 0x200F68U, 0x201782U, 0x202656U, 0x203EBCU, 0x2045FEU, 0x205D14U, 0x206CC0U, 0x20742AU, + 0x2082AEU, 0x209A44U, 0x20AB90U, 0x20B37AU, 0x20C838U, 0x20D0D2U, 0x20E106U, 0x20F9ECU, 0x210C0EU, 0x2114E4U, + 0x212530U, 0x213DDAU, 0x214698U, 0x215E72U, 0x216FA6U, 0x21774CU, 0x2181C8U, 0x219922U, 0x21A8F6U, 0x21B01CU, + 0x21CB5EU, 0x21D3B4U, 0x21E260U, 0x21FA8AU, 0x2209A4U, 0x22114EU, 0x22209AU, 0x223870U, 0x224332U, 0x225BD8U, + 0x226A0CU, 0x2272E6U, 0x228462U, 0x229C88U, 0x22AD5CU, 0x22B5B6U, 0x22CEF4U, 0x22D61EU, 0x22E7CAU, 0x22FF20U, + 0x230AC2U, 0x231228U, 0x2323FCU, 0x233B16U, 0x234054U, 0x2358BEU, 0x23696AU, 0x237180U, 0x238704U, 0x239FEEU, + 0x23AE3AU, 0x23B6D0U, 0x23CD92U, 0x23D578U, 0x23E4ACU, 0x23FC46U, 0x2402F0U, 0x241A1AU, 0x242BCEU, 0x243324U, + 0x244866U, 0x24508CU, 0x246158U, 0x2479B2U, 0x248F36U, 0x2497DCU, 0x24A608U, 0x24BEE2U, 0x24C5A0U, 0x24DD4AU, + 0x24EC9EU, 0x24F474U, 0x250196U, 0x25197CU, 0x2528A8U, 0x253042U, 0x254B00U, 0x2553EAU, 0x25623EU, 0x257AD4U, + 0x258C50U, 0x2594BAU, 0x25A56EU, 0x25BD84U, 0x25C6C6U, 0x25DE2CU, 0x25EFF8U, 0x25F712U, 0x26043CU, 0x261CD6U, + 0x262D02U, 0x2635E8U, 0x264EAAU, 0x265640U, 0x266794U, 0x267F7EU, 0x2689FAU, 0x269110U, 0x26A0C4U, 0x26B82EU, + 0x26C36CU, 0x26DB86U, 0x26EA52U, 0x26F2B8U, 0x27075AU, 0x271FB0U, 0x272E64U, 0x27368EU, 0x274DCCU, 0x275526U, + 0x2764F2U, 0x277C18U, 0x278A9CU, 0x279276U, 0x27A3A2U, 0x27BB48U, 0x27C00AU, 0x27D8E0U, 0x27E934U, 0x27F1DEU, + 0x280CB2U, 0x281458U, 0x28258CU, 0x283D66U, 0x284624U, 0x285ECEU, 0x286F1AU, 0x2877F0U, 0x288174U, 0x28999EU, + 0x28A84AU, 0x28B0A0U, 0x28CBE2U, 0x28D308U, 0x28E2DCU, 0x28FA36U, 0x290FD4U, 0x29173EU, 0x2926EAU, 0x293E00U, + 0x294542U, 0x295DA8U, 0x296C7CU, 0x297496U, 0x298212U, 0x299AF8U, 0x29AB2CU, 0x29B3C6U, 0x29C884U, 0x29D06EU, + 0x29E1BAU, 0x29F950U, 0x2A0A7EU, 0x2A1294U, 0x2A2340U, 0x2A3BAAU, 0x2A40E8U, 0x2A5802U, 0x2A69D6U, 0x2A713CU, + 0x2A87B8U, 0x2A9F52U, 0x2AAE86U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C4U, 0x2AE410U, 0x2AFCFAU, 0x2B0918U, 0x2B11F2U, + 0x2B2026U, 0x2B38CCU, 0x2B438EU, 0x2B5B64U, 0x2B6AB0U, 0x2B725AU, 0x2B84DEU, 0x2B9C34U, 0x2BADE0U, 0x2BB50AU, + 0x2BCE48U, 0x2BD6A2U, 0x2BE776U, 0x2BFF9CU, 0x2C012AU, 0x2C19C0U, 0x2C2814U, 0x2C30FEU, 0x2C4BBCU, 0x2C5356U, + 0x2C6282U, 0x2C7A68U, 0x2C8CECU, 0x2C9406U, 0x2CA5D2U, 0x2CBD38U, 0x2CC67AU, 0x2CDE90U, 0x2CEF44U, 0x2CF7AEU, + 0x2D024CU, 0x2D1AA6U, 0x2D2B72U, 0x2D3398U, 0x2D48DAU, 0x2D5030U, 0x2D61E4U, 0x2D790EU, 0x2D8F8AU, 0x2D9760U, + 0x2DA6B4U, 0x2DBE5EU, 0x2DC51CU, 0x2DDDF6U, 0x2DEC22U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0CU, 0x2E2ED8U, 0x2E3632U, + 0x2E4D70U, 0x2E559AU, 0x2E644EU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CAU, 0x2EA31EU, 0x2EBBF4U, 0x2EC0B6U, 0x2ED85CU, + 0x2EE988U, 0x2EF162U, 0x2F0480U, 0x2F1C6AU, 0x2F2DBEU, 0x2F3554U, 0x2F4E16U, 0x2F56FCU, 0x2F6728U, 0x2F7FC2U, + 0x2F8946U, 0x2F91ACU, 0x2FA078U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3AU, 0x2FEAEEU, 0x2FF204U, 0x3008DCU, 0x301036U, + 0x3021E2U, 0x303908U, 0x30424AU, 0x305AA0U, 0x306B74U, 0x30739EU, 0x30851AU, 0x309DF0U, 0x30AC24U, 0x30B4CEU, + 0x30CF8CU, 0x30D766U, 0x30E6B2U, 0x30FE58U, 0x310BBAU, 0x311350U, 0x312284U, 0x313A6EU, 0x31412CU, 0x3159C6U, + 0x316812U, 0x3170F8U, 0x31867CU, 0x319E96U, 0x31AF42U, 0x31B7A8U, 0x31CCEAU, 0x31D400U, 0x31E5D4U, 0x31FD3EU, + 0x320E10U, 0x3216FAU, 0x32272EU, 0x323FC4U, 0x324486U, 0x325C6CU, 0x326DB8U, 0x327552U, 0x3283D6U, 0x329B3CU, + 0x32AAE8U, 0x32B202U, 0x32C940U, 0x32D1AAU, 0x32E07EU, 0x32F894U, 0x330D76U, 0x33159CU, 0x332448U, 0x333CA2U, + 0x3347E0U, 0x335F0AU, 0x336EDEU, 0x337634U, 0x3380B0U, 0x33985AU, 0x33A98EU, 0x33B164U, 0x33CA26U, 0x33D2CCU, + 0x33E318U, 0x33FBF2U, 0x340544U, 0x341DAEU, 0x342C7AU, 0x343490U, 0x344FD2U, 0x345738U, 0x3466ECU, 0x347E06U, + 0x348882U, 0x349068U, 0x34A1BCU, 0x34B956U, 0x34C214U, 0x34DAFEU, 0x34EB2AU, 0x34F3C0U, 0x350622U, 0x351EC8U, + 0x352F1CU, 0x3537F6U, 0x354CB4U, 0x35545EU, 0x35658AU, 0x357D60U, 0x358BE4U, 0x35930EU, 0x35A2DAU, 0x35BA30U, + 0x35C172U, 0x35D998U, 0x35E84CU, 0x35F0A6U, 0x360388U, 0x361B62U, 0x362AB6U, 0x36325CU, 0x36491EU, 0x3651F4U, + 0x366020U, 0x3678CAU, 0x368E4EU, 0x3696A4U, 0x36A770U, 0x36BF9AU, 0x36C4D8U, 0x36DC32U, 0x36EDE6U, 0x36F50CU, + 0x3700EEU, 0x371804U, 0x3729D0U, 0x37313AU, 0x374A78U, 0x375292U, 0x376346U, 0x377BACU, 0x378D28U, 0x3795C2U, + 0x37A416U, 0x37BCFCU, 0x37C7BEU, 0x37DF54U, 0x37EE80U, 0x37F66AU, 0x380B06U, 0x3813ECU, 0x382238U, 0x383AD2U, + 0x384190U, 0x38597AU, 0x3868AEU, 0x387044U, 0x3886C0U, 0x389E2AU, 0x38AFFEU, 0x38B714U, 0x38CC56U, 0x38D4BCU, + 0x38E568U, 0x38FD82U, 0x390860U, 0x39108AU, 0x39215EU, 0x3939B4U, 0x3942F6U, 0x395A1CU, 0x396BC8U, 0x397322U, + 0x3985A6U, 0x399D4CU, 0x39AC98U, 0x39B472U, 0x39CF30U, 0x39D7DAU, 0x39E60EU, 0x39FEE4U, 0x3A0DCAU, 0x3A1520U, + 0x3A24F4U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB6U, 0x3A6E62U, 0x3A7688U, 0x3A800CU, 0x3A98E6U, 0x3AA932U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD270U, 0x3AE3A4U, 0x3AFB4EU, 0x3B0EACU, 0x3B1646U, 0x3B2792U, 0x3B3F78U, 0x3B443AU, 0x3B5CD0U, + 0x3B6D04U, 0x3B75EEU, 0x3B836AU, 0x3B9B80U, 0x3BAA54U, 0x3BB2BEU, 0x3BC9FCU, 0x3BD116U, 0x3BE0C2U, 0x3BF828U, + 0x3C069EU, 0x3C1E74U, 0x3C2FA0U, 0x3C374AU, 0x3C4C08U, 0x3C54E2U, 0x3C6536U, 0x3C7DDCU, 0x3C8B58U, 0x3C93B2U, + 0x3CA266U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD924U, 0x3CE8F0U, 0x3CF01AU, 0x3D05F8U, 0x3D1D12U, 0x3D2CC6U, 0x3D342CU, + 0x3D4F6EU, 0x3D5784U, 0x3D6650U, 0x3D7EBAU, 0x3D883EU, 0x3D90D4U, 0x3DA100U, 0x3DB9EAU, 0x3DC2A8U, 0x3DDA42U, + 0x3DEB96U, 0x3DF37CU, 0x3E0052U, 0x3E18B8U, 0x3E296CU, 0x3E3186U, 0x3E4AC4U, 0x3E522EU, 0x3E63FAU, 0x3E7B10U, + 0x3E8D94U, 0x3E957EU, 0x3EA4AAU, 0x3EBC40U, 0x3EC702U, 0x3EDFE8U, 0x3EEE3CU, 0x3EF6D6U, 0x3F0334U, 0x3F1BDEU, + 0x3F2A0AU, 0x3F32E0U, 0x3F49A2U, 0x3F5148U, 0x3F609CU, 0x3F7876U, 0x3F8EF2U, 0x3F9618U, 0x3FA7CCU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8EU, 0x3FED5AU, 0x3FF5B0U, 0x40063AU, 0x401ED0U, 0x402F04U, 0x4037EEU, 0x404CACU, 0x405446U, + 0x406592U, 0x407D78U, 0x408BFCU, 0x409316U, 0x40A2C2U, 0x40BA28U, 0x40C16AU, 0x40D980U, 0x40E854U, 0x40F0BEU, + 0x41055CU, 0x411DB6U, 0x412C62U, 0x413488U, 0x414FCAU, 0x415720U, 0x4166F4U, 0x417E1EU, 0x41889AU, 0x419070U, + 0x41A1A4U, 0x41B94EU, 0x41C20CU, 0x41DAE6U, 0x41EB32U, 0x41F3D8U, 0x4200F6U, 0x42181CU, 0x4229C8U, 0x423122U, + 0x424A60U, 0x42528AU, 0x42635EU, 0x427BB4U, 0x428D30U, 0x4295DAU, 0x42A40EU, 0x42BCE4U, 0x42C7A6U, 0x42DF4CU, + 0x42EE98U, 0x42F672U, 0x430390U, 0x431B7AU, 0x432AAEU, 0x433244U, 0x434906U, 0x4351ECU, 0x436038U, 0x4378D2U, + 0x438E56U, 0x4396BCU, 0x43A768U, 0x43BF82U, 0x43C4C0U, 0x43DC2AU, 0x43EDFEU, 0x43F514U, 0x440BA2U, 0x441348U, + 0x44229CU, 0x443A76U, 0x444134U, 0x4459DEU, 0x44680AU, 0x4470E0U, 0x448664U, 0x449E8EU, 0x44AF5AU, 0x44B7B0U, + 0x44CCF2U, 0x44D418U, 0x44E5CCU, 0x44FD26U, 0x4508C4U, 0x45102EU, 0x4521FAU, 0x453910U, 0x454252U, 0x455AB8U, + 0x456B6CU, 0x457386U, 0x458502U, 0x459DE8U, 0x45AC3CU, 0x45B4D6U, 0x45CF94U, 0x45D77EU, 0x45E6AAU, 0x45FE40U, + 0x460D6EU, 0x461584U, 0x462450U, 0x463CBAU, 0x4647F8U, 0x465F12U, 0x466EC6U, 0x46762CU, 0x4680A8U, 0x469842U, + 0x46A996U, 0x46B17CU, 0x46CA3EU, 0x46D2D4U, 0x46E300U, 0x46FBEAU, 0x470E08U, 0x4716E2U, 0x472736U, 0x473FDCU, + 0x47449EU, 0x475C74U, 0x476DA0U, 0x47754AU, 0x4783CEU, 0x479B24U, 0x47AAF0U, 0x47B21AU, 0x47C958U, 0x47D1B2U, + 0x47E066U, 0x47F88CU, 0x4805E0U, 0x481D0AU, 0x482CDEU, 0x483434U, 0x484F76U, 0x48579CU, 0x486648U, 0x487EA2U, + 0x488826U, 0x4890CCU, 0x48A118U, 0x48B9F2U, 0x48C2B0U, 0x48DA5AU, 0x48EB8EU, 0x48F364U, 0x490686U, 0x491E6CU, + 0x492FB8U, 0x493752U, 0x494C10U, 0x4954FAU, 0x49652EU, 0x497DC4U, 0x498B40U, 0x4993AAU, 0x49A27EU, 0x49BA94U, + 0x49C1D6U, 0x49D93CU, 0x49E8E8U, 0x49F002U, 0x4A032CU, 0x4A1BC6U, 0x4A2A12U, 0x4A32F8U, 0x4A49BAU, 0x4A5150U, + 0x4A6084U, 0x4A786EU, 0x4A8EEAU, 0x4A9600U, 0x4AA7D4U, 0x4ABF3EU, 0x4AC47CU, 0x4ADC96U, 0x4AED42U, 0x4AF5A8U, + 0x4B004AU, 0x4B18A0U, 0x4B2974U, 0x4B319EU, 0x4B4ADCU, 0x4B5236U, 0x4B63E2U, 0x4B7B08U, 0x4B8D8CU, 0x4B9566U, + 0x4BA4B2U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF0U, 0x4BEE24U, 0x4BF6CEU, 0x4C0878U, 0x4C1092U, 0x4C2146U, 0x4C39ACU, + 0x4C42EEU, 0x4C5A04U, 0x4C6BD0U, 0x4C733AU, 0x4C85BEU, 0x4C9D54U, 0x4CAC80U, 0x4CB46AU, 0x4CCF28U, 0x4CD7C2U, + 0x4CE616U, 0x4CFEFCU, 0x4D0B1EU, 0x4D13F4U, 0x4D2220U, 0x4D3ACAU, 0x4D4188U, 0x4D5962U, 0x4D68B6U, 0x4D705CU, + 0x4D86D8U, 0x4D9E32U, 0x4DAFE6U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A4U, 0x4DE570U, 0x4DFD9AU, 0x4E0EB4U, 0x4E165EU, + 0x4E278AU, 0x4E3F60U, 0x4E4422U, 0x4E5CC8U, 0x4E6D1CU, 0x4E75F6U, 0x4E8372U, 0x4E9B98U, 0x4EAA4CU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10EU, 0x4EE0DAU, 0x4EF830U, 0x4F0DD2U, 0x4F1538U, 0x4F24ECU, 0x4F3C06U, 0x4F4744U, 0x4F5FAEU, + 0x4F6E7AU, 0x4F7690U, 0x4F8014U, 0x4F98FEU, 0x4FA92AU, 0x4FB1C0U, 0x4FCA82U, 0x4FD268U, 0x4FE3BCU, 0x4FFB56U, + 0x50018EU, 0x501964U, 0x5028B0U, 0x50305AU, 0x504B18U, 0x5053F2U, 0x506226U, 0x507ACCU, 0x508C48U, 0x5094A2U, + 0x50A576U, 0x50BD9CU, 0x50C6DEU, 0x50DE34U, 0x50EFE0U, 0x50F70AU, 0x5102E8U, 0x511A02U, 0x512BD6U, 0x51333CU, + 0x51487EU, 0x515094U, 0x516140U, 0x5179AAU, 0x518F2EU, 0x5197C4U, 0x51A610U, 0x51BEFAU, 0x51C5B8U, 0x51DD52U, + 0x51EC86U, 0x51F46CU, 0x520742U, 0x521FA8U, 0x522E7CU, 0x523696U, 0x524DD4U, 0x52553EU, 0x5264EAU, 0x527C00U, + 0x528A84U, 0x52926EU, 0x52A3BAU, 0x52BB50U, 0x52C012U, 0x52D8F8U, 0x52E92CU, 0x52F1C6U, 0x530424U, 0x531CCEU, + 0x532D1AU, 0x5335F0U, 0x534EB2U, 0x535658U, 0x53678CU, 0x537F66U, 0x5389E2U, 0x539108U, 0x53A0DCU, 0x53B836U, + 0x53C374U, 0x53DB9EU, 0x53EA4AU, 0x53F2A0U, 0x540C16U, 0x5414FCU, 0x542528U, 0x543DC2U, 0x544680U, 0x545E6AU, + 0x546FBEU, 0x547754U, 0x5481D0U, 0x54993AU, 0x54A8EEU, 0x54B004U, 0x54CB46U, 0x54D3ACU, 0x54E278U, 0x54FA92U, + 0x550F70U, 0x55179AU, 0x55264EU, 0x553EA4U, 0x5545E6U, 0x555D0CU, 0x556CD8U, 0x557432U, 0x5582B6U, 0x559A5CU, + 0x55AB88U, 0x55B362U, 0x55C820U, 0x55D0CAU, 0x55E11EU, 0x55F9F4U, 0x560ADAU, 0x561230U, 0x5623E4U, 0x563B0EU, + 0x56404CU, 0x5658A6U, 0x566972U, 0x567198U, 0x56871CU, 0x569FF6U, 0x56AE22U, 0x56B6C8U, 0x56CD8AU, 0x56D560U, + 0x56E4B4U, 0x56FC5EU, 0x5709BCU, 0x571156U, 0x572082U, 0x573868U, 0x57432AU, 0x575BC0U, 0x576A14U, 0x5772FEU, + 0x57847AU, 0x579C90U, 0x57AD44U, 0x57B5AEU, 0x57CEECU, 0x57D606U, 0x57E7D2U, 0x57FF38U, 0x580254U, 0x581ABEU, + 0x582B6AU, 0x583380U, 0x5848C2U, 0x585028U, 0x5861FCU, 0x587916U, 0x588F92U, 0x589778U, 0x58A6ACU, 0x58BE46U, + 0x58C504U, 0x58DDEEU, 0x58EC3AU, 0x58F4D0U, 0x590132U, 0x5919D8U, 0x59280CU, 0x5930E6U, 0x594BA4U, 0x59534EU, + 0x59629AU, 0x597A70U, 0x598CF4U, 0x59941EU, 0x59A5CAU, 0x59BD20U, 0x59C662U, 0x59DE88U, 0x59EF5CU, 0x59F7B6U, + 0x5A0498U, 0x5A1C72U, 0x5A2DA6U, 0x5A354CU, 0x5A4E0EU, 0x5A56E4U, 0x5A6730U, 0x5A7FDAU, 0x5A895EU, 0x5A91B4U, + 0x5AA060U, 0x5AB88AU, 0x5AC3C8U, 0x5ADB22U, 0x5AEAF6U, 0x5AF21CU, 0x5B07FEU, 0x5B1F14U, 0x5B2EC0U, 0x5B362AU, + 0x5B4D68U, 0x5B5582U, 0x5B6456U, 0x5B7CBCU, 0x5B8A38U, 0x5B92D2U, 0x5BA306U, 0x5BBBECU, 0x5BC0AEU, 0x5BD844U, + 0x5BE990U, 0x5BF17AU, 0x5C0FCCU, 0x5C1726U, 0x5C26F2U, 0x5C3E18U, 0x5C455AU, 0x5C5DB0U, 0x5C6C64U, 0x5C748EU, + 0x5C820AU, 0x5C9AE0U, 0x5CAB34U, 0x5CB3DEU, 0x5CC89CU, 0x5CD076U, 0x5CE1A2U, 0x5CF948U, 0x5D0CAAU, 0x5D1440U, + 0x5D2594U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED6U, 0x5D6F02U, 0x5D77E8U, 0x5D816CU, 0x5D9986U, 0x5DA852U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD310U, 0x5DE2C4U, 0x5DFA2EU, 0x5E0900U, 0x5E11EAU, 0x5E203EU, 0x5E38D4U, 0x5E4396U, 0x5E5B7CU, + 0x5E6AA8U, 0x5E7242U, 0x5E84C6U, 0x5E9C2CU, 0x5EADF8U, 0x5EB512U, 0x5ECE50U, 0x5ED6BAU, 0x5EE76EU, 0x5EFF84U, + 0x5F0A66U, 0x5F128CU, 0x5F2358U, 0x5F3BB2U, 0x5F40F0U, 0x5F581AU, 0x5F69CEU, 0x5F7124U, 0x5F87A0U, 0x5F9F4AU, + 0x5FAE9EU, 0x5FB674U, 0x5FCD36U, 0x5FD5DCU, 0x5FE408U, 0x5FFCE2U, 0x600952U, 0x6011B8U, 0x60206CU, 0x603886U, + 0x6043C4U, 0x605B2EU, 0x606AFAU, 0x607210U, 0x608494U, 0x609C7EU, 0x60ADAAU, 0x60B540U, 0x60CE02U, 0x60D6E8U, + 0x60E73CU, 0x60FFD6U, 0x610A34U, 0x6112DEU, 0x61230AU, 0x613BE0U, 0x6140A2U, 0x615848U, 0x61699CU, 0x617176U, + 0x6187F2U, 0x619F18U, 0x61AECCU, 0x61B626U, 0x61CD64U, 0x61D58EU, 0x61E45AU, 0x61FCB0U, 0x620F9EU, 0x621774U, + 0x6226A0U, 0x623E4AU, 0x624508U, 0x625DE2U, 0x626C36U, 0x6274DCU, 0x628258U, 0x629AB2U, 0x62AB66U, 0x62B38CU, + 0x62C8CEU, 0x62D024U, 0x62E1F0U, 0x62F91AU, 0x630CF8U, 0x631412U, 0x6325C6U, 0x633D2CU, 0x63466EU, 0x635E84U, + 0x636F50U, 0x6377BAU, 0x63813EU, 0x6399D4U, 0x63A800U, 0x63B0EAU, 0x63CBA8U, 0x63D342U, 0x63E296U, 0x63FA7CU, + 0x6404CAU, 0x641C20U, 0x642DF4U, 0x64351EU, 0x644E5CU, 0x6456B6U, 0x646762U, 0x647F88U, 0x64890CU, 0x6491E6U, + 0x64A032U, 0x64B8D8U, 0x64C39AU, 0x64DB70U, 0x64EAA4U, 0x64F24EU, 0x6507ACU, 0x651F46U, 0x652E92U, 0x653678U, + 0x654D3AU, 0x6555D0U, 0x656404U, 0x657CEEU, 0x658A6AU, 0x659280U, 0x65A354U, 0x65BBBEU, 0x65C0FCU, 0x65D816U, + 0x65E9C2U, 0x65F128U, 0x660206U, 0x661AECU, 0x662B38U, 0x6633D2U, 0x664890U, 0x66507AU, 0x6661AEU, 0x667944U, + 0x668FC0U, 0x66972AU, 0x66A6FEU, 0x66BE14U, 0x66C556U, 0x66DDBCU, 0x66EC68U, 0x66F482U, 0x670160U, 0x67198AU, + 0x67285EU, 0x6730B4U, 0x674BF6U, 0x67531CU, 0x6762C8U, 0x677A22U, 0x678CA6U, 0x67944CU, 0x67A598U, 0x67BD72U, + 0x67C630U, 0x67DEDAU, 0x67EF0EU, 0x67F7E4U, 0x680A88U, 0x681262U, 0x6823B6U, 0x683B5CU, 0x68401EU, 0x6858F4U, + 0x686920U, 0x6871CAU, 0x68874EU, 0x689FA4U, 0x68AE70U, 0x68B69AU, 0x68CDD8U, 0x68D532U, 0x68E4E6U, 0x68FC0CU, + 0x6909EEU, 0x691104U, 0x6920D0U, 0x69383AU, 0x694378U, 0x695B92U, 0x696A46U, 0x6972ACU, 0x698428U, 0x699CC2U, + 0x69AD16U, 0x69B5FCU, 0x69CEBEU, 0x69D654U, 0x69E780U, 0x69FF6AU, 0x6A0C44U, 0x6A14AEU, 0x6A257AU, 0x6A3D90U, + 0x6A46D2U, 0x6A5E38U, 0x6A6FECU, 0x6A7706U, 0x6A8182U, 0x6A9968U, 0x6AA8BCU, 0x6AB056U, 0x6ACB14U, 0x6AD3FEU, + 0x6AE22AU, 0x6AFAC0U, 0x6B0F22U, 0x6B17C8U, 0x6B261CU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5EU, 0x6B6C8AU, 0x6B7460U, + 0x6B82E4U, 0x6B9A0EU, 0x6BABDAU, 0x6BB330U, 0x6BC872U, 0x6BD098U, 0x6BE14CU, 0x6BF9A6U, 0x6C0710U, 0x6C1FFAU, + 0x6C2E2EU, 0x6C36C4U, 0x6C4D86U, 0x6C556CU, 0x6C64B8U, 0x6C7C52U, 0x6C8AD6U, 0x6C923CU, 0x6CA3E8U, 0x6CBB02U, + 0x6CC040U, 0x6CD8AAU, 0x6CE97EU, 0x6CF194U, 0x6D0476U, 0x6D1C9CU, 0x6D2D48U, 0x6D35A2U, 0x6D4EE0U, 0x6D560AU, + 0x6D67DEU, 0x6D7F34U, 0x6D89B0U, 0x6D915AU, 0x6DA08EU, 0x6DB864U, 0x6DC326U, 0x6DDBCCU, 0x6DEA18U, 0x6DF2F2U, + 0x6E01DCU, 0x6E1936U, 0x6E28E2U, 0x6E3008U, 0x6E4B4AU, 0x6E53A0U, 0x6E6274U, 0x6E7A9EU, 0x6E8C1AU, 0x6E94F0U, + 0x6EA524U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE66U, 0x6EEFB2U, 0x6EF758U, 0x6F02BAU, 0x6F1A50U, 0x6F2B84U, 0x6F336EU, + 0x6F482CU, 0x6F50C6U, 0x6F6112U, 0x6F79F8U, 0x6F8F7CU, 0x6F9796U, 0x6FA642U, 0x6FBEA8U, 0x6FC5EAU, 0x6FDD00U, + 0x6FECD4U, 0x6FF43EU, 0x700EE6U, 0x70160CU, 0x7027D8U, 0x703F32U, 0x704470U, 0x705C9AU, 0x706D4EU, 0x7075A4U, + 0x708320U, 0x709BCAU, 0x70AA1EU, 0x70B2F4U, 0x70C9B6U, 0x70D15CU, 0x70E088U, 0x70F862U, 0x710D80U, 0x71156AU, + 0x7124BEU, 0x713C54U, 0x714716U, 0x715FFCU, 0x716E28U, 0x7176C2U, 0x718046U, 0x7198ACU, 0x71A978U, 0x71B192U, + 0x71CAD0U, 0x71D23AU, 0x71E3EEU, 0x71FB04U, 0x72082AU, 0x7210C0U, 0x722114U, 0x7239FEU, 0x7242BCU, 0x725A56U, + 0x726B82U, 0x727368U, 0x7285ECU, 0x729D06U, 0x72ACD2U, 0x72B438U, 0x72CF7AU, 0x72D790U, 0x72E644U, 0x72FEAEU, + 0x730B4CU, 0x7313A6U, 0x732272U, 0x733A98U, 0x7341DAU, 0x735930U, 0x7368E4U, 0x73700EU, 0x73868AU, 0x739E60U, + 0x73AFB4U, 0x73B75EU, 0x73CC1CU, 0x73D4F6U, 0x73E522U, 0x73FDC8U, 0x74037EU, 0x741B94U, 0x742A40U, 0x7432AAU, + 0x7449E8U, 0x745102U, 0x7460D6U, 0x74783CU, 0x748EB8U, 0x749652U, 0x74A786U, 0x74BF6CU, 0x74C42EU, 0x74DCC4U, + 0x74ED10U, 0x74F5FAU, 0x750018U, 0x7518F2U, 0x752926U, 0x7531CCU, 0x754A8EU, 0x755264U, 0x7563B0U, 0x757B5AU, + 0x758DDEU, 0x759534U, 0x75A4E0U, 0x75BC0AU, 0x75C748U, 0x75DFA2U, 0x75EE76U, 0x75F69CU, 0x7605B2U, 0x761D58U, + 0x762C8CU, 0x763466U, 0x764F24U, 0x7657CEU, 0x76661AU, 0x767EF0U, 0x768874U, 0x76909EU, 0x76A14AU, 0x76B9A0U, + 0x76C2E2U, 0x76DA08U, 0x76EBDCU, 0x76F336U, 0x7706D4U, 0x771E3EU, 0x772FEAU, 0x773700U, 0x774C42U, 0x7754A8U, + 0x77657CU, 0x777D96U, 0x778B12U, 0x7793F8U, 0x77A22CU, 0x77BAC6U, 0x77C184U, 0x77D96EU, 0x77E8BAU, 0x77F050U, + 0x780D3CU, 0x7815D6U, 0x782402U, 0x783CE8U, 0x7847AAU, 0x785F40U, 0x786E94U, 0x78767EU, 0x7880FAU, 0x789810U, + 0x78A9C4U, 0x78B12EU, 0x78CA6CU, 0x78D286U, 0x78E352U, 0x78FBB8U, 0x790E5AU, 0x7916B0U, 0x792764U, 0x793F8EU, + 0x7944CCU, 0x795C26U, 0x796DF2U, 0x797518U, 0x79839CU, 0x799B76U, 0x79AAA2U, 0x79B248U, 0x79C90AU, 0x79D1E0U, + 0x79E034U, 0x79F8DEU, 0x7A0BF0U, 0x7A131AU, 0x7A22CEU, 0x7A3A24U, 0x7A4166U, 0x7A598CU, 0x7A6858U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDCU, 0x7AAF08U, 0x7AB7E2U, 0x7ACCA0U, 0x7AD44AU, 0x7AE59EU, 0x7AFD74U, 0x7B0896U, 0x7B107CU, + 0x7B21A8U, 0x7B3942U, 0x7B4200U, 0x7B5AEAU, 0x7B6B3EU, 0x7B73D4U, 0x7B8550U, 0x7B9DBAU, 0x7BAC6EU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72CU, 0x7BE6F8U, 0x7BFE12U, 0x7C00A4U, 0x7C184EU, 0x7C299AU, 0x7C3170U, 0x7C4A32U, 0x7C52D8U, + 0x7C630CU, 0x7C7BE6U, 0x7C8D62U, 0x7C9588U, 0x7CA45CU, 0x7CBCB6U, 0x7CC7F4U, 0x7CDF1EU, 0x7CEECAU, 0x7CF620U, + 0x7D03C2U, 0x7D1B28U, 0x7D2AFCU, 0x7D3216U, 0x7D4954U, 0x7D51BEU, 0x7D606AU, 0x7D7880U, 0x7D8E04U, 0x7D96EEU, + 0x7DA73AU, 0x7DBFD0U, 0x7DC492U, 0x7DDC78U, 0x7DEDACU, 0x7DF546U, 0x7E0668U, 0x7E1E82U, 0x7E2F56U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5414U, 0x7E65C0U, 0x7E7D2AU, 0x7E8BAEU, 0x7E9344U, 0x7EA290U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D2U, + 0x7EE806U, 0x7EF0ECU, 0x7F050EU, 0x7F1DE4U, 0x7F2C30U, 0x7F34DAU, 0x7F4F98U, 0x7F5772U, 0x7F66A6U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9022U, 0x7FA1F6U, 0x7FB91CU, 0x7FC25EU, 0x7FDAB4U, 0x7FEB60U, 0x7FF38AU, 0x800C74U, 0x80149EU, + 0x80254AU, 0x803DA0U, 0x8046E2U, 0x805E08U, 0x806FDCU, 0x807736U, 0x8081B2U, 0x809958U, 0x80A88CU, 0x80B066U, + 0x80CB24U, 0x80D3CEU, 0x80E21AU, 0x80FAF0U, 0x810F12U, 0x8117F8U, 0x81262CU, 0x813EC6U, 0x814584U, 0x815D6EU, + 0x816CBAU, 0x817450U, 0x8182D4U, 0x819A3EU, 0x81ABEAU, 0x81B300U, 0x81C842U, 0x81D0A8U, 0x81E17CU, 0x81F996U, + 0x820AB8U, 0x821252U, 0x822386U, 0x823B6CU, 0x82402EU, 0x8258C4U, 0x826910U, 0x8271FAU, 0x82877EU, 0x829F94U, + 0x82AE40U, 0x82B6AAU, 0x82CDE8U, 0x82D502U, 0x82E4D6U, 0x82FC3CU, 0x8309DEU, 0x831134U, 0x8320E0U, 0x83380AU, + 0x834348U, 0x835BA2U, 0x836A76U, 0x83729CU, 0x838418U, 0x839CF2U, 0x83AD26U, 0x83B5CCU, 0x83CE8EU, 0x83D664U, + 0x83E7B0U, 0x83FF5AU, 0x8401ECU, 0x841906U, 0x8428D2U, 0x843038U, 0x844B7AU, 0x845390U, 0x846244U, 0x847AAEU, + 0x848C2AU, 0x8494C0U, 0x84A514U, 0x84BDFEU, 0x84C6BCU, 0x84DE56U, 0x84EF82U, 0x84F768U, 0x85028AU, 0x851A60U, + 0x852BB4U, 0x85335EU, 0x85481CU, 0x8550F6U, 0x856122U, 0x8579C8U, 0x858F4CU, 0x8597A6U, 0x85A672U, 0x85BE98U, + 0x85C5DAU, 0x85DD30U, 0x85ECE4U, 0x85F40EU, 0x860720U, 0x861FCAU, 0x862E1EU, 0x8636F4U, 0x864DB6U, 0x86555CU, + 0x866488U, 0x867C62U, 0x868AE6U, 0x86920CU, 0x86A3D8U, 0x86BB32U, 0x86C070U, 0x86D89AU, 0x86E94EU, 0x86F1A4U, + 0x870446U, 0x871CACU, 0x872D78U, 0x873592U, 0x874ED0U, 0x87563AU, 0x8767EEU, 0x877F04U, 0x878980U, 0x87916AU, + 0x87A0BEU, 0x87B854U, 0x87C316U, 0x87DBFCU, 0x87EA28U, 0x87F2C2U, 0x880FAEU, 0x881744U, 0x882690U, 0x883E7AU, + 0x884538U, 0x885DD2U, 0x886C06U, 0x8874ECU, 0x888268U, 0x889A82U, 0x88AB56U, 0x88B3BCU, 0x88C8FEU, 0x88D014U, + 0x88E1C0U, 0x88F92AU, 0x890CC8U, 0x891422U, 0x8925F6U, 0x893D1CU, 0x89465EU, 0x895EB4U, 0x896F60U, 0x89778AU, + 0x89810EU, 0x8999E4U, 0x89A830U, 0x89B0DAU, 0x89CB98U, 0x89D372U, 0x89E2A6U, 0x89FA4CU, 0x8A0962U, 0x8A1188U, + 0x8A205CU, 0x8A38B6U, 0x8A43F4U, 0x8A5B1EU, 0x8A6ACAU, 0x8A7220U, 0x8A84A4U, 0x8A9C4EU, 0x8AAD9AU, 0x8AB570U, + 0x8ACE32U, 0x8AD6D8U, 0x8AE70CU, 0x8AFFE6U, 0x8B0A04U, 0x8B12EEU, 0x8B233AU, 0x8B3BD0U, 0x8B4092U, 0x8B5878U, + 0x8B69ACU, 0x8B7146U, 0x8B87C2U, 0x8B9F28U, 0x8BAEFCU, 0x8BB616U, 0x8BCD54U, 0x8BD5BEU, 0x8BE46AU, 0x8BFC80U, + 0x8C0236U, 0x8C1ADCU, 0x8C2B08U, 0x8C33E2U, 0x8C48A0U, 0x8C504AU, 0x8C619EU, 0x8C7974U, 0x8C8FF0U, 0x8C971AU, + 0x8CA6CEU, 0x8CBE24U, 0x8CC566U, 0x8CDD8CU, 0x8CEC58U, 0x8CF4B2U, 0x8D0150U, 0x8D19BAU, 0x8D286EU, 0x8D3084U, + 0x8D4BC6U, 0x8D532CU, 0x8D62F8U, 0x8D7A12U, 0x8D8C96U, 0x8D947CU, 0x8DA5A8U, 0x8DBD42U, 0x8DC600U, 0x8DDEEAU, + 0x8DEF3EU, 0x8DF7D4U, 0x8E04FAU, 0x8E1C10U, 0x8E2DC4U, 0x8E352EU, 0x8E4E6CU, 0x8E5686U, 0x8E6752U, 0x8E7FB8U, + 0x8E893CU, 0x8E91D6U, 0x8EA002U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB40U, 0x8EEA94U, 0x8EF27EU, 0x8F079CU, 0x8F1F76U, + 0x8F2EA2U, 0x8F3648U, 0x8F4D0AU, 0x8F55E0U, 0x8F6434U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B0U, 0x8FA364U, 0x8FBB8EU, + 0x8FC0CCU, 0x8FD826U, 0x8FE9F2U, 0x8FF118U, 0x900BC0U, 0x90132AU, 0x9022FEU, 0x903A14U, 0x904156U, 0x9059BCU, + 0x906868U, 0x907082U, 0x908606U, 0x909EECU, 0x90AF38U, 0x90B7D2U, 0x90CC90U, 0x90D47AU, 0x90E5AEU, 0x90FD44U, + 0x9108A6U, 0x91104CU, 0x912198U, 0x913972U, 0x914230U, 0x915ADAU, 0x916B0EU, 0x9173E4U, 0x918560U, 0x919D8AU, + 0x91AC5EU, 0x91B4B4U, 0x91CFF6U, 0x91D71CU, 0x91E6C8U, 0x91FE22U, 0x920D0CU, 0x9215E6U, 0x922432U, 0x923CD8U, + 0x92479AU, 0x925F70U, 0x926EA4U, 0x92764EU, 0x9280CAU, 0x929820U, 0x92A9F4U, 0x92B11EU, 0x92CA5CU, 0x92D2B6U, + 0x92E362U, 0x92FB88U, 0x930E6AU, 0x931680U, 0x932754U, 0x933FBEU, 0x9344FCU, 0x935C16U, 0x936DC2U, 0x937528U, + 0x9383ACU, 0x939B46U, 0x93AA92U, 0x93B278U, 0x93C93AU, 0x93D1D0U, 0x93E004U, 0x93F8EEU, 0x940658U, 0x941EB2U, + 0x942F66U, 0x94378CU, 0x944CCEU, 0x945424U, 0x9465F0U, 0x947D1AU, 0x948B9EU, 0x949374U, 0x94A2A0U, 0x94BA4AU, + 0x94C108U, 0x94D9E2U, 0x94E836U, 0x94F0DCU, 0x95053EU, 0x951DD4U, 0x952C00U, 0x9534EAU, 0x954FA8U, 0x955742U, + 0x956696U, 0x957E7CU, 0x9588F8U, 0x959012U, 0x95A1C6U, 0x95B92CU, 0x95C26EU, 0x95DA84U, 0x95EB50U, 0x95F3BAU, + 0x960094U, 0x96187EU, 0x9629AAU, 0x963140U, 0x964A02U, 0x9652E8U, 0x96633CU, 0x967BD6U, 0x968D52U, 0x9695B8U, + 0x96A46CU, 0x96BC86U, 0x96C7C4U, 0x96DF2EU, 0x96EEFAU, 0x96F610U, 0x9703F2U, 0x971B18U, 0x972ACCU, 0x973226U, + 0x974964U, 0x97518EU, 0x97605AU, 0x9778B0U, 0x978E34U, 0x9796DEU, 0x97A70AU, 0x97BFE0U, 0x97C4A2U, 0x97DC48U, + 0x97ED9CU, 0x97F576U, 0x98081AU, 0x9810F0U, 0x982124U, 0x9839CEU, 0x98428CU, 0x985A66U, 0x986BB2U, 0x987358U, + 0x9885DCU, 0x989D36U, 0x98ACE2U, 0x98B408U, 0x98CF4AU, 0x98D7A0U, 0x98E674U, 0x98FE9EU, 0x990B7CU, 0x991396U, + 0x992242U, 0x993AA8U, 0x9941EAU, 0x995900U, 0x9968D4U, 0x99703EU, 0x9986BAU, 0x999E50U, 0x99AF84U, 0x99B76EU, + 0x99CC2CU, 0x99D4C6U, 0x99E512U, 0x99FDF8U, 0x9A0ED6U, 0x9A163CU, 0x9A27E8U, 0x9A3F02U, 0x9A4440U, 0x9A5CAAU, + 0x9A6D7EU, 0x9A7594U, 0x9A8310U, 0x9A9BFAU, 0x9AAA2EU, 0x9AB2C4U, 0x9AC986U, 0x9AD16CU, 0x9AE0B8U, 0x9AF852U, + 0x9B0DB0U, 0x9B155AU, 0x9B248EU, 0x9B3C64U, 0x9B4726U, 0x9B5FCCU, 0x9B6E18U, 0x9B76F2U, 0x9B8076U, 0x9B989CU, + 0x9BA948U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20AU, 0x9BE3DEU, 0x9BFB34U, 0x9C0582U, 0x9C1D68U, 0x9C2CBCU, 0x9C3456U, + 0x9C4F14U, 0x9C57FEU, 0x9C662AU, 0x9C7EC0U, 0x9C8844U, 0x9C90AEU, 0x9CA17AU, 0x9CB990U, 0x9CC2D2U, 0x9CDA38U, + 0x9CEBECU, 0x9CF306U, 0x9D06E4U, 0x9D1E0EU, 0x9D2FDAU, 0x9D3730U, 0x9D4C72U, 0x9D5498U, 0x9D654CU, 0x9D7DA6U, + 0x9D8B22U, 0x9D93C8U, 0x9DA21CU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95EU, 0x9DE88AU, 0x9DF060U, 0x9E034EU, 0x9E1BA4U, + 0x9E2A70U, 0x9E329AU, 0x9E49D8U, 0x9E5132U, 0x9E60E6U, 0x9E780CU, 0x9E8E88U, 0x9E9662U, 0x9EA7B6U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF4U, 0x9EED20U, 0x9EF5CAU, 0x9F0028U, 0x9F18C2U, 0x9F2916U, 0x9F31FCU, 0x9F4ABEU, 0x9F5254U, + 0x9F6380U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9504U, 0x9FA4D0U, 0x9FBC3AU, 0x9FC778U, 0x9FDF92U, 0x9FEE46U, 0x9FF6ACU, + 0xA0031CU, 0xA01BF6U, 0xA02A22U, 0xA032C8U, 0xA0498AU, 0xA05160U, 0xA060B4U, 0xA0785EU, 0xA08EDAU, 0xA09630U, + 0xA0A7E4U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA6U, 0xA0ED72U, 0xA0F598U, 0xA1007AU, 0xA11890U, 0xA12944U, 0xA131AEU, + 0xA14AECU, 0xA15206U, 0xA163D2U, 0xA17B38U, 0xA18DBCU, 0xA19556U, 0xA1A482U, 0xA1BC68U, 0xA1C72AU, 0xA1DFC0U, + 0xA1EE14U, 0xA1F6FEU, 0xA205D0U, 0xA21D3AU, 0xA22CEEU, 0xA23404U, 0xA24F46U, 0xA257ACU, 0xA26678U, 0xA27E92U, + 0xA28816U, 0xA290FCU, 0xA2A128U, 0xA2B9C2U, 0xA2C280U, 0xA2DA6AU, 0xA2EBBEU, 0xA2F354U, 0xA306B6U, 0xA31E5CU, + 0xA32F88U, 0xA33762U, 0xA34C20U, 0xA354CAU, 0xA3651EU, 0xA37DF4U, 0xA38B70U, 0xA3939AU, 0xA3A24EU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90CU, 0xA3E8D8U, 0xA3F032U, 0xA40E84U, 0xA4166EU, 0xA427BAU, 0xA43F50U, 0xA44412U, 0xA45CF8U, + 0xA46D2CU, 0xA475C6U, 0xA48342U, 0xA49BA8U, 0xA4AA7CU, 0xA4B296U, 0xA4C9D4U, 0xA4D13EU, 0xA4E0EAU, 0xA4F800U, + 0xA50DE2U, 0xA51508U, 0xA524DCU, 0xA53C36U, 0xA54774U, 0xA55F9EU, 0xA56E4AU, 0xA576A0U, 0xA58024U, 0xA598CEU, + 0xA5A91AU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D258U, 0xA5E38CU, 0xA5FB66U, 0xA60848U, 0xA610A2U, 0xA62176U, 0xA6399CU, + 0xA642DEU, 0xA65A34U, 0xA66BE0U, 0xA6730AU, 0xA6858EU, 0xA69D64U, 0xA6ACB0U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F2U, + 0xA6E626U, 0xA6FECCU, 0xA70B2EU, 0xA713C4U, 0xA72210U, 0xA73AFAU, 0xA741B8U, 0xA75952U, 0xA76886U, 0xA7706CU, + 0xA786E8U, 0xA79E02U, 0xA7AFD6U, 0xA7B73CU, 0xA7CC7EU, 0xA7D494U, 0xA7E540U, 0xA7FDAAU, 0xA800C6U, 0xA8182CU, + 0xA829F8U, 0xA83112U, 0xA84A50U, 0xA852BAU, 0xA8636EU, 0xA87B84U, 0xA88D00U, 0xA895EAU, 0xA8A43EU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7CU, 0xA8EEA8U, 0xA8F642U, 0xA903A0U, 0xA91B4AU, 0xA92A9EU, 0xA93274U, 0xA94936U, 0xA951DCU, + 0xA96008U, 0xA978E2U, 0xA98E66U, 0xA9968CU, 0xA9A758U, 0xA9BFB2U, 0xA9C4F0U, 0xA9DC1AU, 0xA9EDCEU, 0xA9F524U, + 0xAA060AU, 0xAA1EE0U, 0xAA2F34U, 0xAA37DEU, 0xAA4C9CU, 0xAA5476U, 0xAA65A2U, 0xAA7D48U, 0xAA8BCCU, 0xAA9326U, + 0xAAA2F2U, 0xAABA18U, 0xAAC15AU, 0xAAD9B0U, 0xAAE864U, 0xAAF08EU, 0xAB056CU, 0xAB1D86U, 0xAB2C52U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5710U, 0xAB66C4U, 0xAB7E2EU, 0xAB88AAU, 0xAB9040U, 0xABA194U, 0xABB97EU, 0xABC23CU, 0xABDAD6U, + 0xABEB02U, 0xABF3E8U, 0xAC0D5EU, 0xAC15B4U, 0xAC2460U, 0xAC3C8AU, 0xAC47C8U, 0xAC5F22U, 0xAC6EF6U, 0xAC761CU, + 0xAC8098U, 0xAC9872U, 0xACA9A6U, 0xACB14CU, 0xACCA0EU, 0xACD2E4U, 0xACE330U, 0xACFBDAU, 0xAD0E38U, 0xAD16D2U, + 0xAD2706U, 0xAD3FECU, 0xAD44AEU, 0xAD5C44U, 0xAD6D90U, 0xAD757AU, 0xAD83FEU, 0xAD9B14U, 0xADAAC0U, 0xADB22AU, + 0xADC968U, 0xADD182U, 0xADE056U, 0xADF8BCU, 0xAE0B92U, 0xAE1378U, 0xAE22ACU, 0xAE3A46U, 0xAE4104U, 0xAE59EEU, + 0xAE683AU, 0xAE70D0U, 0xAE8654U, 0xAE9EBEU, 0xAEAF6AU, 0xAEB780U, 0xAECCC2U, 0xAED428U, 0xAEE5FCU, 0xAEFD16U, + 0xAF08F4U, 0xAF101EU, 0xAF21CAU, 0xAF3920U, 0xAF4262U, 0xAF5A88U, 0xAF6B5CU, 0xAF73B6U, 0xAF8532U, 0xAF9DD8U, + 0xAFAC0CU, 0xAFB4E6U, 0xAFCFA4U, 0xAFD74EU, 0xAFE69AU, 0xAFFE70U, 0xB004A8U, 0xB01C42U, 0xB02D96U, 0xB0357CU, + 0xB04E3EU, 0xB056D4U, 0xB06700U, 0xB07FEAU, 0xB0896EU, 0xB09184U, 0xB0A050U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB12U, + 0xB0EAC6U, 0xB0F22CU, 0xB107CEU, 0xB11F24U, 0xB12EF0U, 0xB1361AU, 0xB14D58U, 0xB155B2U, 0xB16466U, 0xB17C8CU, + 0xB18A08U, 0xB192E2U, 0xB1A336U, 0xB1BBDCU, 0xB1C09EU, 0xB1D874U, 0xB1E9A0U, 0xB1F14AU, 0xB20264U, 0xB21A8EU, + 0xB22B5AU, 0xB233B0U, 0xB248F2U, 0xB25018U, 0xB261CCU, 0xB27926U, 0xB28FA2U, 0xB29748U, 0xB2A69CU, 0xB2BE76U, + 0xB2C534U, 0xB2DDDEU, 0xB2EC0AU, 0xB2F4E0U, 0xB30102U, 0xB319E8U, 0xB3283CU, 0xB330D6U, 0xB34B94U, 0xB3537EU, + 0xB362AAU, 0xB37A40U, 0xB38CC4U, 0xB3942EU, 0xB3A5FAU, 0xB3BD10U, 0xB3C652U, 0xB3DEB8U, 0xB3EF6CU, 0xB3F786U, + 0xB40930U, 0xB411DAU, 0xB4200EU, 0xB438E4U, 0xB443A6U, 0xB45B4CU, 0xB46A98U, 0xB47272U, 0xB484F6U, 0xB49C1CU, + 0xB4ADC8U, 0xB4B522U, 0xB4CE60U, 0xB4D68AU, 0xB4E75EU, 0xB4FFB4U, 0xB50A56U, 0xB512BCU, 0xB52368U, 0xB53B82U, + 0xB540C0U, 0xB5582AU, 0xB569FEU, 0xB57114U, 0xB58790U, 0xB59F7AU, 0xB5AEAEU, 0xB5B644U, 0xB5CD06U, 0xB5D5ECU, + 0xB5E438U, 0xB5FCD2U, 0xB60FFCU, 0xB61716U, 0xB626C2U, 0xB63E28U, 0xB6456AU, 0xB65D80U, 0xB66C54U, 0xB674BEU, + 0xB6823AU, 0xB69AD0U, 0xB6AB04U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D046U, 0xB6E192U, 0xB6F978U, 0xB70C9AU, 0xB71470U, + 0xB725A4U, 0xB73D4EU, 0xB7460CU, 0xB75EE6U, 0xB76F32U, 0xB777D8U, 0xB7815CU, 0xB799B6U, 0xB7A862U, 0xB7B088U, + 0xB7CBCAU, 0xB7D320U, 0xB7E2F4U, 0xB7FA1EU, 0xB80772U, 0xB81F98U, 0xB82E4CU, 0xB836A6U, 0xB84DE4U, 0xB8550EU, + 0xB864DAU, 0xB87C30U, 0xB88AB4U, 0xB8925EU, 0xB8A38AU, 0xB8BB60U, 0xB8C022U, 0xB8D8C8U, 0xB8E91CU, 0xB8F1F6U, + 0xB90414U, 0xB91CFEU, 0xB92D2AU, 0xB935C0U, 0xB94E82U, 0xB95668U, 0xB967BCU, 0xB97F56U, 0xB989D2U, 0xB99138U, + 0xB9A0ECU, 0xB9B806U, 0xB9C344U, 0xB9DBAEU, 0xB9EA7AU, 0xB9F290U, 0xBA01BEU, 0xBA1954U, 0xBA2880U, 0xBA306AU, + 0xBA4B28U, 0xBA53C2U, 0xBA6216U, 0xBA7AFCU, 0xBA8C78U, 0xBA9492U, 0xBAA546U, 0xBABDACU, 0xBAC6EEU, 0xBADE04U, + 0xBAEFD0U, 0xBAF73AU, 0xBB02D8U, 0xBB1A32U, 0xBB2BE6U, 0xBB330CU, 0xBB484EU, 0xBB50A4U, 0xBB6170U, 0xBB799AU, + 0xBB8F1EU, 0xBB97F4U, 0xBBA620U, 0xBBBECAU, 0xBBC588U, 0xBBDD62U, 0xBBECB6U, 0xBBF45CU, 0xBC0AEAU, 0xBC1200U, + 0xBC23D4U, 0xBC3B3EU, 0xBC407CU, 0xBC5896U, 0xBC6942U, 0xBC71A8U, 0xBC872CU, 0xBC9FC6U, 0xBCAE12U, 0xBCB6F8U, + 0xBCCDBAU, 0xBCD550U, 0xBCE484U, 0xBCFC6EU, 0xBD098CU, 0xBD1166U, 0xBD20B2U, 0xBD3858U, 0xBD431AU, 0xBD5BF0U, + 0xBD6A24U, 0xBD72CEU, 0xBD844AU, 0xBD9CA0U, 0xBDAD74U, 0xBDB59EU, 0xBDCEDCU, 0xBDD636U, 0xBDE7E2U, 0xBDFF08U, + 0xBE0C26U, 0xBE14CCU, 0xBE2518U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5AU, 0xBE6F8EU, 0xBE7764U, 0xBE81E0U, 0xBE990AU, + 0xBEA8DEU, 0xBEB034U, 0xBECB76U, 0xBED39CU, 0xBEE248U, 0xBEFAA2U, 0xBF0F40U, 0xBF17AAU, 0xBF267EU, 0xBF3E94U, + 0xBF45D6U, 0xBF5D3CU, 0xBF6CE8U, 0xBF7402U, 0xBF8286U, 0xBF9A6CU, 0xBFABB8U, 0xBFB352U, 0xBFC810U, 0xBFD0FAU, + 0xBFE12EU, 0xBFF9C4U, 0xC00A4EU, 0xC012A4U, 0xC02370U, 0xC03B9AU, 0xC040D8U, 0xC05832U, 0xC069E6U, 0xC0710CU, + 0xC08788U, 0xC09F62U, 0xC0AEB6U, 0xC0B65CU, 0xC0CD1EU, 0xC0D5F4U, 0xC0E420U, 0xC0FCCAU, 0xC10928U, 0xC111C2U, + 0xC12016U, 0xC138FCU, 0xC143BEU, 0xC15B54U, 0xC16A80U, 0xC1726AU, 0xC184EEU, 0xC19C04U, 0xC1ADD0U, 0xC1B53AU, + 0xC1CE78U, 0xC1D692U, 0xC1E746U, 0xC1FFACU, 0xC20C82U, 0xC21468U, 0xC225BCU, 0xC23D56U, 0xC24614U, 0xC25EFEU, + 0xC26F2AU, 0xC277C0U, 0xC28144U, 0xC299AEU, 0xC2A87AU, 0xC2B090U, 0xC2CBD2U, 0xC2D338U, 0xC2E2ECU, 0xC2FA06U, + 0xC30FE4U, 0xC3170EU, 0xC326DAU, 0xC33E30U, 0xC34572U, 0xC35D98U, 0xC36C4CU, 0xC374A6U, 0xC38222U, 0xC39AC8U, + 0xC3AB1CU, 0xC3B3F6U, 0xC3C8B4U, 0xC3D05EU, 0xC3E18AU, 0xC3F960U, 0xC407D6U, 0xC41F3CU, 0xC42EE8U, 0xC43602U, + 0xC44D40U, 0xC455AAU, 0xC4647EU, 0xC47C94U, 0xC48A10U, 0xC492FAU, 0xC4A32EU, 0xC4BBC4U, 0xC4C086U, 0xC4D86CU, + 0xC4E9B8U, 0xC4F152U, 0xC504B0U, 0xC51C5AU, 0xC52D8EU, 0xC53564U, 0xC54E26U, 0xC556CCU, 0xC56718U, 0xC57FF2U, + 0xC58976U, 0xC5919CU, 0xC5A048U, 0xC5B8A2U, 0xC5C3E0U, 0xC5DB0AU, 0xC5EADEU, 0xC5F234U, 0xC6011AU, 0xC619F0U, + 0xC62824U, 0xC630CEU, 0xC64B8CU, 0xC65366U, 0xC662B2U, 0xC67A58U, 0xC68CDCU, 0xC69436U, 0xC6A5E2U, 0xC6BD08U, + 0xC6C64AU, 0xC6DEA0U, 0xC6EF74U, 0xC6F79EU, 0xC7027CU, 0xC71A96U, 0xC72B42U, 0xC733A8U, 0xC748EAU, 0xC75000U, + 0xC761D4U, 0xC7793EU, 0xC78FBAU, 0xC79750U, 0xC7A684U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC6U, 0xC7EC12U, 0xC7F4F8U, + 0xC80994U, 0xC8117EU, 0xC820AAU, 0xC83840U, 0xC84302U, 0xC85BE8U, 0xC86A3CU, 0xC872D6U, 0xC88452U, 0xC89CB8U, + 0xC8AD6CU, 0xC8B586U, 0xC8CEC4U, 0xC8D62EU, 0xC8E7FAU, 0xC8FF10U, 0xC90AF2U, 0xC91218U, 0xC923CCU, 0xC93B26U, + 0xC94064U, 0xC9588EU, 0xC9695AU, 0xC971B0U, 0xC98734U, 0xC99FDEU, 0xC9AE0AU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D548U, + 0xC9E49CU, 0xC9FC76U, 0xCA0F58U, 0xCA17B2U, 0xCA2666U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D24U, 0xCA6CF0U, 0xCA741AU, + 0xCA829EU, 0xCA9A74U, 0xCAABA0U, 0xCAB34AU, 0xCAC808U, 0xCAD0E2U, 0xCAE136U, 0xCAF9DCU, 0xCB0C3EU, 0xCB14D4U, + 0xCB2500U, 0xCB3DEAU, 0xCB46A8U, 0xCB5E42U, 0xCB6F96U, 0xCB777CU, 0xCB81F8U, 0xCB9912U, 0xCBA8C6U, 0xCBB02CU, + 0xCBCB6EU, 0xCBD384U, 0xCBE250U, 0xCBFABAU, 0xCC040CU, 0xCC1CE6U, 0xCC2D32U, 0xCC35D8U, 0xCC4E9AU, 0xCC5670U, + 0xCC67A4U, 0xCC7F4EU, 0xCC89CAU, 0xCC9120U, 0xCCA0F4U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB6U, 0xCCEA62U, 0xCCF288U, + 0xCD076AU, 0xCD1F80U, 0xCD2E54U, 0xCD36BEU, 0xCD4DFCU, 0xCD5516U, 0xCD64C2U, 0xCD7C28U, 0xCD8AACU, 0xCD9246U, + 0xCDA392U, 0xCDBB78U, 0xCDC03AU, 0xCDD8D0U, 0xCDE904U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2AU, 0xCE2BFEU, 0xCE3314U, + 0xCE4856U, 0xCE50BCU, 0xCE6168U, 0xCE7982U, 0xCE8F06U, 0xCE97ECU, 0xCEA638U, 0xCEBED2U, 0xCEC590U, 0xCEDD7AU, + 0xCEECAEU, 0xCEF444U, 0xCF01A6U, 0xCF194CU, 0xCF2898U, 0xCF3072U, 0xCF4B30U, 0xCF53DAU, 0xCF620EU, 0xCF7AE4U, + 0xCF8C60U, 0xCF948AU, 0xCFA55EU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1CU, 0xCFEFC8U, 0xCFF722U, 0xD00DFAU, 0xD01510U, + 0xD024C4U, 0xD03C2EU, 0xD0476CU, 0xD05F86U, 0xD06E52U, 0xD076B8U, 0xD0803CU, 0xD098D6U, 0xD0A902U, 0xD0B1E8U, + 0xD0CAAAU, 0xD0D240U, 0xD0E394U, 0xD0FB7EU, 0xD10E9CU, 0xD11676U, 0xD127A2U, 0xD13F48U, 0xD1440AU, 0xD15CE0U, + 0xD16D34U, 0xD175DEU, 0xD1835AU, 0xD19BB0U, 0xD1AA64U, 0xD1B28EU, 0xD1C9CCU, 0xD1D126U, 0xD1E0F2U, 0xD1F818U, + 0xD20B36U, 0xD213DCU, 0xD22208U, 0xD23AE2U, 0xD241A0U, 0xD2594AU, 0xD2689EU, 0xD27074U, 0xD286F0U, 0xD29E1AU, + 0xD2AFCEU, 0xD2B724U, 0xD2CC66U, 0xD2D48CU, 0xD2E558U, 0xD2FDB2U, 0xD30850U, 0xD310BAU, 0xD3216EU, 0xD33984U, + 0xD342C6U, 0xD35A2CU, 0xD36BF8U, 0xD37312U, 0xD38596U, 0xD39D7CU, 0xD3ACA8U, 0xD3B442U, 0xD3CF00U, 0xD3D7EAU, + 0xD3E63EU, 0xD3FED4U, 0xD40062U, 0xD41888U, 0xD4295CU, 0xD431B6U, 0xD44AF4U, 0xD4521EU, 0xD463CAU, 0xD47B20U, + 0xD48DA4U, 0xD4954EU, 0xD4A49AU, 0xD4BC70U, 0xD4C732U, 0xD4DFD8U, 0xD4EE0CU, 0xD4F6E6U, 0xD50304U, 0xD51BEEU, + 0xD52A3AU, 0xD532D0U, 0xD54992U, 0xD55178U, 0xD560ACU, 0xD57846U, 0xD58EC2U, 0xD59628U, 0xD5A7FCU, 0xD5BF16U, + 0xD5C454U, 0xD5DCBEU, 0xD5ED6AU, 0xD5F580U, 0xD606AEU, 0xD61E44U, 0xD62F90U, 0xD6377AU, 0xD64C38U, 0xD654D2U, + 0xD66506U, 0xD67DECU, 0xD68B68U, 0xD69382U, 0xD6A256U, 0xD6BABCU, 0xD6C1FEU, 0xD6D914U, 0xD6E8C0U, 0xD6F02AU, + 0xD705C8U, 0xD71D22U, 0xD72CF6U, 0xD7341CU, 0xD74F5EU, 0xD757B4U, 0xD76660U, 0xD77E8AU, 0xD7880EU, 0xD790E4U, + 0xD7A130U, 0xD7B9DAU, 0xD7C298U, 0xD7DA72U, 0xD7EBA6U, 0xD7F34CU, 0xD80E20U, 0xD816CAU, 0xD8271EU, 0xD83FF4U, + 0xD844B6U, 0xD85C5CU, 0xD86D88U, 0xD87562U, 0xD883E6U, 0xD89B0CU, 0xD8AAD8U, 0xD8B232U, 0xD8C970U, 0xD8D19AU, + 0xD8E04EU, 0xD8F8A4U, 0xD90D46U, 0xD915ACU, 0xD92478U, 0xD93C92U, 0xD947D0U, 0xD95F3AU, 0xD96EEEU, 0xD97604U, + 0xD98080U, 0xD9986AU, 0xD9A9BEU, 0xD9B154U, 0xD9CA16U, 0xD9D2FCU, 0xD9E328U, 0xD9FBC2U, 0xDA08ECU, 0xDA1006U, + 0xDA21D2U, 0xDA3938U, 0xDA427AU, 0xDA5A90U, 0xDA6B44U, 0xDA73AEU, 0xDA852AU, 0xDA9DC0U, 0xDAAC14U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD756U, 0xDAE682U, 0xDAFE68U, 0xDB0B8AU, 0xDB1360U, 0xDB22B4U, 0xDB3A5EU, 0xDB411CU, 0xDB59F6U, + 0xDB6822U, 0xDB70C8U, 0xDB864CU, 0xDB9EA6U, 0xDBAF72U, 0xDBB798U, 0xDBCCDAU, 0xDBD430U, 0xDBE5E4U, 0xDBFD0EU, + 0xDC03B8U, 0xDC1B52U, 0xDC2A86U, 0xDC326CU, 0xDC492EU, 0xDC51C4U, 0xDC6010U, 0xDC78FAU, 0xDC8E7EU, 0xDC9694U, + 0xDCA740U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC02U, 0xDCEDD6U, 0xDCF53CU, 0xDD00DEU, 0xDD1834U, 0xDD29E0U, 0xDD310AU, + 0xDD4A48U, 0xDD52A2U, 0xDD6376U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F2U, 0xDDA426U, 0xDDBCCCU, 0xDDC78EU, 0xDDDF64U, + 0xDDEEB0U, 0xDDF65AU, 0xDE0574U, 0xDE1D9EU, 0xDE2C4AU, 0xDE34A0U, 0xDE4FE2U, 0xDE5708U, 0xDE66DCU, 0xDE7E36U, + 0xDE88B2U, 0xDE9058U, 0xDEA18CU, 0xDEB966U, 0xDEC224U, 0xDEDACEU, 0xDEEB1AU, 0xDEF3F0U, 0xDF0612U, 0xDF1EF8U, + 0xDF2F2CU, 0xDF37C6U, 0xDF4C84U, 0xDF546EU, 0xDF65BAU, 0xDF7D50U, 0xDF8BD4U, 0xDF933EU, 0xDFA2EAU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A8U, 0xDFE87CU, 0xDFF096U, 0xE00526U, 0xE01DCCU, 0xE02C18U, 0xE034F2U, 0xE04FB0U, 0xE0575AU, + 0xE0668EU, 0xE07E64U, 0xE088E0U, 0xE0900AU, 0xE0A1DEU, 0xE0B934U, 0xE0C276U, 0xE0DA9CU, 0xE0EB48U, 0xE0F3A2U, + 0xE10640U, 0xE11EAAU, 0xE12F7EU, 0xE13794U, 0xE14CD6U, 0xE1543CU, 0xE165E8U, 0xE17D02U, 0xE18B86U, 0xE1936CU, + 0xE1A2B8U, 0xE1BA52U, 0xE1C110U, 0xE1D9FAU, 0xE1E82EU, 0xE1F0C4U, 0xE203EAU, 0xE21B00U, 0xE22AD4U, 0xE2323EU, + 0xE2497CU, 0xE25196U, 0xE26042U, 0xE278A8U, 0xE28E2CU, 0xE296C6U, 0xE2A712U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC50U, + 0xE2ED84U, 0xE2F56EU, 0xE3008CU, 0xE31866U, 0xE329B2U, 0xE33158U, 0xE34A1AU, 0xE352F0U, 0xE36324U, 0xE37BCEU, + 0xE38D4AU, 0xE395A0U, 0xE3A474U, 0xE3BC9EU, 0xE3C7DCU, 0xE3DF36U, 0xE3EEE2U, 0xE3F608U, 0xE408BEU, 0xE41054U, + 0xE42180U, 0xE4396AU, 0xE44228U, 0xE45AC2U, 0xE46B16U, 0xE473FCU, 0xE48578U, 0xE49D92U, 0xE4AC46U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D704U, 0xE4E6D0U, 0xE4FE3AU, 0xE50BD8U, 0xE51332U, 0xE522E6U, 0xE53A0CU, 0xE5414EU, 0xE559A4U, + 0xE56870U, 0xE5709AU, 0xE5861EU, 0xE59EF4U, 0xE5AF20U, 0xE5B7CAU, 0xE5CC88U, 0xE5D462U, 0xE5E5B6U, 0xE5FD5CU, + 0xE60E72U, 0xE61698U, 0xE6274CU, 0xE63FA6U, 0xE644E4U, 0xE65C0EU, 0xE66DDAU, 0xE67530U, 0xE683B4U, 0xE69B5EU, + 0xE6AA8AU, 0xE6B260U, 0xE6C922U, 0xE6D1C8U, 0xE6E01CU, 0xE6F8F6U, 0xE70D14U, 0xE715FEU, 0xE7242AU, 0xE73CC0U, + 0xE74782U, 0xE75F68U, 0xE76EBCU, 0xE77656U, 0xE780D2U, 0xE79838U, 0xE7A9ECU, 0xE7B106U, 0xE7CA44U, 0xE7D2AEU, + 0xE7E37AU, 0xE7FB90U, 0xE806FCU, 0xE81E16U, 0xE82FC2U, 0xE83728U, 0xE84C6AU, 0xE85480U, 0xE86554U, 0xE87DBEU, + 0xE88B3AU, 0xE893D0U, 0xE8A204U, 0xE8BAEEU, 0xE8C1ACU, 0xE8D946U, 0xE8E892U, 0xE8F078U, 0xE9059AU, 0xE91D70U, + 0xE92CA4U, 0xE9344EU, 0xE94F0CU, 0xE957E6U, 0xE96632U, 0xE97ED8U, 0xE9885CU, 0xE990B6U, 0xE9A162U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA20U, 0xE9EBF4U, 0xE9F31EU, 0xEA0030U, 0xEA18DAU, 0xEA290EU, 0xEA31E4U, 0xEA4AA6U, 0xEA524CU, + 0xEA6398U, 0xEA7B72U, 0xEA8DF6U, 0xEA951CU, 0xEAA4C8U, 0xEABC22U, 0xEAC760U, 0xEADF8AU, 0xEAEE5EU, 0xEAF6B4U, + 0xEB0356U, 0xEB1BBCU, 0xEB2A68U, 0xEB3282U, 0xEB49C0U, 0xEB512AU, 0xEB60FEU, 0xEB7814U, 0xEB8E90U, 0xEB967AU, + 0xEBA7AEU, 0xEBBF44U, 0xEBC406U, 0xEBDCECU, 0xEBED38U, 0xEBF5D2U, 0xEC0B64U, 0xEC138EU, 0xEC225AU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5918U, 0xEC68CCU, 0xEC7026U, 0xEC86A2U, 0xEC9E48U, 0xECAF9CU, 0xECB776U, 0xECCC34U, 0xECD4DEU, + 0xECE50AU, 0xECFDE0U, 0xED0802U, 0xED10E8U, 0xED213CU, 0xED39D6U, 0xED4294U, 0xED5A7EU, 0xED6BAAU, 0xED7340U, + 0xED85C4U, 0xED9D2EU, 0xEDACFAU, 0xEDB410U, 0xEDCF52U, 0xEDD7B8U, 0xEDE66CU, 0xEDFE86U, 0xEE0DA8U, 0xEE1542U, + 0xEE2496U, 0xEE3C7CU, 0xEE473EU, 0xEE5FD4U, 0xEE6E00U, 0xEE76EAU, 0xEE806EU, 0xEE9884U, 0xEEA950U, 0xEEB1BAU, + 0xEECAF8U, 0xEED212U, 0xEEE3C6U, 0xEEFB2CU, 0xEF0ECEU, 0xEF1624U, 0xEF27F0U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB2U, + 0xEF6D66U, 0xEF758CU, 0xEF8308U, 0xEF9BE2U, 0xEFAA36U, 0xEFB2DCU, 0xEFC99EU, 0xEFD174U, 0xEFE0A0U, 0xEFF84AU, + 0xF00292U, 0xF01A78U, 0xF02BACU, 0xF03346U, 0xF04804U, 0xF050EEU, 0xF0613AU, 0xF079D0U, 0xF08F54U, 0xF097BEU, + 0xF0A66AU, 0xF0BE80U, 0xF0C5C2U, 0xF0DD28U, 0xF0ECFCU, 0xF0F416U, 0xF101F4U, 0xF1191EU, 0xF128CAU, 0xF13020U, + 0xF14B62U, 0xF15388U, 0xF1625CU, 0xF17AB6U, 0xF18C32U, 0xF194D8U, 0xF1A50CU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4EU, + 0xF1EF9AU, 0xF1F770U, 0xF2045EU, 0xF21CB4U, 0xF22D60U, 0xF2358AU, 0xF24EC8U, 0xF25622U, 0xF267F6U, 0xF27F1CU, + 0xF28998U, 0xF29172U, 0xF2A0A6U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE4U, 0xF2EA30U, 0xF2F2DAU, 0xF30738U, 0xF31FD2U, + 0xF32E06U, 0xF336ECU, 0xF34DAEU, 0xF35544U, 0xF36490U, 0xF37C7AU, 0xF38AFEU, 0xF39214U, 0xF3A3C0U, 0xF3BB2AU, + 0xF3C068U, 0xF3D882U, 0xF3E956U, 0xF3F1BCU, 0xF40F0AU, 0xF417E0U, 0xF42634U, 0xF43EDEU, 0xF4459CU, 0xF45D76U, + 0xF46CA2U, 0xF47448U, 0xF482CCU, 0xF49A26U, 0xF4ABF2U, 0xF4B318U, 0xF4C85AU, 0xF4D0B0U, 0xF4E164U, 0xF4F98EU, + 0xF50C6CU, 0xF51486U, 0xF52552U, 0xF53DB8U, 0xF546FAU, 0xF55E10U, 0xF56FC4U, 0xF5772EU, 0xF581AAU, 0xF59940U, + 0xF5A894U, 0xF5B07EU, 0xF5CB3CU, 0xF5D3D6U, 0xF5E202U, 0xF5FAE8U, 0xF609C6U, 0xF6112CU, 0xF620F8U, 0xF63812U, + 0xF64350U, 0xF65BBAU, 0xF66A6EU, 0xF67284U, 0xF68400U, 0xF69CEAU, 0xF6AD3EU, 0xF6B5D4U, 0xF6CE96U, 0xF6D67CU, + 0xF6E7A8U, 0xF6FF42U, 0xF70AA0U, 0xF7124AU, 0xF7239EU, 0xF73B74U, 0xF74036U, 0xF758DCU, 0xF76908U, 0xF771E2U, + 0xF78766U, 0xF79F8CU, 0xF7AE58U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51AU, 0xF7E4CEU, 0xF7FC24U, 0xF80148U, 0xF819A2U, + 0xF82876U, 0xF8309CU, 0xF84BDEU, 0xF85334U, 0xF862E0U, 0xF87A0AU, 0xF88C8EU, 0xF89464U, 0xF8A5B0U, 0xF8BD5AU, + 0xF8C618U, 0xF8DEF2U, 0xF8EF26U, 0xF8F7CCU, 0xF9022EU, 0xF91AC4U, 0xF92B10U, 0xF933FAU, 0xF948B8U, 0xF95052U, + 0xF96186U, 0xF9796CU, 0xF98FE8U, 0xF99702U, 0xF9A6D6U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD94U, 0xF9EC40U, 0xF9F4AAU, + 0xFA0784U, 0xFA1F6EU, 0xFA2EBAU, 0xFA3650U, 0xFA4D12U, 0xFA55F8U, 0xFA642CU, 0xFA7CC6U, 0xFA8A42U, 0xFA92A8U, + 0xFAA37CU, 0xFABB96U, 0xFAC0D4U, 0xFAD83EU, 0xFAE9EAU, 0xFAF100U, 0xFB04E2U, 0xFB1C08U, 0xFB2DDCU, 0xFB3536U, + 0xFB4E74U, 0xFB569EU, 0xFB674AU, 0xFB7FA0U, 0xFB8924U, 0xFB91CEU, 0xFBA01AU, 0xFBB8F0U, 0xFBC3B2U, 0xFBDB58U, + 0xFBEA8CU, 0xFBF266U, 0xFC0CD0U, 0xFC143AU, 0xFC25EEU, 0xFC3D04U, 0xFC4646U, 0xFC5EACU, 0xFC6F78U, 0xFC7792U, + 0xFC8116U, 0xFC99FCU, 0xFCA828U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36AU, 0xFCE2BEU, 0xFCFA54U, 0xFD0FB6U, 0xFD175CU, + 0xFD2688U, 0xFD3E62U, 0xFD4520U, 0xFD5DCAU, 0xFD6C1EU, 0xFD74F4U, 0xFD8270U, 0xFD9A9AU, 0xFDAB4EU, 0xFDB3A4U, + 0xFDC8E6U, 0xFDD00CU, 0xFDE1D8U, 0xFDF932U, 0xFE0A1CU, 0xFE12F6U, 0xFE2322U, 0xFE3BC8U, 0xFE408AU, 0xFE5860U, + 0xFE69B4U, 0xFE715EU, 0xFE87DAU, 0xFE9F30U, 0xFEAEE4U, 0xFEB60EU, 0xFECD4CU, 0xFED5A6U, 0xFEE472U, 0xFEFC98U, + 0xFF097AU, 0xFF1190U, 0xFF2044U, 0xFF38AEU, 0xFF43ECU, 0xFF5B06U, 0xFF6AD2U, 0xFF7238U, 0xFF84BCU, 0xFF9C56U, + 0xFFAD82U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C0U, 0xFFE714U, 0xFFFFFEU }; + +static const uint32_t ENCODING_TABLE_24128[] = { + 0x000000U, 0x0018EBU, 0x00293EU, 0x0031D5U, 0x004A97U, 0x00527CU, 0x0063A9U, 0x007B42U, 0x008DC6U, 0x00952DU, + 0x00A4F8U, 0x00BC13U, 0x00C751U, 0x00DFBAU, 0x00EE6FU, 0x00F684U, 0x010367U, 0x011B8CU, 0x012A59U, 0x0132B2U, + 0x0149F0U, 0x01511BU, 0x0160CEU, 0x017825U, 0x018EA1U, 0x01964AU, 0x01A79FU, 0x01BF74U, 0x01C436U, 0x01DCDDU, + 0x01ED08U, 0x01F5E3U, 0x0206CDU, 0x021E26U, 0x022FF3U, 0x023718U, 0x024C5AU, 0x0254B1U, 0x026564U, 0x027D8FU, + 0x028B0BU, 0x0293E0U, 0x02A235U, 0x02BADEU, 0x02C19CU, 0x02D977U, 0x02E8A2U, 0x02F049U, 0x0305AAU, 0x031D41U, + 0x032C94U, 0x03347FU, 0x034F3DU, 0x0357D6U, 0x036603U, 0x037EE8U, 0x03886CU, 0x039087U, 0x03A152U, 0x03B9B9U, + 0x03C2FBU, 0x03DA10U, 0x03EBC5U, 0x03F32EU, 0x040D99U, 0x041572U, 0x0424A7U, 0x043C4CU, 0x04470EU, 0x045FE5U, + 0x046E30U, 0x0476DBU, 0x04805FU, 0x0498B4U, 0x04A961U, 0x04B18AU, 0x04CAC8U, 0x04D223U, 0x04E3F6U, 0x04FB1DU, + 0x050EFEU, 0x051615U, 0x0527C0U, 0x053F2BU, 0x054469U, 0x055C82U, 0x056D57U, 0x0575BCU, 0x058338U, 0x059BD3U, + 0x05AA06U, 0x05B2EDU, 0x05C9AFU, 0x05D144U, 0x05E091U, 0x05F87AU, 0x060B54U, 0x0613BFU, 0x06226AU, 0x063A81U, + 0x0641C3U, 0x065928U, 0x0668FDU, 0x067016U, 0x068692U, 0x069E79U, 0x06AFACU, 0x06B747U, 0x06CC05U, 0x06D4EEU, + 0x06E53BU, 0x06FDD0U, 0x070833U, 0x0710D8U, 0x07210DU, 0x0739E6U, 0x0742A4U, 0x075A4FU, 0x076B9AU, 0x077371U, + 0x0785F5U, 0x079D1EU, 0x07ACCBU, 0x07B420U, 0x07CF62U, 0x07D789U, 0x07E65CU, 0x07FEB7U, 0x0803DAU, 0x081B31U, + 0x082AE4U, 0x08320FU, 0x08494DU, 0x0851A6U, 0x086073U, 0x087898U, 0x088E1CU, 0x0896F7U, 0x08A722U, 0x08BFC9U, + 0x08C48BU, 0x08DC60U, 0x08EDB5U, 0x08F55EU, 0x0900BDU, 0x091856U, 0x092983U, 0x093168U, 0x094A2AU, 0x0952C1U, + 0x096314U, 0x097BFFU, 0x098D7BU, 0x099590U, 0x09A445U, 0x09BCAEU, 0x09C7ECU, 0x09DF07U, 0x09EED2U, 0x09F639U, + 0x0A0517U, 0x0A1DFCU, 0x0A2C29U, 0x0A34C2U, 0x0A4F80U, 0x0A576BU, 0x0A66BEU, 0x0A7E55U, 0x0A88D1U, 0x0A903AU, + 0x0AA1EFU, 0x0AB904U, 0x0AC246U, 0x0ADAADU, 0x0AEB78U, 0x0AF393U, 0x0B0670U, 0x0B1E9BU, 0x0B2F4EU, 0x0B37A5U, + 0x0B4CE7U, 0x0B540CU, 0x0B65D9U, 0x0B7D32U, 0x0B8BB6U, 0x0B935DU, 0x0BA288U, 0x0BBA63U, 0x0BC121U, 0x0BD9CAU, + 0x0BE81FU, 0x0BF0F4U, 0x0C0E43U, 0x0C16A8U, 0x0C277DU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3FU, 0x0C6DEAU, 0x0C7501U, + 0x0C8385U, 0x0C9B6EU, 0x0CAABBU, 0x0CB250U, 0x0CC912U, 0x0CD1F9U, 0x0CE02CU, 0x0CF8C7U, 0x0D0D24U, 0x0D15CFU, + 0x0D241AU, 0x0D3CF1U, 0x0D47B3U, 0x0D5F58U, 0x0D6E8DU, 0x0D7666U, 0x0D80E2U, 0x0D9809U, 0x0DA9DCU, 0x0DB137U, + 0x0DCA75U, 0x0DD29EU, 0x0DE34BU, 0x0DFBA0U, 0x0E088EU, 0x0E1065U, 0x0E21B0U, 0x0E395BU, 0x0E4219U, 0x0E5AF2U, + 0x0E6B27U, 0x0E73CCU, 0x0E8548U, 0x0E9DA3U, 0x0EAC76U, 0x0EB49DU, 0x0ECFDFU, 0x0ED734U, 0x0EE6E1U, 0x0EFE0AU, + 0x0F0BE9U, 0x0F1302U, 0x0F22D7U, 0x0F3A3CU, 0x0F417EU, 0x0F5995U, 0x0F6840U, 0x0F70ABU, 0x0F862FU, 0x0F9EC4U, + 0x0FAF11U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD453U, 0x0FE586U, 0x0FFD6DU, 0x1007B4U, 0x101F5FU, 0x102E8AU, 0x103661U, + 0x104D23U, 0x1055C8U, 0x10641DU, 0x107CF6U, 0x108A72U, 0x109299U, 0x10A34CU, 0x10BBA7U, 0x10C0E5U, 0x10D80EU, + 0x10E9DBU, 0x10F130U, 0x1104D3U, 0x111C38U, 0x112DEDU, 0x113506U, 0x114E44U, 0x1156AFU, 0x11677AU, 0x117F91U, + 0x118915U, 0x1191FEU, 0x11A02BU, 0x11B8C0U, 0x11C382U, 0x11DB69U, 0x11EABCU, 0x11F257U, 0x120179U, 0x121992U, + 0x122847U, 0x1230ACU, 0x124BEEU, 0x125305U, 0x1262D0U, 0x127A3BU, 0x128CBFU, 0x129454U, 0x12A581U, 0x12BD6AU, + 0x12C628U, 0x12DEC3U, 0x12EF16U, 0x12F7FDU, 0x13021EU, 0x131AF5U, 0x132B20U, 0x1333CBU, 0x134889U, 0x135062U, + 0x1361B7U, 0x13795CU, 0x138FD8U, 0x139733U, 0x13A6E6U, 0x13BE0DU, 0x13C54FU, 0x13DDA4U, 0x13EC71U, 0x13F49AU, + 0x140A2DU, 0x1412C6U, 0x142313U, 0x143BF8U, 0x1440BAU, 0x145851U, 0x146984U, 0x14716FU, 0x1487EBU, 0x149F00U, + 0x14AED5U, 0x14B63EU, 0x14CD7CU, 0x14D597U, 0x14E442U, 0x14FCA9U, 0x15094AU, 0x1511A1U, 0x152074U, 0x15389FU, + 0x1543DDU, 0x155B36U, 0x156AE3U, 0x157208U, 0x15848CU, 0x159C67U, 0x15ADB2U, 0x15B559U, 0x15CE1BU, 0x15D6F0U, + 0x15E725U, 0x15FFCEU, 0x160CE0U, 0x16140BU, 0x1625DEU, 0x163D35U, 0x164677U, 0x165E9CU, 0x166F49U, 0x1677A2U, + 0x168126U, 0x1699CDU, 0x16A818U, 0x16B0F3U, 0x16CBB1U, 0x16D35AU, 0x16E28FU, 0x16FA64U, 0x170F87U, 0x17176CU, + 0x1726B9U, 0x173E52U, 0x174510U, 0x175DFBU, 0x176C2EU, 0x1774C5U, 0x178241U, 0x179AAAU, 0x17AB7FU, 0x17B394U, + 0x17C8D6U, 0x17D03DU, 0x17E1E8U, 0x17F903U, 0x18046EU, 0x181C85U, 0x182D50U, 0x1835BBU, 0x184EF9U, 0x185612U, + 0x1867C7U, 0x187F2CU, 0x1889A8U, 0x189143U, 0x18A096U, 0x18B87DU, 0x18C33FU, 0x18DBD4U, 0x18EA01U, 0x18F2EAU, + 0x190709U, 0x191FE2U, 0x192E37U, 0x1936DCU, 0x194D9EU, 0x195575U, 0x1964A0U, 0x197C4BU, 0x198ACFU, 0x199224U, + 0x19A3F1U, 0x19BB1AU, 0x19C058U, 0x19D8B3U, 0x19E966U, 0x19F18DU, 0x1A02A3U, 0x1A1A48U, 0x1A2B9DU, 0x1A3376U, + 0x1A4834U, 0x1A50DFU, 0x1A610AU, 0x1A79E1U, 0x1A8F65U, 0x1A978EU, 0x1AA65BU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD19U, + 0x1AECCCU, 0x1AF427U, 0x1B01C4U, 0x1B192FU, 0x1B28FAU, 0x1B3011U, 0x1B4B53U, 0x1B53B8U, 0x1B626DU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E9U, 0x1BA53CU, 0x1BBDD7U, 0x1BC695U, 0x1BDE7EU, 0x1BEFABU, 0x1BF740U, 0x1C09F7U, 0x1C111CU, + 0x1C20C9U, 0x1C3822U, 0x1C4360U, 0x1C5B8BU, 0x1C6A5EU, 0x1C72B5U, 0x1C8431U, 0x1C9CDAU, 0x1CAD0FU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64DU, 0x1CE798U, 0x1CFF73U, 0x1D0A90U, 0x1D127BU, 0x1D23AEU, 0x1D3B45U, 0x1D4007U, 0x1D58ECU, + 0x1D6939U, 0x1D71D2U, 0x1D8756U, 0x1D9FBDU, 0x1DAE68U, 0x1DB683U, 0x1DCDC1U, 0x1DD52AU, 0x1DE4FFU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D1U, 0x1E2604U, 0x1E3EEFU, 0x1E45ADU, 0x1E5D46U, 0x1E6C93U, 0x1E7478U, 0x1E82FCU, 0x1E9A17U, + 0x1EABC2U, 0x1EB329U, 0x1EC86BU, 0x1ED080U, 0x1EE155U, 0x1EF9BEU, 0x1F0C5DU, 0x1F14B6U, 0x1F2563U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E21U, 0x1F6FF4U, 0x1F771FU, 0x1F819BU, 0x1F9970U, 0x1FA8A5U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E7U, + 0x1FE232U, 0x1FFAD9U, 0x200F68U, 0x201783U, 0x202656U, 0x203EBDU, 0x2045FFU, 0x205D14U, 0x206CC1U, 0x20742AU, + 0x2082AEU, 0x209A45U, 0x20AB90U, 0x20B37BU, 0x20C839U, 0x20D0D2U, 0x20E107U, 0x20F9ECU, 0x210C0FU, 0x2114E4U, + 0x212531U, 0x213DDAU, 0x214698U, 0x215E73U, 0x216FA6U, 0x21774DU, 0x2181C9U, 0x219922U, 0x21A8F7U, 0x21B01CU, + 0x21CB5EU, 0x21D3B5U, 0x21E260U, 0x21FA8BU, 0x2209A5U, 0x22114EU, 0x22209BU, 0x223870U, 0x224332U, 0x225BD9U, + 0x226A0CU, 0x2272E7U, 0x228463U, 0x229C88U, 0x22AD5DU, 0x22B5B6U, 0x22CEF4U, 0x22D61FU, 0x22E7CAU, 0x22FF21U, + 0x230AC2U, 0x231229U, 0x2323FCU, 0x233B17U, 0x234055U, 0x2358BEU, 0x23696BU, 0x237180U, 0x238704U, 0x239FEFU, + 0x23AE3AU, 0x23B6D1U, 0x23CD93U, 0x23D578U, 0x23E4ADU, 0x23FC46U, 0x2402F1U, 0x241A1AU, 0x242BCFU, 0x243324U, + 0x244866U, 0x24508DU, 0x246158U, 0x2479B3U, 0x248F37U, 0x2497DCU, 0x24A609U, 0x24BEE2U, 0x24C5A0U, 0x24DD4BU, + 0x24EC9EU, 0x24F475U, 0x250196U, 0x25197DU, 0x2528A8U, 0x253043U, 0x254B01U, 0x2553EAU, 0x25623FU, 0x257AD4U, + 0x258C50U, 0x2594BBU, 0x25A56EU, 0x25BD85U, 0x25C6C7U, 0x25DE2CU, 0x25EFF9U, 0x25F712U, 0x26043CU, 0x261CD7U, + 0x262D02U, 0x2635E9U, 0x264EABU, 0x265640U, 0x266795U, 0x267F7EU, 0x2689FAU, 0x269111U, 0x26A0C4U, 0x26B82FU, + 0x26C36DU, 0x26DB86U, 0x26EA53U, 0x26F2B8U, 0x27075BU, 0x271FB0U, 0x272E65U, 0x27368EU, 0x274DCCU, 0x275527U, + 0x2764F2U, 0x277C19U, 0x278A9DU, 0x279276U, 0x27A3A3U, 0x27BB48U, 0x27C00AU, 0x27D8E1U, 0x27E934U, 0x27F1DFU, + 0x280CB2U, 0x281459U, 0x28258CU, 0x283D67U, 0x284625U, 0x285ECEU, 0x286F1BU, 0x2877F0U, 0x288174U, 0x28999FU, + 0x28A84AU, 0x28B0A1U, 0x28CBE3U, 0x28D308U, 0x28E2DDU, 0x28FA36U, 0x290FD5U, 0x29173EU, 0x2926EBU, 0x293E00U, + 0x294542U, 0x295DA9U, 0x296C7CU, 0x297497U, 0x298213U, 0x299AF8U, 0x29AB2DU, 0x29B3C6U, 0x29C884U, 0x29D06FU, + 0x29E1BAU, 0x29F951U, 0x2A0A7FU, 0x2A1294U, 0x2A2341U, 0x2A3BAAU, 0x2A40E8U, 0x2A5803U, 0x2A69D6U, 0x2A713DU, + 0x2A87B9U, 0x2A9F52U, 0x2AAE87U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C5U, 0x2AE410U, 0x2AFCFBU, 0x2B0918U, 0x2B11F3U, + 0x2B2026U, 0x2B38CDU, 0x2B438FU, 0x2B5B64U, 0x2B6AB1U, 0x2B725AU, 0x2B84DEU, 0x2B9C35U, 0x2BADE0U, 0x2BB50BU, + 0x2BCE49U, 0x2BD6A2U, 0x2BE777U, 0x2BFF9CU, 0x2C012BU, 0x2C19C0U, 0x2C2815U, 0x2C30FEU, 0x2C4BBCU, 0x2C5357U, + 0x2C6282U, 0x2C7A69U, 0x2C8CEDU, 0x2C9406U, 0x2CA5D3U, 0x2CBD38U, 0x2CC67AU, 0x2CDE91U, 0x2CEF44U, 0x2CF7AFU, + 0x2D024CU, 0x2D1AA7U, 0x2D2B72U, 0x2D3399U, 0x2D48DBU, 0x2D5030U, 0x2D61E5U, 0x2D790EU, 0x2D8F8AU, 0x2D9761U, + 0x2DA6B4U, 0x2DBE5FU, 0x2DC51DU, 0x2DDDF6U, 0x2DEC23U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0DU, 0x2E2ED8U, 0x2E3633U, + 0x2E4D71U, 0x2E559AU, 0x2E644FU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CBU, 0x2EA31EU, 0x2EBBF5U, 0x2EC0B7U, 0x2ED85CU, + 0x2EE989U, 0x2EF162U, 0x2F0481U, 0x2F1C6AU, 0x2F2DBFU, 0x2F3554U, 0x2F4E16U, 0x2F56FDU, 0x2F6728U, 0x2F7FC3U, + 0x2F8947U, 0x2F91ACU, 0x2FA079U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3BU, 0x2FEAEEU, 0x2FF205U, 0x3008DCU, 0x301037U, + 0x3021E2U, 0x303909U, 0x30424BU, 0x305AA0U, 0x306B75U, 0x30739EU, 0x30851AU, 0x309DF1U, 0x30AC24U, 0x30B4CFU, + 0x30CF8DU, 0x30D766U, 0x30E6B3U, 0x30FE58U, 0x310BBBU, 0x311350U, 0x312285U, 0x313A6EU, 0x31412CU, 0x3159C7U, + 0x316812U, 0x3170F9U, 0x31867DU, 0x319E96U, 0x31AF43U, 0x31B7A8U, 0x31CCEAU, 0x31D401U, 0x31E5D4U, 0x31FD3FU, + 0x320E11U, 0x3216FAU, 0x32272FU, 0x323FC4U, 0x324486U, 0x325C6DU, 0x326DB8U, 0x327553U, 0x3283D7U, 0x329B3CU, + 0x32AAE9U, 0x32B202U, 0x32C940U, 0x32D1ABU, 0x32E07EU, 0x32F895U, 0x330D76U, 0x33159DU, 0x332448U, 0x333CA3U, + 0x3347E1U, 0x335F0AU, 0x336EDFU, 0x337634U, 0x3380B0U, 0x33985BU, 0x33A98EU, 0x33B165U, 0x33CA27U, 0x33D2CCU, + 0x33E319U, 0x33FBF2U, 0x340545U, 0x341DAEU, 0x342C7BU, 0x343490U, 0x344FD2U, 0x345739U, 0x3466ECU, 0x347E07U, + 0x348883U, 0x349068U, 0x34A1BDU, 0x34B956U, 0x34C214U, 0x34DAFFU, 0x34EB2AU, 0x34F3C1U, 0x350622U, 0x351EC9U, + 0x352F1CU, 0x3537F7U, 0x354CB5U, 0x35545EU, 0x35658BU, 0x357D60U, 0x358BE4U, 0x35930FU, 0x35A2DAU, 0x35BA31U, + 0x35C173U, 0x35D998U, 0x35E84DU, 0x35F0A6U, 0x360388U, 0x361B63U, 0x362AB6U, 0x36325DU, 0x36491FU, 0x3651F4U, + 0x366021U, 0x3678CAU, 0x368E4EU, 0x3696A5U, 0x36A770U, 0x36BF9BU, 0x36C4D9U, 0x36DC32U, 0x36EDE7U, 0x36F50CU, + 0x3700EFU, 0x371804U, 0x3729D1U, 0x37313AU, 0x374A78U, 0x375293U, 0x376346U, 0x377BADU, 0x378D29U, 0x3795C2U, + 0x37A417U, 0x37BCFCU, 0x37C7BEU, 0x37DF55U, 0x37EE80U, 0x37F66BU, 0x380B06U, 0x3813EDU, 0x382238U, 0x383AD3U, + 0x384191U, 0x38597AU, 0x3868AFU, 0x387044U, 0x3886C0U, 0x389E2BU, 0x38AFFEU, 0x38B715U, 0x38CC57U, 0x38D4BCU, + 0x38E569U, 0x38FD82U, 0x390861U, 0x39108AU, 0x39215FU, 0x3939B4U, 0x3942F6U, 0x395A1DU, 0x396BC8U, 0x397323U, + 0x3985A7U, 0x399D4CU, 0x39AC99U, 0x39B472U, 0x39CF30U, 0x39D7DBU, 0x39E60EU, 0x39FEE5U, 0x3A0DCBU, 0x3A1520U, + 0x3A24F5U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB7U, 0x3A6E62U, 0x3A7689U, 0x3A800DU, 0x3A98E6U, 0x3AA933U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD271U, 0x3AE3A4U, 0x3AFB4FU, 0x3B0EACU, 0x3B1647U, 0x3B2792U, 0x3B3F79U, 0x3B443BU, 0x3B5CD0U, + 0x3B6D05U, 0x3B75EEU, 0x3B836AU, 0x3B9B81U, 0x3BAA54U, 0x3BB2BFU, 0x3BC9FDU, 0x3BD116U, 0x3BE0C3U, 0x3BF828U, + 0x3C069FU, 0x3C1E74U, 0x3C2FA1U, 0x3C374AU, 0x3C4C08U, 0x3C54E3U, 0x3C6536U, 0x3C7DDDU, 0x3C8B59U, 0x3C93B2U, + 0x3CA267U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD925U, 0x3CE8F0U, 0x3CF01BU, 0x3D05F8U, 0x3D1D13U, 0x3D2CC6U, 0x3D342DU, + 0x3D4F6FU, 0x3D5784U, 0x3D6651U, 0x3D7EBAU, 0x3D883EU, 0x3D90D5U, 0x3DA100U, 0x3DB9EBU, 0x3DC2A9U, 0x3DDA42U, + 0x3DEB97U, 0x3DF37CU, 0x3E0052U, 0x3E18B9U, 0x3E296CU, 0x3E3187U, 0x3E4AC5U, 0x3E522EU, 0x3E63FBU, 0x3E7B10U, + 0x3E8D94U, 0x3E957FU, 0x3EA4AAU, 0x3EBC41U, 0x3EC703U, 0x3EDFE8U, 0x3EEE3DU, 0x3EF6D6U, 0x3F0335U, 0x3F1BDEU, + 0x3F2A0BU, 0x3F32E0U, 0x3F49A2U, 0x3F5149U, 0x3F609CU, 0x3F7877U, 0x3F8EF3U, 0x3F9618U, 0x3FA7CDU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8FU, 0x3FED5AU, 0x3FF5B1U, 0x40063BU, 0x401ED0U, 0x402F05U, 0x4037EEU, 0x404CACU, 0x405447U, + 0x406592U, 0x407D79U, 0x408BFDU, 0x409316U, 0x40A2C3U, 0x40BA28U, 0x40C16AU, 0x40D981U, 0x40E854U, 0x40F0BFU, + 0x41055CU, 0x411DB7U, 0x412C62U, 0x413489U, 0x414FCBU, 0x415720U, 0x4166F5U, 0x417E1EU, 0x41889AU, 0x419071U, + 0x41A1A4U, 0x41B94FU, 0x41C20DU, 0x41DAE6U, 0x41EB33U, 0x41F3D8U, 0x4200F6U, 0x42181DU, 0x4229C8U, 0x423123U, + 0x424A61U, 0x42528AU, 0x42635FU, 0x427BB4U, 0x428D30U, 0x4295DBU, 0x42A40EU, 0x42BCE5U, 0x42C7A7U, 0x42DF4CU, + 0x42EE99U, 0x42F672U, 0x430391U, 0x431B7AU, 0x432AAFU, 0x433244U, 0x434906U, 0x4351EDU, 0x436038U, 0x4378D3U, + 0x438E57U, 0x4396BCU, 0x43A769U, 0x43BF82U, 0x43C4C0U, 0x43DC2BU, 0x43EDFEU, 0x43F515U, 0x440BA2U, 0x441349U, + 0x44229CU, 0x443A77U, 0x444135U, 0x4459DEU, 0x44680BU, 0x4470E0U, 0x448664U, 0x449E8FU, 0x44AF5AU, 0x44B7B1U, + 0x44CCF3U, 0x44D418U, 0x44E5CDU, 0x44FD26U, 0x4508C5U, 0x45102EU, 0x4521FBU, 0x453910U, 0x454252U, 0x455AB9U, + 0x456B6CU, 0x457387U, 0x458503U, 0x459DE8U, 0x45AC3DU, 0x45B4D6U, 0x45CF94U, 0x45D77FU, 0x45E6AAU, 0x45FE41U, + 0x460D6FU, 0x461584U, 0x462451U, 0x463CBAU, 0x4647F8U, 0x465F13U, 0x466EC6U, 0x46762DU, 0x4680A9U, 0x469842U, + 0x46A997U, 0x46B17CU, 0x46CA3EU, 0x46D2D5U, 0x46E300U, 0x46FBEBU, 0x470E08U, 0x4716E3U, 0x472736U, 0x473FDDU, + 0x47449FU, 0x475C74U, 0x476DA1U, 0x47754AU, 0x4783CEU, 0x479B25U, 0x47AAF0U, 0x47B21BU, 0x47C959U, 0x47D1B2U, + 0x47E067U, 0x47F88CU, 0x4805E1U, 0x481D0AU, 0x482CDFU, 0x483434U, 0x484F76U, 0x48579DU, 0x486648U, 0x487EA3U, + 0x488827U, 0x4890CCU, 0x48A119U, 0x48B9F2U, 0x48C2B0U, 0x48DA5BU, 0x48EB8EU, 0x48F365U, 0x490686U, 0x491E6DU, + 0x492FB8U, 0x493753U, 0x494C11U, 0x4954FAU, 0x49652FU, 0x497DC4U, 0x498B40U, 0x4993ABU, 0x49A27EU, 0x49BA95U, + 0x49C1D7U, 0x49D93CU, 0x49E8E9U, 0x49F002U, 0x4A032CU, 0x4A1BC7U, 0x4A2A12U, 0x4A32F9U, 0x4A49BBU, 0x4A5150U, + 0x4A6085U, 0x4A786EU, 0x4A8EEAU, 0x4A9601U, 0x4AA7D4U, 0x4ABF3FU, 0x4AC47DU, 0x4ADC96U, 0x4AED43U, 0x4AF5A8U, + 0x4B004BU, 0x4B18A0U, 0x4B2975U, 0x4B319EU, 0x4B4ADCU, 0x4B5237U, 0x4B63E2U, 0x4B7B09U, 0x4B8D8DU, 0x4B9566U, + 0x4BA4B3U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF1U, 0x4BEE24U, 0x4BF6CFU, 0x4C0878U, 0x4C1093U, 0x4C2146U, 0x4C39ADU, + 0x4C42EFU, 0x4C5A04U, 0x4C6BD1U, 0x4C733AU, 0x4C85BEU, 0x4C9D55U, 0x4CAC80U, 0x4CB46BU, 0x4CCF29U, 0x4CD7C2U, + 0x4CE617U, 0x4CFEFCU, 0x4D0B1FU, 0x4D13F4U, 0x4D2221U, 0x4D3ACAU, 0x4D4188U, 0x4D5963U, 0x4D68B6U, 0x4D705DU, + 0x4D86D9U, 0x4D9E32U, 0x4DAFE7U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A5U, 0x4DE570U, 0x4DFD9BU, 0x4E0EB5U, 0x4E165EU, + 0x4E278BU, 0x4E3F60U, 0x4E4422U, 0x4E5CC9U, 0x4E6D1CU, 0x4E75F7U, 0x4E8373U, 0x4E9B98U, 0x4EAA4DU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10FU, 0x4EE0DAU, 0x4EF831U, 0x4F0DD2U, 0x4F1539U, 0x4F24ECU, 0x4F3C07U, 0x4F4745U, 0x4F5FAEU, + 0x4F6E7BU, 0x4F7690U, 0x4F8014U, 0x4F98FFU, 0x4FA92AU, 0x4FB1C1U, 0x4FCA83U, 0x4FD268U, 0x4FE3BDU, 0x4FFB56U, + 0x50018FU, 0x501964U, 0x5028B1U, 0x50305AU, 0x504B18U, 0x5053F3U, 0x506226U, 0x507ACDU, 0x508C49U, 0x5094A2U, + 0x50A577U, 0x50BD9CU, 0x50C6DEU, 0x50DE35U, 0x50EFE0U, 0x50F70BU, 0x5102E8U, 0x511A03U, 0x512BD6U, 0x51333DU, + 0x51487FU, 0x515094U, 0x516141U, 0x5179AAU, 0x518F2EU, 0x5197C5U, 0x51A610U, 0x51BEFBU, 0x51C5B9U, 0x51DD52U, + 0x51EC87U, 0x51F46CU, 0x520742U, 0x521FA9U, 0x522E7CU, 0x523697U, 0x524DD5U, 0x52553EU, 0x5264EBU, 0x527C00U, + 0x528A84U, 0x52926FU, 0x52A3BAU, 0x52BB51U, 0x52C013U, 0x52D8F8U, 0x52E92DU, 0x52F1C6U, 0x530425U, 0x531CCEU, + 0x532D1BU, 0x5335F0U, 0x534EB2U, 0x535659U, 0x53678CU, 0x537F67U, 0x5389E3U, 0x539108U, 0x53A0DDU, 0x53B836U, + 0x53C374U, 0x53DB9FU, 0x53EA4AU, 0x53F2A1U, 0x540C16U, 0x5414FDU, 0x542528U, 0x543DC3U, 0x544681U, 0x545E6AU, + 0x546FBFU, 0x547754U, 0x5481D0U, 0x54993BU, 0x54A8EEU, 0x54B005U, 0x54CB47U, 0x54D3ACU, 0x54E279U, 0x54FA92U, + 0x550F71U, 0x55179AU, 0x55264FU, 0x553EA4U, 0x5545E6U, 0x555D0DU, 0x556CD8U, 0x557433U, 0x5582B7U, 0x559A5CU, + 0x55AB89U, 0x55B362U, 0x55C820U, 0x55D0CBU, 0x55E11EU, 0x55F9F5U, 0x560ADBU, 0x561230U, 0x5623E5U, 0x563B0EU, + 0x56404CU, 0x5658A7U, 0x566972U, 0x567199U, 0x56871DU, 0x569FF6U, 0x56AE23U, 0x56B6C8U, 0x56CD8AU, 0x56D561U, + 0x56E4B4U, 0x56FC5FU, 0x5709BCU, 0x571157U, 0x572082U, 0x573869U, 0x57432BU, 0x575BC0U, 0x576A15U, 0x5772FEU, + 0x57847AU, 0x579C91U, 0x57AD44U, 0x57B5AFU, 0x57CEEDU, 0x57D606U, 0x57E7D3U, 0x57FF38U, 0x580255U, 0x581ABEU, + 0x582B6BU, 0x583380U, 0x5848C2U, 0x585029U, 0x5861FCU, 0x587917U, 0x588F93U, 0x589778U, 0x58A6ADU, 0x58BE46U, + 0x58C504U, 0x58DDEFU, 0x58EC3AU, 0x58F4D1U, 0x590132U, 0x5919D9U, 0x59280CU, 0x5930E7U, 0x594BA5U, 0x59534EU, + 0x59629BU, 0x597A70U, 0x598CF4U, 0x59941FU, 0x59A5CAU, 0x59BD21U, 0x59C663U, 0x59DE88U, 0x59EF5DU, 0x59F7B6U, + 0x5A0498U, 0x5A1C73U, 0x5A2DA6U, 0x5A354DU, 0x5A4E0FU, 0x5A56E4U, 0x5A6731U, 0x5A7FDAU, 0x5A895EU, 0x5A91B5U, + 0x5AA060U, 0x5AB88BU, 0x5AC3C9U, 0x5ADB22U, 0x5AEAF7U, 0x5AF21CU, 0x5B07FFU, 0x5B1F14U, 0x5B2EC1U, 0x5B362AU, + 0x5B4D68U, 0x5B5583U, 0x5B6456U, 0x5B7CBDU, 0x5B8A39U, 0x5B92D2U, 0x5BA307U, 0x5BBBECU, 0x5BC0AEU, 0x5BD845U, + 0x5BE990U, 0x5BF17BU, 0x5C0FCCU, 0x5C1727U, 0x5C26F2U, 0x5C3E19U, 0x5C455BU, 0x5C5DB0U, 0x5C6C65U, 0x5C748EU, + 0x5C820AU, 0x5C9AE1U, 0x5CAB34U, 0x5CB3DFU, 0x5CC89DU, 0x5CD076U, 0x5CE1A3U, 0x5CF948U, 0x5D0CABU, 0x5D1440U, + 0x5D2595U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED7U, 0x5D6F02U, 0x5D77E9U, 0x5D816DU, 0x5D9986U, 0x5DA853U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD311U, 0x5DE2C4U, 0x5DFA2FU, 0x5E0901U, 0x5E11EAU, 0x5E203FU, 0x5E38D4U, 0x5E4396U, 0x5E5B7DU, + 0x5E6AA8U, 0x5E7243U, 0x5E84C7U, 0x5E9C2CU, 0x5EADF9U, 0x5EB512U, 0x5ECE50U, 0x5ED6BBU, 0x5EE76EU, 0x5EFF85U, + 0x5F0A66U, 0x5F128DU, 0x5F2358U, 0x5F3BB3U, 0x5F40F1U, 0x5F581AU, 0x5F69CFU, 0x5F7124U, 0x5F87A0U, 0x5F9F4BU, + 0x5FAE9EU, 0x5FB675U, 0x5FCD37U, 0x5FD5DCU, 0x5FE409U, 0x5FFCE2U, 0x600953U, 0x6011B8U, 0x60206DU, 0x603886U, + 0x6043C4U, 0x605B2FU, 0x606AFAU, 0x607211U, 0x608495U, 0x609C7EU, 0x60ADABU, 0x60B540U, 0x60CE02U, 0x60D6E9U, + 0x60E73CU, 0x60FFD7U, 0x610A34U, 0x6112DFU, 0x61230AU, 0x613BE1U, 0x6140A3U, 0x615848U, 0x61699DU, 0x617176U, + 0x6187F2U, 0x619F19U, 0x61AECCU, 0x61B627U, 0x61CD65U, 0x61D58EU, 0x61E45BU, 0x61FCB0U, 0x620F9EU, 0x621775U, + 0x6226A0U, 0x623E4BU, 0x624509U, 0x625DE2U, 0x626C37U, 0x6274DCU, 0x628258U, 0x629AB3U, 0x62AB66U, 0x62B38DU, + 0x62C8CFU, 0x62D024U, 0x62E1F1U, 0x62F91AU, 0x630CF9U, 0x631412U, 0x6325C7U, 0x633D2CU, 0x63466EU, 0x635E85U, + 0x636F50U, 0x6377BBU, 0x63813FU, 0x6399D4U, 0x63A801U, 0x63B0EAU, 0x63CBA8U, 0x63D343U, 0x63E296U, 0x63FA7DU, + 0x6404CAU, 0x641C21U, 0x642DF4U, 0x64351FU, 0x644E5DU, 0x6456B6U, 0x646763U, 0x647F88U, 0x64890CU, 0x6491E7U, + 0x64A032U, 0x64B8D9U, 0x64C39BU, 0x64DB70U, 0x64EAA5U, 0x64F24EU, 0x6507ADU, 0x651F46U, 0x652E93U, 0x653678U, + 0x654D3AU, 0x6555D1U, 0x656404U, 0x657CEFU, 0x658A6BU, 0x659280U, 0x65A355U, 0x65BBBEU, 0x65C0FCU, 0x65D817U, + 0x65E9C2U, 0x65F129U, 0x660207U, 0x661AECU, 0x662B39U, 0x6633D2U, 0x664890U, 0x66507BU, 0x6661AEU, 0x667945U, + 0x668FC1U, 0x66972AU, 0x66A6FFU, 0x66BE14U, 0x66C556U, 0x66DDBDU, 0x66EC68U, 0x66F483U, 0x670160U, 0x67198BU, + 0x67285EU, 0x6730B5U, 0x674BF7U, 0x67531CU, 0x6762C9U, 0x677A22U, 0x678CA6U, 0x67944DU, 0x67A598U, 0x67BD73U, + 0x67C631U, 0x67DEDAU, 0x67EF0FU, 0x67F7E4U, 0x680A89U, 0x681262U, 0x6823B7U, 0x683B5CU, 0x68401EU, 0x6858F5U, + 0x686920U, 0x6871CBU, 0x68874FU, 0x689FA4U, 0x68AE71U, 0x68B69AU, 0x68CDD8U, 0x68D533U, 0x68E4E6U, 0x68FC0DU, + 0x6909EEU, 0x691105U, 0x6920D0U, 0x69383BU, 0x694379U, 0x695B92U, 0x696A47U, 0x6972ACU, 0x698428U, 0x699CC3U, + 0x69AD16U, 0x69B5FDU, 0x69CEBFU, 0x69D654U, 0x69E781U, 0x69FF6AU, 0x6A0C44U, 0x6A14AFU, 0x6A257AU, 0x6A3D91U, + 0x6A46D3U, 0x6A5E38U, 0x6A6FEDU, 0x6A7706U, 0x6A8182U, 0x6A9969U, 0x6AA8BCU, 0x6AB057U, 0x6ACB15U, 0x6AD3FEU, + 0x6AE22BU, 0x6AFAC0U, 0x6B0F23U, 0x6B17C8U, 0x6B261DU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5FU, 0x6B6C8AU, 0x6B7461U, + 0x6B82E5U, 0x6B9A0EU, 0x6BABDBU, 0x6BB330U, 0x6BC872U, 0x6BD099U, 0x6BE14CU, 0x6BF9A7U, 0x6C0710U, 0x6C1FFBU, + 0x6C2E2EU, 0x6C36C5U, 0x6C4D87U, 0x6C556CU, 0x6C64B9U, 0x6C7C52U, 0x6C8AD6U, 0x6C923DU, 0x6CA3E8U, 0x6CBB03U, + 0x6CC041U, 0x6CD8AAU, 0x6CE97FU, 0x6CF194U, 0x6D0477U, 0x6D1C9CU, 0x6D2D49U, 0x6D35A2U, 0x6D4EE0U, 0x6D560BU, + 0x6D67DEU, 0x6D7F35U, 0x6D89B1U, 0x6D915AU, 0x6DA08FU, 0x6DB864U, 0x6DC326U, 0x6DDBCDU, 0x6DEA18U, 0x6DF2F3U, + 0x6E01DDU, 0x6E1936U, 0x6E28E3U, 0x6E3008U, 0x6E4B4AU, 0x6E53A1U, 0x6E6274U, 0x6E7A9FU, 0x6E8C1BU, 0x6E94F0U, + 0x6EA525U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE67U, 0x6EEFB2U, 0x6EF759U, 0x6F02BAU, 0x6F1A51U, 0x6F2B84U, 0x6F336FU, + 0x6F482DU, 0x6F50C6U, 0x6F6113U, 0x6F79F8U, 0x6F8F7CU, 0x6F9797U, 0x6FA642U, 0x6FBEA9U, 0x6FC5EBU, 0x6FDD00U, + 0x6FECD5U, 0x6FF43EU, 0x700EE7U, 0x70160CU, 0x7027D9U, 0x703F32U, 0x704470U, 0x705C9BU, 0x706D4EU, 0x7075A5U, + 0x708321U, 0x709BCAU, 0x70AA1FU, 0x70B2F4U, 0x70C9B6U, 0x70D15DU, 0x70E088U, 0x70F863U, 0x710D80U, 0x71156BU, + 0x7124BEU, 0x713C55U, 0x714717U, 0x715FFCU, 0x716E29U, 0x7176C2U, 0x718046U, 0x7198ADU, 0x71A978U, 0x71B193U, + 0x71CAD1U, 0x71D23AU, 0x71E3EFU, 0x71FB04U, 0x72082AU, 0x7210C1U, 0x722114U, 0x7239FFU, 0x7242BDU, 0x725A56U, + 0x726B83U, 0x727368U, 0x7285ECU, 0x729D07U, 0x72ACD2U, 0x72B439U, 0x72CF7BU, 0x72D790U, 0x72E645U, 0x72FEAEU, + 0x730B4DU, 0x7313A6U, 0x732273U, 0x733A98U, 0x7341DAU, 0x735931U, 0x7368E4U, 0x73700FU, 0x73868BU, 0x739E60U, + 0x73AFB5U, 0x73B75EU, 0x73CC1CU, 0x73D4F7U, 0x73E522U, 0x73FDC9U, 0x74037EU, 0x741B95U, 0x742A40U, 0x7432ABU, + 0x7449E9U, 0x745102U, 0x7460D7U, 0x74783CU, 0x748EB8U, 0x749653U, 0x74A786U, 0x74BF6DU, 0x74C42FU, 0x74DCC4U, + 0x74ED11U, 0x74F5FAU, 0x750019U, 0x7518F2U, 0x752927U, 0x7531CCU, 0x754A8EU, 0x755265U, 0x7563B0U, 0x757B5BU, + 0x758DDFU, 0x759534U, 0x75A4E1U, 0x75BC0AU, 0x75C748U, 0x75DFA3U, 0x75EE76U, 0x75F69DU, 0x7605B3U, 0x761D58U, + 0x762C8DU, 0x763466U, 0x764F24U, 0x7657CFU, 0x76661AU, 0x767EF1U, 0x768875U, 0x76909EU, 0x76A14BU, 0x76B9A0U, + 0x76C2E2U, 0x76DA09U, 0x76EBDCU, 0x76F337U, 0x7706D4U, 0x771E3FU, 0x772FEAU, 0x773701U, 0x774C43U, 0x7754A8U, + 0x77657DU, 0x777D96U, 0x778B12U, 0x7793F9U, 0x77A22CU, 0x77BAC7U, 0x77C185U, 0x77D96EU, 0x77E8BBU, 0x77F050U, + 0x780D3DU, 0x7815D6U, 0x782403U, 0x783CE8U, 0x7847AAU, 0x785F41U, 0x786E94U, 0x78767FU, 0x7880FBU, 0x789810U, + 0x78A9C5U, 0x78B12EU, 0x78CA6CU, 0x78D287U, 0x78E352U, 0x78FBB9U, 0x790E5AU, 0x7916B1U, 0x792764U, 0x793F8FU, + 0x7944CDU, 0x795C26U, 0x796DF3U, 0x797518U, 0x79839CU, 0x799B77U, 0x79AAA2U, 0x79B249U, 0x79C90BU, 0x79D1E0U, + 0x79E035U, 0x79F8DEU, 0x7A0BF0U, 0x7A131BU, 0x7A22CEU, 0x7A3A25U, 0x7A4167U, 0x7A598CU, 0x7A6859U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDDU, 0x7AAF08U, 0x7AB7E3U, 0x7ACCA1U, 0x7AD44AU, 0x7AE59FU, 0x7AFD74U, 0x7B0897U, 0x7B107CU, + 0x7B21A9U, 0x7B3942U, 0x7B4200U, 0x7B5AEBU, 0x7B6B3EU, 0x7B73D5U, 0x7B8551U, 0x7B9DBAU, 0x7BAC6FU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72DU, 0x7BE6F8U, 0x7BFE13U, 0x7C00A4U, 0x7C184FU, 0x7C299AU, 0x7C3171U, 0x7C4A33U, 0x7C52D8U, + 0x7C630DU, 0x7C7BE6U, 0x7C8D62U, 0x7C9589U, 0x7CA45CU, 0x7CBCB7U, 0x7CC7F5U, 0x7CDF1EU, 0x7CEECBU, 0x7CF620U, + 0x7D03C3U, 0x7D1B28U, 0x7D2AFDU, 0x7D3216U, 0x7D4954U, 0x7D51BFU, 0x7D606AU, 0x7D7881U, 0x7D8E05U, 0x7D96EEU, + 0x7DA73BU, 0x7DBFD0U, 0x7DC492U, 0x7DDC79U, 0x7DEDACU, 0x7DF547U, 0x7E0669U, 0x7E1E82U, 0x7E2F57U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5415U, 0x7E65C0U, 0x7E7D2BU, 0x7E8BAFU, 0x7E9344U, 0x7EA291U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D3U, + 0x7EE806U, 0x7EF0EDU, 0x7F050EU, 0x7F1DE5U, 0x7F2C30U, 0x7F34DBU, 0x7F4F99U, 0x7F5772U, 0x7F66A7U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9023U, 0x7FA1F6U, 0x7FB91DU, 0x7FC25FU, 0x7FDAB4U, 0x7FEB61U, 0x7FF38AU, 0x800C75U, 0x80149EU, + 0x80254BU, 0x803DA0U, 0x8046E2U, 0x805E09U, 0x806FDCU, 0x807737U, 0x8081B3U, 0x809958U, 0x80A88DU, 0x80B066U, + 0x80CB24U, 0x80D3CFU, 0x80E21AU, 0x80FAF1U, 0x810F12U, 0x8117F9U, 0x81262CU, 0x813EC7U, 0x814585U, 0x815D6EU, + 0x816CBBU, 0x817450U, 0x8182D4U, 0x819A3FU, 0x81ABEAU, 0x81B301U, 0x81C843U, 0x81D0A8U, 0x81E17DU, 0x81F996U, + 0x820AB8U, 0x821253U, 0x822386U, 0x823B6DU, 0x82402FU, 0x8258C4U, 0x826911U, 0x8271FAU, 0x82877EU, 0x829F95U, + 0x82AE40U, 0x82B6ABU, 0x82CDE9U, 0x82D502U, 0x82E4D7U, 0x82FC3CU, 0x8309DFU, 0x831134U, 0x8320E1U, 0x83380AU, + 0x834348U, 0x835BA3U, 0x836A76U, 0x83729DU, 0x838419U, 0x839CF2U, 0x83AD27U, 0x83B5CCU, 0x83CE8EU, 0x83D665U, + 0x83E7B0U, 0x83FF5BU, 0x8401ECU, 0x841907U, 0x8428D2U, 0x843039U, 0x844B7BU, 0x845390U, 0x846245U, 0x847AAEU, + 0x848C2AU, 0x8494C1U, 0x84A514U, 0x84BDFFU, 0x84C6BDU, 0x84DE56U, 0x84EF83U, 0x84F768U, 0x85028BU, 0x851A60U, + 0x852BB5U, 0x85335EU, 0x85481CU, 0x8550F7U, 0x856122U, 0x8579C9U, 0x858F4DU, 0x8597A6U, 0x85A673U, 0x85BE98U, + 0x85C5DAU, 0x85DD31U, 0x85ECE4U, 0x85F40FU, 0x860721U, 0x861FCAU, 0x862E1FU, 0x8636F4U, 0x864DB6U, 0x86555DU, + 0x866488U, 0x867C63U, 0x868AE7U, 0x86920CU, 0x86A3D9U, 0x86BB32U, 0x86C070U, 0x86D89BU, 0x86E94EU, 0x86F1A5U, + 0x870446U, 0x871CADU, 0x872D78U, 0x873593U, 0x874ED1U, 0x87563AU, 0x8767EFU, 0x877F04U, 0x878980U, 0x87916BU, + 0x87A0BEU, 0x87B855U, 0x87C317U, 0x87DBFCU, 0x87EA29U, 0x87F2C2U, 0x880FAFU, 0x881744U, 0x882691U, 0x883E7AU, + 0x884538U, 0x885DD3U, 0x886C06U, 0x8874EDU, 0x888269U, 0x889A82U, 0x88AB57U, 0x88B3BCU, 0x88C8FEU, 0x88D015U, + 0x88E1C0U, 0x88F92BU, 0x890CC8U, 0x891423U, 0x8925F6U, 0x893D1DU, 0x89465FU, 0x895EB4U, 0x896F61U, 0x89778AU, + 0x89810EU, 0x8999E5U, 0x89A830U, 0x89B0DBU, 0x89CB99U, 0x89D372U, 0x89E2A7U, 0x89FA4CU, 0x8A0962U, 0x8A1189U, + 0x8A205CU, 0x8A38B7U, 0x8A43F5U, 0x8A5B1EU, 0x8A6ACBU, 0x8A7220U, 0x8A84A4U, 0x8A9C4FU, 0x8AAD9AU, 0x8AB571U, + 0x8ACE33U, 0x8AD6D8U, 0x8AE70DU, 0x8AFFE6U, 0x8B0A05U, 0x8B12EEU, 0x8B233BU, 0x8B3BD0U, 0x8B4092U, 0x8B5879U, + 0x8B69ACU, 0x8B7147U, 0x8B87C3U, 0x8B9F28U, 0x8BAEFDU, 0x8BB616U, 0x8BCD54U, 0x8BD5BFU, 0x8BE46AU, 0x8BFC81U, + 0x8C0236U, 0x8C1ADDU, 0x8C2B08U, 0x8C33E3U, 0x8C48A1U, 0x8C504AU, 0x8C619FU, 0x8C7974U, 0x8C8FF0U, 0x8C971BU, + 0x8CA6CEU, 0x8CBE25U, 0x8CC567U, 0x8CDD8CU, 0x8CEC59U, 0x8CF4B2U, 0x8D0151U, 0x8D19BAU, 0x8D286FU, 0x8D3084U, + 0x8D4BC6U, 0x8D532DU, 0x8D62F8U, 0x8D7A13U, 0x8D8C97U, 0x8D947CU, 0x8DA5A9U, 0x8DBD42U, 0x8DC600U, 0x8DDEEBU, + 0x8DEF3EU, 0x8DF7D5U, 0x8E04FBU, 0x8E1C10U, 0x8E2DC5U, 0x8E352EU, 0x8E4E6CU, 0x8E5687U, 0x8E6752U, 0x8E7FB9U, + 0x8E893DU, 0x8E91D6U, 0x8EA003U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB41U, 0x8EEA94U, 0x8EF27FU, 0x8F079CU, 0x8F1F77U, + 0x8F2EA2U, 0x8F3649U, 0x8F4D0BU, 0x8F55E0U, 0x8F6435U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B1U, 0x8FA364U, 0x8FBB8FU, + 0x8FC0CDU, 0x8FD826U, 0x8FE9F3U, 0x8FF118U, 0x900BC1U, 0x90132AU, 0x9022FFU, 0x903A14U, 0x904156U, 0x9059BDU, + 0x906868U, 0x907083U, 0x908607U, 0x909EECU, 0x90AF39U, 0x90B7D2U, 0x90CC90U, 0x90D47BU, 0x90E5AEU, 0x90FD45U, + 0x9108A6U, 0x91104DU, 0x912198U, 0x913973U, 0x914231U, 0x915ADAU, 0x916B0FU, 0x9173E4U, 0x918560U, 0x919D8BU, + 0x91AC5EU, 0x91B4B5U, 0x91CFF7U, 0x91D71CU, 0x91E6C9U, 0x91FE22U, 0x920D0CU, 0x9215E7U, 0x922432U, 0x923CD9U, + 0x92479BU, 0x925F70U, 0x926EA5U, 0x92764EU, 0x9280CAU, 0x929821U, 0x92A9F4U, 0x92B11FU, 0x92CA5DU, 0x92D2B6U, + 0x92E363U, 0x92FB88U, 0x930E6BU, 0x931680U, 0x932755U, 0x933FBEU, 0x9344FCU, 0x935C17U, 0x936DC2U, 0x937529U, + 0x9383ADU, 0x939B46U, 0x93AA93U, 0x93B278U, 0x93C93AU, 0x93D1D1U, 0x93E004U, 0x93F8EFU, 0x940658U, 0x941EB3U, + 0x942F66U, 0x94378DU, 0x944CCFU, 0x945424U, 0x9465F1U, 0x947D1AU, 0x948B9EU, 0x949375U, 0x94A2A0U, 0x94BA4BU, + 0x94C109U, 0x94D9E2U, 0x94E837U, 0x94F0DCU, 0x95053FU, 0x951DD4U, 0x952C01U, 0x9534EAU, 0x954FA8U, 0x955743U, + 0x956696U, 0x957E7DU, 0x9588F9U, 0x959012U, 0x95A1C7U, 0x95B92CU, 0x95C26EU, 0x95DA85U, 0x95EB50U, 0x95F3BBU, + 0x960095U, 0x96187EU, 0x9629ABU, 0x963140U, 0x964A02U, 0x9652E9U, 0x96633CU, 0x967BD7U, 0x968D53U, 0x9695B8U, + 0x96A46DU, 0x96BC86U, 0x96C7C4U, 0x96DF2FU, 0x96EEFAU, 0x96F611U, 0x9703F2U, 0x971B19U, 0x972ACCU, 0x973227U, + 0x974965U, 0x97518EU, 0x97605BU, 0x9778B0U, 0x978E34U, 0x9796DFU, 0x97A70AU, 0x97BFE1U, 0x97C4A3U, 0x97DC48U, + 0x97ED9DU, 0x97F576U, 0x98081BU, 0x9810F0U, 0x982125U, 0x9839CEU, 0x98428CU, 0x985A67U, 0x986BB2U, 0x987359U, + 0x9885DDU, 0x989D36U, 0x98ACE3U, 0x98B408U, 0x98CF4AU, 0x98D7A1U, 0x98E674U, 0x98FE9FU, 0x990B7CU, 0x991397U, + 0x992242U, 0x993AA9U, 0x9941EBU, 0x995900U, 0x9968D5U, 0x99703EU, 0x9986BAU, 0x999E51U, 0x99AF84U, 0x99B76FU, + 0x99CC2DU, 0x99D4C6U, 0x99E513U, 0x99FDF8U, 0x9A0ED6U, 0x9A163DU, 0x9A27E8U, 0x9A3F03U, 0x9A4441U, 0x9A5CAAU, + 0x9A6D7FU, 0x9A7594U, 0x9A8310U, 0x9A9BFBU, 0x9AAA2EU, 0x9AB2C5U, 0x9AC987U, 0x9AD16CU, 0x9AE0B9U, 0x9AF852U, + 0x9B0DB1U, 0x9B155AU, 0x9B248FU, 0x9B3C64U, 0x9B4726U, 0x9B5FCDU, 0x9B6E18U, 0x9B76F3U, 0x9B8077U, 0x9B989CU, + 0x9BA949U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20BU, 0x9BE3DEU, 0x9BFB35U, 0x9C0582U, 0x9C1D69U, 0x9C2CBCU, 0x9C3457U, + 0x9C4F15U, 0x9C57FEU, 0x9C662BU, 0x9C7EC0U, 0x9C8844U, 0x9C90AFU, 0x9CA17AU, 0x9CB991U, 0x9CC2D3U, 0x9CDA38U, + 0x9CEBEDU, 0x9CF306U, 0x9D06E5U, 0x9D1E0EU, 0x9D2FDBU, 0x9D3730U, 0x9D4C72U, 0x9D5499U, 0x9D654CU, 0x9D7DA7U, + 0x9D8B23U, 0x9D93C8U, 0x9DA21DU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95FU, 0x9DE88AU, 0x9DF061U, 0x9E034FU, 0x9E1BA4U, + 0x9E2A71U, 0x9E329AU, 0x9E49D8U, 0x9E5133U, 0x9E60E6U, 0x9E780DU, 0x9E8E89U, 0x9E9662U, 0x9EA7B7U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF5U, 0x9EED20U, 0x9EF5CBU, 0x9F0028U, 0x9F18C3U, 0x9F2916U, 0x9F31FDU, 0x9F4ABFU, 0x9F5254U, + 0x9F6381U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9505U, 0x9FA4D0U, 0x9FBC3BU, 0x9FC779U, 0x9FDF92U, 0x9FEE47U, 0x9FF6ACU, + 0xA0031DU, 0xA01BF6U, 0xA02A23U, 0xA032C8U, 0xA0498AU, 0xA05161U, 0xA060B4U, 0xA0785FU, 0xA08EDBU, 0xA09630U, + 0xA0A7E5U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA7U, 0xA0ED72U, 0xA0F599U, 0xA1007AU, 0xA11891U, 0xA12944U, 0xA131AFU, + 0xA14AEDU, 0xA15206U, 0xA163D3U, 0xA17B38U, 0xA18DBCU, 0xA19557U, 0xA1A482U, 0xA1BC69U, 0xA1C72BU, 0xA1DFC0U, + 0xA1EE15U, 0xA1F6FEU, 0xA205D0U, 0xA21D3BU, 0xA22CEEU, 0xA23405U, 0xA24F47U, 0xA257ACU, 0xA26679U, 0xA27E92U, + 0xA28816U, 0xA290FDU, 0xA2A128U, 0xA2B9C3U, 0xA2C281U, 0xA2DA6AU, 0xA2EBBFU, 0xA2F354U, 0xA306B7U, 0xA31E5CU, + 0xA32F89U, 0xA33762U, 0xA34C20U, 0xA354CBU, 0xA3651EU, 0xA37DF5U, 0xA38B71U, 0xA3939AU, 0xA3A24FU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90DU, 0xA3E8D8U, 0xA3F033U, 0xA40E84U, 0xA4166FU, 0xA427BAU, 0xA43F51U, 0xA44413U, 0xA45CF8U, + 0xA46D2DU, 0xA475C6U, 0xA48342U, 0xA49BA9U, 0xA4AA7CU, 0xA4B297U, 0xA4C9D5U, 0xA4D13EU, 0xA4E0EBU, 0xA4F800U, + 0xA50DE3U, 0xA51508U, 0xA524DDU, 0xA53C36U, 0xA54774U, 0xA55F9FU, 0xA56E4AU, 0xA576A1U, 0xA58025U, 0xA598CEU, + 0xA5A91BU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D259U, 0xA5E38CU, 0xA5FB67U, 0xA60849U, 0xA610A2U, 0xA62177U, 0xA6399CU, + 0xA642DEU, 0xA65A35U, 0xA66BE0U, 0xA6730BU, 0xA6858FU, 0xA69D64U, 0xA6ACB1U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F3U, + 0xA6E626U, 0xA6FECDU, 0xA70B2EU, 0xA713C5U, 0xA72210U, 0xA73AFBU, 0xA741B9U, 0xA75952U, 0xA76887U, 0xA7706CU, + 0xA786E8U, 0xA79E03U, 0xA7AFD6U, 0xA7B73DU, 0xA7CC7FU, 0xA7D494U, 0xA7E541U, 0xA7FDAAU, 0xA800C7U, 0xA8182CU, + 0xA829F9U, 0xA83112U, 0xA84A50U, 0xA852BBU, 0xA8636EU, 0xA87B85U, 0xA88D01U, 0xA895EAU, 0xA8A43FU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7DU, 0xA8EEA8U, 0xA8F643U, 0xA903A0U, 0xA91B4BU, 0xA92A9EU, 0xA93275U, 0xA94937U, 0xA951DCU, + 0xA96009U, 0xA978E2U, 0xA98E66U, 0xA9968DU, 0xA9A758U, 0xA9BFB3U, 0xA9C4F1U, 0xA9DC1AU, 0xA9EDCFU, 0xA9F524U, + 0xAA060AU, 0xAA1EE1U, 0xAA2F34U, 0xAA37DFU, 0xAA4C9DU, 0xAA5476U, 0xAA65A3U, 0xAA7D48U, 0xAA8BCCU, 0xAA9327U, + 0xAAA2F2U, 0xAABA19U, 0xAAC15BU, 0xAAD9B0U, 0xAAE865U, 0xAAF08EU, 0xAB056DU, 0xAB1D86U, 0xAB2C53U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5711U, 0xAB66C4U, 0xAB7E2FU, 0xAB88ABU, 0xAB9040U, 0xABA195U, 0xABB97EU, 0xABC23CU, 0xABDAD7U, + 0xABEB02U, 0xABF3E9U, 0xAC0D5EU, 0xAC15B5U, 0xAC2460U, 0xAC3C8BU, 0xAC47C9U, 0xAC5F22U, 0xAC6EF7U, 0xAC761CU, + 0xAC8098U, 0xAC9873U, 0xACA9A6U, 0xACB14DU, 0xACCA0FU, 0xACD2E4U, 0xACE331U, 0xACFBDAU, 0xAD0E39U, 0xAD16D2U, + 0xAD2707U, 0xAD3FECU, 0xAD44AEU, 0xAD5C45U, 0xAD6D90U, 0xAD757BU, 0xAD83FFU, 0xAD9B14U, 0xADAAC1U, 0xADB22AU, + 0xADC968U, 0xADD183U, 0xADE056U, 0xADF8BDU, 0xAE0B93U, 0xAE1378U, 0xAE22ADU, 0xAE3A46U, 0xAE4104U, 0xAE59EFU, + 0xAE683AU, 0xAE70D1U, 0xAE8655U, 0xAE9EBEU, 0xAEAF6BU, 0xAEB780U, 0xAECCC2U, 0xAED429U, 0xAEE5FCU, 0xAEFD17U, + 0xAF08F4U, 0xAF101FU, 0xAF21CAU, 0xAF3921U, 0xAF4263U, 0xAF5A88U, 0xAF6B5DU, 0xAF73B6U, 0xAF8532U, 0xAF9DD9U, + 0xAFAC0CU, 0xAFB4E7U, 0xAFCFA5U, 0xAFD74EU, 0xAFE69BU, 0xAFFE70U, 0xB004A9U, 0xB01C42U, 0xB02D97U, 0xB0357CU, + 0xB04E3EU, 0xB056D5U, 0xB06700U, 0xB07FEBU, 0xB0896FU, 0xB09184U, 0xB0A051U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB13U, + 0xB0EAC6U, 0xB0F22DU, 0xB107CEU, 0xB11F25U, 0xB12EF0U, 0xB1361BU, 0xB14D59U, 0xB155B2U, 0xB16467U, 0xB17C8CU, + 0xB18A08U, 0xB192E3U, 0xB1A336U, 0xB1BBDDU, 0xB1C09FU, 0xB1D874U, 0xB1E9A1U, 0xB1F14AU, 0xB20264U, 0xB21A8FU, + 0xB22B5AU, 0xB233B1U, 0xB248F3U, 0xB25018U, 0xB261CDU, 0xB27926U, 0xB28FA2U, 0xB29749U, 0xB2A69CU, 0xB2BE77U, + 0xB2C535U, 0xB2DDDEU, 0xB2EC0BU, 0xB2F4E0U, 0xB30103U, 0xB319E8U, 0xB3283DU, 0xB330D6U, 0xB34B94U, 0xB3537FU, + 0xB362AAU, 0xB37A41U, 0xB38CC5U, 0xB3942EU, 0xB3A5FBU, 0xB3BD10U, 0xB3C652U, 0xB3DEB9U, 0xB3EF6CU, 0xB3F787U, + 0xB40930U, 0xB411DBU, 0xB4200EU, 0xB438E5U, 0xB443A7U, 0xB45B4CU, 0xB46A99U, 0xB47272U, 0xB484F6U, 0xB49C1DU, + 0xB4ADC8U, 0xB4B523U, 0xB4CE61U, 0xB4D68AU, 0xB4E75FU, 0xB4FFB4U, 0xB50A57U, 0xB512BCU, 0xB52369U, 0xB53B82U, + 0xB540C0U, 0xB5582BU, 0xB569FEU, 0xB57115U, 0xB58791U, 0xB59F7AU, 0xB5AEAFU, 0xB5B644U, 0xB5CD06U, 0xB5D5EDU, + 0xB5E438U, 0xB5FCD3U, 0xB60FFDU, 0xB61716U, 0xB626C3U, 0xB63E28U, 0xB6456AU, 0xB65D81U, 0xB66C54U, 0xB674BFU, + 0xB6823BU, 0xB69AD0U, 0xB6AB05U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D047U, 0xB6E192U, 0xB6F979U, 0xB70C9AU, 0xB71471U, + 0xB725A4U, 0xB73D4FU, 0xB7460DU, 0xB75EE6U, 0xB76F33U, 0xB777D8U, 0xB7815CU, 0xB799B7U, 0xB7A862U, 0xB7B089U, + 0xB7CBCBU, 0xB7D320U, 0xB7E2F5U, 0xB7FA1EU, 0xB80773U, 0xB81F98U, 0xB82E4DU, 0xB836A6U, 0xB84DE4U, 0xB8550FU, + 0xB864DAU, 0xB87C31U, 0xB88AB5U, 0xB8925EU, 0xB8A38BU, 0xB8BB60U, 0xB8C022U, 0xB8D8C9U, 0xB8E91CU, 0xB8F1F7U, + 0xB90414U, 0xB91CFFU, 0xB92D2AU, 0xB935C1U, 0xB94E83U, 0xB95668U, 0xB967BDU, 0xB97F56U, 0xB989D2U, 0xB99139U, + 0xB9A0ECU, 0xB9B807U, 0xB9C345U, 0xB9DBAEU, 0xB9EA7BU, 0xB9F290U, 0xBA01BEU, 0xBA1955U, 0xBA2880U, 0xBA306BU, + 0xBA4B29U, 0xBA53C2U, 0xBA6217U, 0xBA7AFCU, 0xBA8C78U, 0xBA9493U, 0xBAA546U, 0xBABDADU, 0xBAC6EFU, 0xBADE04U, + 0xBAEFD1U, 0xBAF73AU, 0xBB02D9U, 0xBB1A32U, 0xBB2BE7U, 0xBB330CU, 0xBB484EU, 0xBB50A5U, 0xBB6170U, 0xBB799BU, + 0xBB8F1FU, 0xBB97F4U, 0xBBA621U, 0xBBBECAU, 0xBBC588U, 0xBBDD63U, 0xBBECB6U, 0xBBF45DU, 0xBC0AEAU, 0xBC1201U, + 0xBC23D4U, 0xBC3B3FU, 0xBC407DU, 0xBC5896U, 0xBC6943U, 0xBC71A8U, 0xBC872CU, 0xBC9FC7U, 0xBCAE12U, 0xBCB6F9U, + 0xBCCDBBU, 0xBCD550U, 0xBCE485U, 0xBCFC6EU, 0xBD098DU, 0xBD1166U, 0xBD20B3U, 0xBD3858U, 0xBD431AU, 0xBD5BF1U, + 0xBD6A24U, 0xBD72CFU, 0xBD844BU, 0xBD9CA0U, 0xBDAD75U, 0xBDB59EU, 0xBDCEDCU, 0xBDD637U, 0xBDE7E2U, 0xBDFF09U, + 0xBE0C27U, 0xBE14CCU, 0xBE2519U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5BU, 0xBE6F8EU, 0xBE7765U, 0xBE81E1U, 0xBE990AU, + 0xBEA8DFU, 0xBEB034U, 0xBECB76U, 0xBED39DU, 0xBEE248U, 0xBEFAA3U, 0xBF0F40U, 0xBF17ABU, 0xBF267EU, 0xBF3E95U, + 0xBF45D7U, 0xBF5D3CU, 0xBF6CE9U, 0xBF7402U, 0xBF8286U, 0xBF9A6DU, 0xBFABB8U, 0xBFB353U, 0xBFC811U, 0xBFD0FAU, + 0xBFE12FU, 0xBFF9C4U, 0xC00A4EU, 0xC012A5U, 0xC02370U, 0xC03B9BU, 0xC040D9U, 0xC05832U, 0xC069E7U, 0xC0710CU, + 0xC08788U, 0xC09F63U, 0xC0AEB6U, 0xC0B65DU, 0xC0CD1FU, 0xC0D5F4U, 0xC0E421U, 0xC0FCCAU, 0xC10929U, 0xC111C2U, + 0xC12017U, 0xC138FCU, 0xC143BEU, 0xC15B55U, 0xC16A80U, 0xC1726BU, 0xC184EFU, 0xC19C04U, 0xC1ADD1U, 0xC1B53AU, + 0xC1CE78U, 0xC1D693U, 0xC1E746U, 0xC1FFADU, 0xC20C83U, 0xC21468U, 0xC225BDU, 0xC23D56U, 0xC24614U, 0xC25EFFU, + 0xC26F2AU, 0xC277C1U, 0xC28145U, 0xC299AEU, 0xC2A87BU, 0xC2B090U, 0xC2CBD2U, 0xC2D339U, 0xC2E2ECU, 0xC2FA07U, + 0xC30FE4U, 0xC3170FU, 0xC326DAU, 0xC33E31U, 0xC34573U, 0xC35D98U, 0xC36C4DU, 0xC374A6U, 0xC38222U, 0xC39AC9U, + 0xC3AB1CU, 0xC3B3F7U, 0xC3C8B5U, 0xC3D05EU, 0xC3E18BU, 0xC3F960U, 0xC407D7U, 0xC41F3CU, 0xC42EE9U, 0xC43602U, + 0xC44D40U, 0xC455ABU, 0xC4647EU, 0xC47C95U, 0xC48A11U, 0xC492FAU, 0xC4A32FU, 0xC4BBC4U, 0xC4C086U, 0xC4D86DU, + 0xC4E9B8U, 0xC4F153U, 0xC504B0U, 0xC51C5BU, 0xC52D8EU, 0xC53565U, 0xC54E27U, 0xC556CCU, 0xC56719U, 0xC57FF2U, + 0xC58976U, 0xC5919DU, 0xC5A048U, 0xC5B8A3U, 0xC5C3E1U, 0xC5DB0AU, 0xC5EADFU, 0xC5F234U, 0xC6011AU, 0xC619F1U, + 0xC62824U, 0xC630CFU, 0xC64B8DU, 0xC65366U, 0xC662B3U, 0xC67A58U, 0xC68CDCU, 0xC69437U, 0xC6A5E2U, 0xC6BD09U, + 0xC6C64BU, 0xC6DEA0U, 0xC6EF75U, 0xC6F79EU, 0xC7027DU, 0xC71A96U, 0xC72B43U, 0xC733A8U, 0xC748EAU, 0xC75001U, + 0xC761D4U, 0xC7793FU, 0xC78FBBU, 0xC79750U, 0xC7A685U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC7U, 0xC7EC12U, 0xC7F4F9U, + 0xC80994U, 0xC8117FU, 0xC820AAU, 0xC83841U, 0xC84303U, 0xC85BE8U, 0xC86A3DU, 0xC872D6U, 0xC88452U, 0xC89CB9U, + 0xC8AD6CU, 0xC8B587U, 0xC8CEC5U, 0xC8D62EU, 0xC8E7FBU, 0xC8FF10U, 0xC90AF3U, 0xC91218U, 0xC923CDU, 0xC93B26U, + 0xC94064U, 0xC9588FU, 0xC9695AU, 0xC971B1U, 0xC98735U, 0xC99FDEU, 0xC9AE0BU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D549U, + 0xC9E49CU, 0xC9FC77U, 0xCA0F59U, 0xCA17B2U, 0xCA2667U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D25U, 0xCA6CF0U, 0xCA741BU, + 0xCA829FU, 0xCA9A74U, 0xCAABA1U, 0xCAB34AU, 0xCAC808U, 0xCAD0E3U, 0xCAE136U, 0xCAF9DDU, 0xCB0C3EU, 0xCB14D5U, + 0xCB2500U, 0xCB3DEBU, 0xCB46A9U, 0xCB5E42U, 0xCB6F97U, 0xCB777CU, 0xCB81F8U, 0xCB9913U, 0xCBA8C6U, 0xCBB02DU, + 0xCBCB6FU, 0xCBD384U, 0xCBE251U, 0xCBFABAU, 0xCC040DU, 0xCC1CE6U, 0xCC2D33U, 0xCC35D8U, 0xCC4E9AU, 0xCC5671U, + 0xCC67A4U, 0xCC7F4FU, 0xCC89CBU, 0xCC9120U, 0xCCA0F5U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB7U, 0xCCEA62U, 0xCCF289U, + 0xCD076AU, 0xCD1F81U, 0xCD2E54U, 0xCD36BFU, 0xCD4DFDU, 0xCD5516U, 0xCD64C3U, 0xCD7C28U, 0xCD8AACU, 0xCD9247U, + 0xCDA392U, 0xCDBB79U, 0xCDC03BU, 0xCDD8D0U, 0xCDE905U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2BU, 0xCE2BFEU, 0xCE3315U, + 0xCE4857U, 0xCE50BCU, 0xCE6169U, 0xCE7982U, 0xCE8F06U, 0xCE97EDU, 0xCEA638U, 0xCEBED3U, 0xCEC591U, 0xCEDD7AU, + 0xCEECAFU, 0xCEF444U, 0xCF01A7U, 0xCF194CU, 0xCF2899U, 0xCF3072U, 0xCF4B30U, 0xCF53DBU, 0xCF620EU, 0xCF7AE5U, + 0xCF8C61U, 0xCF948AU, 0xCFA55FU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1DU, 0xCFEFC8U, 0xCFF723U, 0xD00DFAU, 0xD01511U, + 0xD024C4U, 0xD03C2FU, 0xD0476DU, 0xD05F86U, 0xD06E53U, 0xD076B8U, 0xD0803CU, 0xD098D7U, 0xD0A902U, 0xD0B1E9U, + 0xD0CAABU, 0xD0D240U, 0xD0E395U, 0xD0FB7EU, 0xD10E9DU, 0xD11676U, 0xD127A3U, 0xD13F48U, 0xD1440AU, 0xD15CE1U, + 0xD16D34U, 0xD175DFU, 0xD1835BU, 0xD19BB0U, 0xD1AA65U, 0xD1B28EU, 0xD1C9CCU, 0xD1D127U, 0xD1E0F2U, 0xD1F819U, + 0xD20B37U, 0xD213DCU, 0xD22209U, 0xD23AE2U, 0xD241A0U, 0xD2594BU, 0xD2689EU, 0xD27075U, 0xD286F1U, 0xD29E1AU, + 0xD2AFCFU, 0xD2B724U, 0xD2CC66U, 0xD2D48DU, 0xD2E558U, 0xD2FDB3U, 0xD30850U, 0xD310BBU, 0xD3216EU, 0xD33985U, + 0xD342C7U, 0xD35A2CU, 0xD36BF9U, 0xD37312U, 0xD38596U, 0xD39D7DU, 0xD3ACA8U, 0xD3B443U, 0xD3CF01U, 0xD3D7EAU, + 0xD3E63FU, 0xD3FED4U, 0xD40063U, 0xD41888U, 0xD4295DU, 0xD431B6U, 0xD44AF4U, 0xD4521FU, 0xD463CAU, 0xD47B21U, + 0xD48DA5U, 0xD4954EU, 0xD4A49BU, 0xD4BC70U, 0xD4C732U, 0xD4DFD9U, 0xD4EE0CU, 0xD4F6E7U, 0xD50304U, 0xD51BEFU, + 0xD52A3AU, 0xD532D1U, 0xD54993U, 0xD55178U, 0xD560ADU, 0xD57846U, 0xD58EC2U, 0xD59629U, 0xD5A7FCU, 0xD5BF17U, + 0xD5C455U, 0xD5DCBEU, 0xD5ED6BU, 0xD5F580U, 0xD606AEU, 0xD61E45U, 0xD62F90U, 0xD6377BU, 0xD64C39U, 0xD654D2U, + 0xD66507U, 0xD67DECU, 0xD68B68U, 0xD69383U, 0xD6A256U, 0xD6BABDU, 0xD6C1FFU, 0xD6D914U, 0xD6E8C1U, 0xD6F02AU, + 0xD705C9U, 0xD71D22U, 0xD72CF7U, 0xD7341CU, 0xD74F5EU, 0xD757B5U, 0xD76660U, 0xD77E8BU, 0xD7880FU, 0xD790E4U, + 0xD7A131U, 0xD7B9DAU, 0xD7C298U, 0xD7DA73U, 0xD7EBA6U, 0xD7F34DU, 0xD80E20U, 0xD816CBU, 0xD8271EU, 0xD83FF5U, + 0xD844B7U, 0xD85C5CU, 0xD86D89U, 0xD87562U, 0xD883E6U, 0xD89B0DU, 0xD8AAD8U, 0xD8B233U, 0xD8C971U, 0xD8D19AU, + 0xD8E04FU, 0xD8F8A4U, 0xD90D47U, 0xD915ACU, 0xD92479U, 0xD93C92U, 0xD947D0U, 0xD95F3BU, 0xD96EEEU, 0xD97605U, + 0xD98081U, 0xD9986AU, 0xD9A9BFU, 0xD9B154U, 0xD9CA16U, 0xD9D2FDU, 0xD9E328U, 0xD9FBC3U, 0xDA08EDU, 0xDA1006U, + 0xDA21D3U, 0xDA3938U, 0xDA427AU, 0xDA5A91U, 0xDA6B44U, 0xDA73AFU, 0xDA852BU, 0xDA9DC0U, 0xDAAC15U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD757U, 0xDAE682U, 0xDAFE69U, 0xDB0B8AU, 0xDB1361U, 0xDB22B4U, 0xDB3A5FU, 0xDB411DU, 0xDB59F6U, + 0xDB6823U, 0xDB70C8U, 0xDB864CU, 0xDB9EA7U, 0xDBAF72U, 0xDBB799U, 0xDBCCDBU, 0xDBD430U, 0xDBE5E5U, 0xDBFD0EU, + 0xDC03B9U, 0xDC1B52U, 0xDC2A87U, 0xDC326CU, 0xDC492EU, 0xDC51C5U, 0xDC6010U, 0xDC78FBU, 0xDC8E7FU, 0xDC9694U, + 0xDCA741U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC03U, 0xDCEDD6U, 0xDCF53DU, 0xDD00DEU, 0xDD1835U, 0xDD29E0U, 0xDD310BU, + 0xDD4A49U, 0xDD52A2U, 0xDD6377U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F3U, 0xDDA426U, 0xDDBCCDU, 0xDDC78FU, 0xDDDF64U, + 0xDDEEB1U, 0xDDF65AU, 0xDE0574U, 0xDE1D9FU, 0xDE2C4AU, 0xDE34A1U, 0xDE4FE3U, 0xDE5708U, 0xDE66DDU, 0xDE7E36U, + 0xDE88B2U, 0xDE9059U, 0xDEA18CU, 0xDEB967U, 0xDEC225U, 0xDEDACEU, 0xDEEB1BU, 0xDEF3F0U, 0xDF0613U, 0xDF1EF8U, + 0xDF2F2DU, 0xDF37C6U, 0xDF4C84U, 0xDF546FU, 0xDF65BAU, 0xDF7D51U, 0xDF8BD5U, 0xDF933EU, 0xDFA2EBU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A9U, 0xDFE87CU, 0xDFF097U, 0xE00526U, 0xE01DCDU, 0xE02C18U, 0xE034F3U, 0xE04FB1U, 0xE0575AU, + 0xE0668FU, 0xE07E64U, 0xE088E0U, 0xE0900BU, 0xE0A1DEU, 0xE0B935U, 0xE0C277U, 0xE0DA9CU, 0xE0EB49U, 0xE0F3A2U, + 0xE10641U, 0xE11EAAU, 0xE12F7FU, 0xE13794U, 0xE14CD6U, 0xE1543DU, 0xE165E8U, 0xE17D03U, 0xE18B87U, 0xE1936CU, + 0xE1A2B9U, 0xE1BA52U, 0xE1C110U, 0xE1D9FBU, 0xE1E82EU, 0xE1F0C5U, 0xE203EBU, 0xE21B00U, 0xE22AD5U, 0xE2323EU, + 0xE2497CU, 0xE25197U, 0xE26042U, 0xE278A9U, 0xE28E2DU, 0xE296C6U, 0xE2A713U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC51U, + 0xE2ED84U, 0xE2F56FU, 0xE3008CU, 0xE31867U, 0xE329B2U, 0xE33159U, 0xE34A1BU, 0xE352F0U, 0xE36325U, 0xE37BCEU, + 0xE38D4AU, 0xE395A1U, 0xE3A474U, 0xE3BC9FU, 0xE3C7DDU, 0xE3DF36U, 0xE3EEE3U, 0xE3F608U, 0xE408BFU, 0xE41054U, + 0xE42181U, 0xE4396AU, 0xE44228U, 0xE45AC3U, 0xE46B16U, 0xE473FDU, 0xE48579U, 0xE49D92U, 0xE4AC47U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D705U, 0xE4E6D0U, 0xE4FE3BU, 0xE50BD8U, 0xE51333U, 0xE522E6U, 0xE53A0DU, 0xE5414FU, 0xE559A4U, + 0xE56871U, 0xE5709AU, 0xE5861EU, 0xE59EF5U, 0xE5AF20U, 0xE5B7CBU, 0xE5CC89U, 0xE5D462U, 0xE5E5B7U, 0xE5FD5CU, + 0xE60E72U, 0xE61699U, 0xE6274CU, 0xE63FA7U, 0xE644E5U, 0xE65C0EU, 0xE66DDBU, 0xE67530U, 0xE683B4U, 0xE69B5FU, + 0xE6AA8AU, 0xE6B261U, 0xE6C923U, 0xE6D1C8U, 0xE6E01DU, 0xE6F8F6U, 0xE70D15U, 0xE715FEU, 0xE7242BU, 0xE73CC0U, + 0xE74782U, 0xE75F69U, 0xE76EBCU, 0xE77657U, 0xE780D3U, 0xE79838U, 0xE7A9EDU, 0xE7B106U, 0xE7CA44U, 0xE7D2AFU, + 0xE7E37AU, 0xE7FB91U, 0xE806FCU, 0xE81E17U, 0xE82FC2U, 0xE83729U, 0xE84C6BU, 0xE85480U, 0xE86555U, 0xE87DBEU, + 0xE88B3AU, 0xE893D1U, 0xE8A204U, 0xE8BAEFU, 0xE8C1ADU, 0xE8D946U, 0xE8E893U, 0xE8F078U, 0xE9059BU, 0xE91D70U, + 0xE92CA5U, 0xE9344EU, 0xE94F0CU, 0xE957E7U, 0xE96632U, 0xE97ED9U, 0xE9885DU, 0xE990B6U, 0xE9A163U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA21U, 0xE9EBF4U, 0xE9F31FU, 0xEA0031U, 0xEA18DAU, 0xEA290FU, 0xEA31E4U, 0xEA4AA6U, 0xEA524DU, + 0xEA6398U, 0xEA7B73U, 0xEA8DF7U, 0xEA951CU, 0xEAA4C9U, 0xEABC22U, 0xEAC760U, 0xEADF8BU, 0xEAEE5EU, 0xEAF6B5U, + 0xEB0356U, 0xEB1BBDU, 0xEB2A68U, 0xEB3283U, 0xEB49C1U, 0xEB512AU, 0xEB60FFU, 0xEB7814U, 0xEB8E90U, 0xEB967BU, + 0xEBA7AEU, 0xEBBF45U, 0xEBC407U, 0xEBDCECU, 0xEBED39U, 0xEBF5D2U, 0xEC0B65U, 0xEC138EU, 0xEC225BU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5919U, 0xEC68CCU, 0xEC7027U, 0xEC86A3U, 0xEC9E48U, 0xECAF9DU, 0xECB776U, 0xECCC34U, 0xECD4DFU, + 0xECE50AU, 0xECFDE1U, 0xED0802U, 0xED10E9U, 0xED213CU, 0xED39D7U, 0xED4295U, 0xED5A7EU, 0xED6BABU, 0xED7340U, + 0xED85C4U, 0xED9D2FU, 0xEDACFAU, 0xEDB411U, 0xEDCF53U, 0xEDD7B8U, 0xEDE66DU, 0xEDFE86U, 0xEE0DA8U, 0xEE1543U, + 0xEE2496U, 0xEE3C7DU, 0xEE473FU, 0xEE5FD4U, 0xEE6E01U, 0xEE76EAU, 0xEE806EU, 0xEE9885U, 0xEEA950U, 0xEEB1BBU, + 0xEECAF9U, 0xEED212U, 0xEEE3C7U, 0xEEFB2CU, 0xEF0ECFU, 0xEF1624U, 0xEF27F1U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB3U, + 0xEF6D66U, 0xEF758DU, 0xEF8309U, 0xEF9BE2U, 0xEFAA37U, 0xEFB2DCU, 0xEFC99EU, 0xEFD175U, 0xEFE0A0U, 0xEFF84BU, + 0xF00292U, 0xF01A79U, 0xF02BACU, 0xF03347U, 0xF04805U, 0xF050EEU, 0xF0613BU, 0xF079D0U, 0xF08F54U, 0xF097BFU, + 0xF0A66AU, 0xF0BE81U, 0xF0C5C3U, 0xF0DD28U, 0xF0ECFDU, 0xF0F416U, 0xF101F5U, 0xF1191EU, 0xF128CBU, 0xF13020U, + 0xF14B62U, 0xF15389U, 0xF1625CU, 0xF17AB7U, 0xF18C33U, 0xF194D8U, 0xF1A50DU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4FU, + 0xF1EF9AU, 0xF1F771U, 0xF2045FU, 0xF21CB4U, 0xF22D61U, 0xF2358AU, 0xF24EC8U, 0xF25623U, 0xF267F6U, 0xF27F1DU, + 0xF28999U, 0xF29172U, 0xF2A0A7U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE5U, 0xF2EA30U, 0xF2F2DBU, 0xF30738U, 0xF31FD3U, + 0xF32E06U, 0xF336EDU, 0xF34DAFU, 0xF35544U, 0xF36491U, 0xF37C7AU, 0xF38AFEU, 0xF39215U, 0xF3A3C0U, 0xF3BB2BU, + 0xF3C069U, 0xF3D882U, 0xF3E957U, 0xF3F1BCU, 0xF40F0BU, 0xF417E0U, 0xF42635U, 0xF43EDEU, 0xF4459CU, 0xF45D77U, + 0xF46CA2U, 0xF47449U, 0xF482CDU, 0xF49A26U, 0xF4ABF3U, 0xF4B318U, 0xF4C85AU, 0xF4D0B1U, 0xF4E164U, 0xF4F98FU, + 0xF50C6CU, 0xF51487U, 0xF52552U, 0xF53DB9U, 0xF546FBU, 0xF55E10U, 0xF56FC5U, 0xF5772EU, 0xF581AAU, 0xF59941U, + 0xF5A894U, 0xF5B07FU, 0xF5CB3DU, 0xF5D3D6U, 0xF5E203U, 0xF5FAE8U, 0xF609C6U, 0xF6112DU, 0xF620F8U, 0xF63813U, + 0xF64351U, 0xF65BBAU, 0xF66A6FU, 0xF67284U, 0xF68400U, 0xF69CEBU, 0xF6AD3EU, 0xF6B5D5U, 0xF6CE97U, 0xF6D67CU, + 0xF6E7A9U, 0xF6FF42U, 0xF70AA1U, 0xF7124AU, 0xF7239FU, 0xF73B74U, 0xF74036U, 0xF758DDU, 0xF76908U, 0xF771E3U, + 0xF78767U, 0xF79F8CU, 0xF7AE59U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51BU, 0xF7E4CEU, 0xF7FC25U, 0xF80148U, 0xF819A3U, + 0xF82876U, 0xF8309DU, 0xF84BDFU, 0xF85334U, 0xF862E1U, 0xF87A0AU, 0xF88C8EU, 0xF89465U, 0xF8A5B0U, 0xF8BD5BU, + 0xF8C619U, 0xF8DEF2U, 0xF8EF27U, 0xF8F7CCU, 0xF9022FU, 0xF91AC4U, 0xF92B11U, 0xF933FAU, 0xF948B8U, 0xF95053U, + 0xF96186U, 0xF9796DU, 0xF98FE9U, 0xF99702U, 0xF9A6D7U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD95U, 0xF9EC40U, 0xF9F4ABU, + 0xFA0785U, 0xFA1F6EU, 0xFA2EBBU, 0xFA3650U, 0xFA4D12U, 0xFA55F9U, 0xFA642CU, 0xFA7CC7U, 0xFA8A43U, 0xFA92A8U, + 0xFAA37DU, 0xFABB96U, 0xFAC0D4U, 0xFAD83FU, 0xFAE9EAU, 0xFAF101U, 0xFB04E2U, 0xFB1C09U, 0xFB2DDCU, 0xFB3537U, + 0xFB4E75U, 0xFB569EU, 0xFB674BU, 0xFB7FA0U, 0xFB8924U, 0xFB91CFU, 0xFBA01AU, 0xFBB8F1U, 0xFBC3B3U, 0xFBDB58U, + 0xFBEA8DU, 0xFBF266U, 0xFC0CD1U, 0xFC143AU, 0xFC25EFU, 0xFC3D04U, 0xFC4646U, 0xFC5EADU, 0xFC6F78U, 0xFC7793U, + 0xFC8117U, 0xFC99FCU, 0xFCA829U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36BU, 0xFCE2BEU, 0xFCFA55U, 0xFD0FB6U, 0xFD175DU, + 0xFD2688U, 0xFD3E63U, 0xFD4521U, 0xFD5DCAU, 0xFD6C1FU, 0xFD74F4U, 0xFD8270U, 0xFD9A9BU, 0xFDAB4EU, 0xFDB3A5U, + 0xFDC8E7U, 0xFDD00CU, 0xFDE1D9U, 0xFDF932U, 0xFE0A1CU, 0xFE12F7U, 0xFE2322U, 0xFE3BC9U, 0xFE408BU, 0xFE5860U, + 0xFE69B5U, 0xFE715EU, 0xFE87DAU, 0xFE9F31U, 0xFEAEE4U, 0xFEB60FU, 0xFECD4DU, 0xFED5A6U, 0xFEE473U, 0xFEFC98U, + 0xFF097BU, 0xFF1190U, 0xFF2045U, 0xFF38AEU, 0xFF43ECU, 0xFF5B07U, 0xFF6AD2U, 0xFF7239U, 0xFF84BDU, 0xFF9C56U, + 0xFFAD83U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C1U, 0xFFE714U, 0xFFFFFFU }; + +static const uint32_t DECODING_TABLE_23127[] = { + 0x000000U, 0x000001U, 0x000002U, 0x000003U, 0x000004U, 0x000005U, 0x000006U, 0x000007U, 0x000008U, 0x000009U, + 0x00000AU, 0x00000BU, 0x00000CU, 0x00000DU, 0x00000EU, 0x024020U, 0x000010U, 0x000011U, 0x000012U, 0x000013U, + 0x000014U, 0x000015U, 0x000016U, 0x412000U, 0x000018U, 0x000019U, 0x00001AU, 0x180800U, 0x00001CU, 0x200300U, + 0x048040U, 0x001480U, 0x000020U, 0x000021U, 0x000022U, 0x000023U, 0x000024U, 0x000025U, 0x000026U, 0x024008U, + 0x000028U, 0x000029U, 0x00002AU, 0x024004U, 0x00002CU, 0x024002U, 0x024001U, 0x024000U, 0x000030U, 0x000031U, + 0x000032U, 0x008180U, 0x000034U, 0x000C40U, 0x301000U, 0x0C0200U, 0x000038U, 0x043000U, 0x400600U, 0x210040U, + 0x090080U, 0x508000U, 0x002900U, 0x024010U, 0x000040U, 0x000041U, 0x000042U, 0x000043U, 0x000044U, 0x000045U, + 0x000046U, 0x280080U, 0x000048U, 0x000049U, 0x00004AU, 0x002500U, 0x00004CU, 0x111000U, 0x048010U, 0x400A00U, + 0x000050U, 0x000051U, 0x000052U, 0x021200U, 0x000054U, 0x000C20U, 0x048008U, 0x104100U, 0x000058U, 0x404080U, + 0x048004U, 0x210020U, 0x048002U, 0x0A2000U, 0x048000U, 0x048001U, 0x000060U, 0x000061U, 0x000062U, 0x540000U, + 0x000064U, 0x000C10U, 0x010300U, 0x00B000U, 0x000068U, 0x088200U, 0x001880U, 0x210010U, 0x602000U, 0x040180U, + 0x180400U, 0x024040U, 0x000070U, 0x000C04U, 0x086000U, 0x210008U, 0x000C01U, 0x000C00U, 0x420080U, 0x000C02U, + 0x120100U, 0x210002U, 0x210001U, 0x210000U, 0x005200U, 0x000C08U, 0x048020U, 0x210004U, 0x000080U, 0x000081U, + 0x000082U, 0x000083U, 0x000084U, 0x000085U, 0x000086U, 0x280040U, 0x000088U, 0x000089U, 0x00008AU, 0x050200U, + 0x00008CU, 0x00A800U, 0x500100U, 0x001410U, 0x000090U, 0x000091U, 0x000092U, 0x008120U, 0x000094U, 0x160000U, + 0x004A00U, 0x001408U, 0x000098U, 0x404040U, 0x222000U, 0x001404U, 0x090020U, 0x001402U, 0x001401U, 0x001400U, + 0x0000A0U, 0x0000A1U, 0x0000A2U, 0x008110U, 0x0000A4U, 0x401200U, 0x042400U, 0x110800U, 0x0000A8U, 0x300400U, + 0x001840U, 0x482000U, 0x090010U, 0x040140U, 0x208200U, 0x024080U, 0x0000B0U, 0x008102U, 0x008101U, 0x008100U, + 0x090008U, 0x206000U, 0x420040U, 0x008104U, 0x090004U, 0x020A00U, 0x144000U, 0x008108U, 0x090000U, 0x090001U, + 0x090002U, 0x001420U, 0x0000C0U, 0x0000C1U, 0x0000C2U, 0x280004U, 0x0000C4U, 0x280002U, 0x280001U, 0x280000U, + 0x0000C8U, 0x404010U, 0x001820U, 0x128000U, 0x020600U, 0x040120U, 0x016000U, 0x280008U, 0x0000D0U, 0x404008U, + 0x110400U, 0x042800U, 0x003100U, 0x018200U, 0x420020U, 0x280010U, 0x404001U, 0x404000U, 0x080300U, 0x404002U, + 0x300800U, 0x404004U, 0x048080U, 0x001440U, 0x0000E0U, 0x032000U, 0x001808U, 0x004600U, 0x10C000U, 0x040108U, + 0x420010U, 0x280020U, 0x001802U, 0x040104U, 0x001800U, 0x001801U, 0x040101U, 0x040100U, 0x001804U, 0x040102U, + 0x240200U, 0x181000U, 0x420004U, 0x008140U, 0x420002U, 0x000C80U, 0x420000U, 0x420001U, 0x00A400U, 0x404020U, + 0x001810U, 0x210080U, 0x090040U, 0x040110U, 0x420008U, 0x102200U, 0x000100U, 0x000101U, 0x000102U, 0x000103U, + 0x000104U, 0x000105U, 0x000106U, 0x041800U, 0x000108U, 0x000109U, 0x00010AU, 0x002440U, 0x00010CU, 0x200210U, + 0x500080U, 0x098000U, 0x000110U, 0x000111U, 0x000112U, 0x0080A0U, 0x000114U, 0x200208U, 0x0A0400U, 0x104040U, + 0x000118U, 0x200204U, 0x015000U, 0x460000U, 0x200201U, 0x200200U, 0x002820U, 0x200202U, 0x000120U, 0x000121U, + 0x000122U, 0x008090U, 0x000124U, 0x182000U, 0x010240U, 0x600400U, 0x000128U, 0x410800U, 0x2C0000U, 0x101200U, + 0x009400U, 0x0400C0U, 0x002810U, 0x024100U, 0x000130U, 0x008082U, 0x008081U, 0x008080U, 0x444000U, 0x031000U, + 0x002808U, 0x008084U, 0x120040U, 0x084400U, 0x002804U, 0x008088U, 0x002802U, 0x200220U, 0x002800U, 0x002801U, + 0x000140U, 0x000141U, 0x000142U, 0x002408U, 0x000144U, 0x428000U, 0x010220U, 0x104010U, 0x000148U, 0x002402U, + 0x002401U, 0x002400U, 0x084800U, 0x0400A0U, 0x221000U, 0x002404U, 0x000150U, 0x0D0000U, 0x600800U, 0x104004U, + 0x003080U, 0x104002U, 0x104001U, 0x104000U, 0x120020U, 0x009800U, 0x080280U, 0x002410U, 0x410400U, 0x200240U, + 0x048100U, 0x104008U, 0x000160U, 0x205000U, 0x010204U, 0x0A0800U, 0x010202U, 0x040088U, 0x010200U, 0x010201U, + 0x120010U, 0x040084U, 0x40C000U, 0x002420U, 0x040081U, 0x040080U, 0x010208U, 0x040082U, 0x120008U, 0x402200U, + 0x041400U, 0x0080C0U, 0x288000U, 0x000D00U, 0x010210U, 0x104020U, 0x120000U, 0x120001U, 0x120002U, 0x210100U, + 0x120004U, 0x040090U, 0x002840U, 0x481000U, 0x000180U, 0x000181U, 0x000182U, 0x008030U, 0x000184U, 0x014400U, + 0x500008U, 0x022200U, 0x000188U, 0x0A1000U, 0x500004U, 0x204800U, 0x500002U, 0x040060U, 0x500000U, 0x500001U, + 0x000190U, 0x008022U, 0x008021U, 0x008020U, 0x003040U, 0x480800U, 0x250000U, 0x008024U, 0x040C00U, 0x112000U, + 0x080240U, 0x008028U, 0x02C000U, 0x200280U, 0x500010U, 0x001500U, 0x0001A0U, 0x008012U, 0x008011U, 0x008010U, + 0x220800U, 0x040048U, 0x085000U, 0x008014U, 0x006200U, 0x040044U, 0x030400U, 0x008018U, 0x040041U, 0x040040U, + 0x500020U, 0x040042U, 0x008003U, 0x008002U, 0x008001U, 0x008000U, 0x100600U, 0x008006U, 0x008005U, 0x008004U, + 0x601000U, 0x00800AU, 0x008009U, 0x008008U, 0x090100U, 0x040050U, 0x002880U, 0x00800CU, 0x0001C0U, 0x100A00U, + 0x064000U, 0x411000U, 0x003010U, 0x040028U, 0x008C00U, 0x280100U, 0x218000U, 0x040024U, 0x080210U, 0x002480U, + 0x040021U, 0x040020U, 0x500040U, 0x040022U, 0x003004U, 0x220400U, 0x080208U, 0x008060U, 0x003000U, 0x003001U, + 0x003002U, 0x104080U, 0x080202U, 0x404100U, 0x080200U, 0x080201U, 0x003008U, 0x040030U, 0x080204U, 0x030800U, + 0x480400U, 0x04000CU, 0x302000U, 0x008050U, 0x040009U, 0x040008U, 0x010280U, 0x04000AU, 0x040005U, 0x040004U, + 0x001900U, 0x040006U, 0x040001U, 0x040000U, 0x040003U, 0x040002U, 0x014800U, 0x008042U, 0x008041U, 0x008040U, + 0x003020U, 0x040018U, 0x420100U, 0x008044U, 0x120080U, 0x040014U, 0x080220U, 0x008048U, 0x040011U, 0x040010U, + 0x204400U, 0x040012U, 0x000200U, 0x000201U, 0x000202U, 0x000203U, 0x000204U, 0x000205U, 0x000206U, 0x108400U, + 0x000208U, 0x000209U, 0x00020AU, 0x050080U, 0x00020CU, 0x200110U, 0x083000U, 0x400840U, 0x000210U, 0x000211U, + 0x000212U, 0x021040U, 0x000214U, 0x200108U, 0x004880U, 0x0C0020U, 0x000218U, 0x200104U, 0x400420U, 0x00E000U, + 0x200101U, 0x200100U, 0x130000U, 0x200102U, 0x000220U, 0x000221U, 0x000222U, 0x202800U, 0x000224U, 0x401080U, + 0x010140U, 0x0C0010U, 0x000228U, 0x088040U, 0x400410U, 0x101100U, 0x140800U, 0x012400U, 0x208080U, 0x024200U, + 0x000230U, 0x114000U, 0x400408U, 0x0C0004U, 0x02A000U, 0x0C0002U, 0x0C0001U, 0x0C0000U, 0x400402U, 0x020880U, + 0x400400U, 0x400401U, 0x005040U, 0x200120U, 0x400404U, 0x0C0008U, 0x000240U, 0x000241U, 0x000242U, 0x021010U, + 0x000244U, 0x046000U, 0x010120U, 0x400808U, 0x000248U, 0x088020U, 0x304000U, 0x400804U, 0x020480U, 0x400802U, + 0x400801U, 0x400800U, 0x000250U, 0x021002U, 0x021001U, 0x021000U, 0x580000U, 0x018080U, 0x202400U, 0x021004U, + 0x012800U, 0x140400U, 0x080180U, 0x021008U, 0x005020U, 0x200140U, 0x048200U, 0x400810U, 0x000260U, 0x088008U, + 0x010104U, 0x004480U, 0x010102U, 0x320000U, 0x010100U, 0x010101U, 0x088001U, 0x088000U, 0x062000U, 0x088002U, + 0x005010U, 0x088004U, 0x010108U, 0x400820U, 0x240080U, 0x402100U, 0x108800U, 0x021020U, 0x005008U, 0x000E00U, + 0x010110U, 0x0C0040U, 0x005004U, 0x088010U, 0x400440U, 0x210200U, 0x005000U, 0x005001U, 0x005002U, 0x102080U, + 0x000280U, 0x000281U, 0x000282U, 0x050008U, 0x000284U, 0x401020U, 0x004810U, 0x022100U, 0x000288U, 0x050002U, + 0x050001U, 0x050000U, 0x020440U, 0x184000U, 0x208020U, 0x050004U, 0x000290U, 0x082400U, 0x004804U, 0x700000U, + 0x004802U, 0x018040U, 0x004800U, 0x004801U, 0x109000U, 0x020820U, 0x080140U, 0x050010U, 0x442000U, 0x200180U, + 0x004808U, 0x001600U, 0x0002A0U, 0x401004U, 0x1A0000U, 0x004440U, 0x401001U, 0x401000U, 0x208008U, 0x401002U, + 0x006100U, 0x020810U, 0x208004U, 0x050020U, 0x208002U, 0x401008U, 0x208000U, 0x208001U, 0x240040U, 0x020808U, + 0x013000U, 0x008300U, 0x100500U, 0x401010U, 0x004820U, 0x0C0080U, 0x020801U, 0x020800U, 0x400480U, 0x020802U, + 0x090200U, 0x020804U, 0x208010U, 0x102040U, 0x0002C0U, 0x100900U, 0x40A000U, 0x004420U, 0x020408U, 0x018010U, + 0x141000U, 0x280200U, 0x020404U, 0x203000U, 0x080110U, 0x050040U, 0x020400U, 0x020401U, 0x020402U, 0x400880U, + 0x240020U, 0x018004U, 0x080108U, 0x021080U, 0x018001U, 0x018000U, 0x004840U, 0x018002U, 0x080102U, 0x404200U, + 0x080100U, 0x080101U, 0x020410U, 0x018008U, 0x080104U, 0x102020U, 0x240010U, 0x004402U, 0x004401U, 0x004400U, + 0x082800U, 0x401040U, 0x010180U, 0x004404U, 0x510000U, 0x088080U, 0x001A00U, 0x004408U, 0x020420U, 0x040300U, + 0x208040U, 0x102010U, 0x240000U, 0x240001U, 0x240002U, 0x004410U, 0x240004U, 0x018020U, 0x420200U, 0x102008U, + 0x240008U, 0x020840U, 0x080120U, 0x102004U, 0x005080U, 0x102002U, 0x102001U, 0x102000U, 0x000300U, 0x000301U, + 0x000302U, 0x484000U, 0x000304U, 0x200018U, 0x010060U, 0x022080U, 0x000308U, 0x200014U, 0x028800U, 0x101020U, + 0x200011U, 0x200010U, 0x044400U, 0x200012U, 0x000310U, 0x20000CU, 0x142000U, 0x010C00U, 0x200009U, 0x200008U, + 0x409000U, 0x20000AU, 0x200005U, 0x200004U, 0x0800C0U, 0x200006U, 0x200001U, 0x200000U, 0x200003U, 0x200002U, + 0x000320U, 0x060400U, 0x010044U, 0x101008U, 0x010042U, 0x00C800U, 0x010040U, 0x010041U, 0x006080U, 0x101002U, + 0x101001U, 0x101000U, 0x4A0000U, 0x200030U, 0x010048U, 0x101004U, 0x081800U, 0x402040U, 0x224000U, 0x008280U, + 0x100480U, 0x200028U, 0x010050U, 0x0C0100U, 0x058000U, 0x200024U, 0x400500U, 0x101010U, 0x200021U, 0x200020U, + 0x002A00U, 0x200022U, 0x000340U, 0x100880U, 0x010024U, 0x248000U, 0x010022U, 0x081400U, 0x010020U, 0x010021U, + 0x441000U, 0x034000U, 0x080090U, 0x002600U, 0x10A000U, 0x200050U, 0x010028U, 0x400900U, 0x00C400U, 0x402020U, + 0x080088U, 0x021100U, 0x060800U, 0x200048U, 0x010030U, 0x104200U, 0x080082U, 0x200044U, 0x080080U, 0x080081U, + 0x200041U, 0x200040U, 0x080084U, 0x200042U, 0x010006U, 0x402010U, 0x010004U, 0x010005U, 0x010002U, 0x010003U, + 0x010000U, 0x010001U, 0x200C00U, 0x088100U, 0x01000CU, 0x101040U, 0x01000AU, 0x040280U, 0x010008U, 0x010009U, + 0x402001U, 0x402000U, 0x010014U, 0x402002U, 0x010012U, 0x402004U, 0x010010U, 0x010011U, 0x120200U, 0x402008U, + 0x0800A0U, 0x044800U, 0x005100U, 0x200060U, 0x010018U, 0x028400U, 0x000380U, 0x100840U, 0x201400U, 0x022004U, + 0x0C8000U, 0x022002U, 0x022001U, 0x022000U, 0x006020U, 0x408400U, 0x080050U, 0x050100U, 0x011800U, 0x200090U, + 0x500200U, 0x022008U, 0x430000U, 0x045000U, 0x080048U, 0x008220U, 0x100420U, 0x200088U, 0x004900U, 0x022010U, + 0x080042U, 0x200084U, 0x080040U, 0x080041U, 0x200081U, 0x200080U, 0x080044U, 0x200082U, 0x006008U, 0x290000U, + 0x440800U, 0x008210U, 0x100410U, 0x401100U, 0x0100C0U, 0x022020U, 0x006000U, 0x006001U, 0x006002U, 0x101080U, + 0x006004U, 0x040240U, 0x208100U, 0x080C00U, 0x100404U, 0x008202U, 0x008201U, 0x008200U, 0x100400U, 0x100401U, + 0x100402U, 0x008204U, 0x006010U, 0x020900U, 0x080060U, 0x008208U, 0x100408U, 0x2000A0U, 0x061000U, 0x414000U, + 0x100801U, 0x100800U, 0x080018U, 0x100802U, 0x604000U, 0x100804U, 0x0100A0U, 0x022040U, 0x080012U, 0x100808U, + 0x080010U, 0x080011U, 0x020500U, 0x040220U, 0x080014U, 0x00D000U, 0x08000AU, 0x100810U, 0x080008U, 0x080009U, + 0x003200U, 0x018100U, 0x08000CU, 0x440400U, 0x080002U, 0x080003U, 0x080000U, 0x080001U, 0x080006U, 0x2000C0U, + 0x080004U, 0x080005U, 0x029000U, 0x100820U, 0x010084U, 0x004500U, 0x010082U, 0x040208U, 0x010080U, 0x010081U, + 0x006040U, 0x040204U, 0x080030U, 0x620000U, 0x040201U, 0x040200U, 0x010088U, 0x040202U, 0x240100U, 0x402080U, + 0x080028U, 0x008240U, 0x100440U, 0x0A4000U, 0x010090U, 0x201800U, 0x080022U, 0x011400U, 0x080020U, 0x080021U, + 0x408800U, 0x040210U, 0x080024U, 0x102100U, 0x000400U, 0x000401U, 0x000402U, 0x000403U, 0x000404U, 0x000405U, + 0x000406U, 0x108200U, 0x000408U, 0x000409U, 0x00040AU, 0x002140U, 0x00040CU, 0x4C0000U, 0x210800U, 0x001090U, + 0x000410U, 0x000411U, 0x000412U, 0x244000U, 0x000414U, 0x000860U, 0x0A0100U, 0x001088U, 0x000418U, 0x038000U, + 0x400220U, 0x001084U, 0x106000U, 0x001082U, 0x001081U, 0x001080U, 0x000420U, 0x000421U, 0x000422U, 0x091000U, + 0x000424U, 0x000850U, 0x042080U, 0x600100U, 0x000428U, 0x300080U, 0x400210U, 0x048800U, 0x009100U, 0x012200U, + 0x180040U, 0x024400U, 0x000430U, 0x000844U, 0x400208U, 0x122000U, 0x000841U, 0x000840U, 0x01C000U, 0x000842U, + 0x400202U, 0x084100U, 0x400200U, 0x400201U, 0x260000U, 0x000848U, 0x400204U, 0x0010A0U, 0x000440U, 0x000441U, + 0x000442U, 0x002108U, 0x000444U, 0x000830U, 0x405000U, 0x070000U, 0x000448U, 0x002102U, 0x002101U, 0x002100U, + 0x020280U, 0x20C000U, 0x180020U, 0x002104U, 0x000450U, 0x000824U, 0x110080U, 0x488000U, 0x000821U, 0x000820U, + 0x202200U, 0x000822U, 0x281000U, 0x140200U, 0x024800U, 0x002110U, 0x410100U, 0x000828U, 0x048400U, 0x0010C0U, + 0x000460U, 0x000814U, 0x228000U, 0x004280U, 0x000811U, 0x000810U, 0x180008U, 0x000812U, 0x054000U, 0x421000U, + 0x180004U, 0x002120U, 0x180002U, 0x000818U, 0x180000U, 0x180001U, 0x000805U, 0x000804U, 0x041100U, 0x000806U, + 0x000801U, 0x000800U, 0x000803U, 0x000802U, 0x00A080U, 0x00080CU, 0x400240U, 0x210400U, 0x000809U, 0x000808U, + 0x180010U, 0x00080AU, 0x000480U, 0x000481U, 0x000482U, 0x420800U, 0x000484U, 0x014100U, 0x042020U, 0x001018U, + 0x000488U, 0x300020U, 0x08C000U, 0x001014U, 0x020240U, 0x001012U, 0x001011U, 0x001010U, 0x000490U, 0x082200U, + 0x110040U, 0x00100CU, 0x608000U, 0x00100AU, 0x001009U, 0x001008U, 0x040900U, 0x001006U, 0x001005U, 0x001004U, + 0x001003U, 0x001002U, 0x001001U, 0x001000U, 0x0004A0U, 0x300008U, 0x042004U, 0x004240U, 0x042002U, 0x0A8000U, + 0x042000U, 0x042001U, 0x300001U, 0x300000U, 0x030100U, 0x300002U, 0x404800U, 0x300004U, 0x042008U, 0x001030U, + 0x025000U, 0x450000U, 0x280800U, 0x008500U, 0x100300U, 0x0008C0U, 0x042010U, 0x001028U, 0x00A040U, 0x300010U, + 0x400280U, 0x001024U, 0x090400U, 0x001022U, 0x001021U, 0x001020U, 0x0004C0U, 0x049000U, 0x110010U, 0x004220U, + 0x020208U, 0x502000U, 0x008900U, 0x280400U, 0x020204U, 0x090800U, 0x640000U, 0x002180U, 0x020200U, 0x020201U, + 0x020202U, 0x001050U, 0x110002U, 0x220100U, 0x110000U, 0x110001U, 0x0C4000U, 0x0008A0U, 0x110004U, 0x001048U, + 0x00A020U, 0x404400U, 0x110008U, 0x001044U, 0x020210U, 0x001042U, 0x001041U, 0x001040U, 0x480100U, 0x004202U, + 0x004201U, 0x004200U, 0x211000U, 0x000890U, 0x042040U, 0x004204U, 0x00A010U, 0x300040U, 0x001C00U, 0x004208U, + 0x020220U, 0x040500U, 0x180080U, 0x418000U, 0x00A008U, 0x000884U, 0x110020U, 0x004210U, 0x000881U, 0x000880U, + 0x420400U, 0x000882U, 0x00A000U, 0x00A001U, 0x00A002U, 0x0E0000U, 0x00A004U, 0x000888U, 0x204100U, 0x001060U, + 0x000500U, 0x000501U, 0x000502U, 0x002048U, 0x000504U, 0x014080U, 0x0A0010U, 0x600020U, 0x000508U, 0x002042U, + 0x002041U, 0x002040U, 0x009020U, 0x120800U, 0x044200U, 0x002044U, 0x000510U, 0x501000U, 0x0A0004U, 0x010A00U, + 0x0A0002U, 0x04A000U, 0x0A0000U, 0x0A0001U, 0x040880U, 0x084020U, 0x308000U, 0x002050U, 0x410040U, 0x200600U, + 0x0A0008U, 0x001180U, 0x000520U, 0x060200U, 0x104800U, 0x600004U, 0x009008U, 0x600002U, 0x600001U, 0x600000U, + 0x009004U, 0x084010U, 0x030080U, 0x002060U, 0x009000U, 0x009001U, 0x009002U, 0x600008U, 0x212000U, 0x084008U, + 0x041040U, 0x008480U, 0x100280U, 0x000940U, 0x0A0020U, 0x600010U, 0x084001U, 0x084000U, 0x400300U, 0x084002U, + 0x009010U, 0x084004U, 0x002C00U, 0x150000U, 0x000540U, 0x00200AU, 0x002009U, 0x002008U, 0x340000U, 0x081200U, + 0x008880U, 0x00200CU, 0x002003U, 0x002002U, 0x002001U, 0x002000U, 0x410010U, 0x002006U, 0x002005U, 0x002004U, + 0x00C200U, 0x220080U, 0x041020U, 0x002018U, 0x410008U, 0x000920U, 0x0A0040U, 0x104400U, 0x410004U, 0x002012U, + 0x002011U, 0x002010U, 0x410000U, 0x410001U, 0x410002U, 0x002014U, 0x480080U, 0x118000U, 0x041010U, 0x002028U, + 0x026000U, 0x000910U, 0x010600U, 0x600040U, 0x200A00U, 0x002022U, 0x002021U, 0x002020U, 0x009040U, 0x040480U, + 0x180100U, 0x002024U, 0x041002U, 0x000904U, 0x041000U, 0x041001U, 0x000901U, 0x000900U, 0x041004U, 0x000902U, + 0x120400U, 0x084040U, 0x041008U, 0x002030U, 0x410020U, 0x000908U, 0x204080U, 0x028200U, 0x000580U, 0x014004U, + 0x201200U, 0x1C0000U, 0x014001U, 0x014000U, 0x008840U, 0x014002U, 0x040810U, 0x408200U, 0x030020U, 0x0020C0U, + 0x282000U, 0x014008U, 0x500400U, 0x001110U, 0x040808U, 0x220040U, 0x406000U, 0x008420U, 0x100220U, 0x014010U, + 0x0A0080U, 0x001108U, 0x040800U, 0x040801U, 0x040802U, 0x001104U, 0x040804U, 0x001102U, 0x001101U, 0x001100U, + 0x480040U, 0x003800U, 0x030008U, 0x008410U, 0x100210U, 0x014020U, 0x042100U, 0x600080U, 0x030002U, 0x300100U, + 0x030000U, 0x030001U, 0x009080U, 0x040440U, 0x030004U, 0x080A00U, 0x100204U, 0x008402U, 0x008401U, 0x008400U, + 0x100200U, 0x100201U, 0x100202U, 0x008404U, 0x040820U, 0x084080U, 0x030010U, 0x008408U, 0x100208U, 0x422000U, + 0x204040U, 0x001120U, 0x480020U, 0x220010U, 0x008804U, 0x002088U, 0x008802U, 0x014040U, 0x008800U, 0x008801U, + 0x105000U, 0x002082U, 0x002081U, 0x002080U, 0x020300U, 0x040420U, 0x008808U, 0x002084U, 0x220001U, 0x220000U, + 0x110100U, 0x220002U, 0x003400U, 0x220004U, 0x008810U, 0x440200U, 0x040840U, 0x220008U, 0x080600U, 0x002090U, + 0x410080U, 0x188000U, 0x204020U, 0x001140U, 0x480000U, 0x480001U, 0x480002U, 0x004300U, 0x480004U, 0x040408U, + 0x008820U, 0x121000U, 0x480008U, 0x040404U, 0x030040U, 0x0020A0U, 0x040401U, 0x040400U, 0x204010U, 0x040402U, + 0x480010U, 0x220020U, 0x041080U, 0x008440U, 0x100240U, 0x000980U, 0x204008U, 0x092000U, 0x00A100U, 0x011200U, + 0x204004U, 0x500800U, 0x204002U, 0x040410U, 0x204000U, 0x204001U, 0x000600U, 0x000601U, 0x000602U, 0x108004U, + 0x000604U, 0x108002U, 0x108001U, 0x108000U, 0x000608U, 0x005800U, 0x400030U, 0x2A0000U, 0x0200C0U, 0x012020U, + 0x044100U, 0x108008U, 0x000610U, 0x082080U, 0x400028U, 0x010900U, 0x051000U, 0x424000U, 0x202040U, 0x108010U, + 0x400022U, 0x140040U, 0x400020U, 0x400021U, 0x088800U, 0x200500U, 0x400024U, 0x001280U, 0x000620U, 0x060100U, + 0x400018U, 0x0040C0U, 0x284000U, 0x012008U, 0x021800U, 0x108020U, 0x400012U, 0x012004U, 0x400010U, 0x400011U, + 0x012001U, 0x012000U, 0x400014U, 0x012002U, 0x40000AU, 0x209000U, 0x400008U, 0x400009U, 0x100180U, 0x000A40U, + 0x40000CU, 0x0C0400U, 0x400002U, 0x400003U, 0x400000U, 0x400001U, 0x400006U, 0x012010U, 0x400004U, 0x400005U, + 0x000640U, 0x610000U, 0x0C0800U, 0x0040A0U, 0x020088U, 0x081100U, 0x202010U, 0x108040U, 0x020084U, 0x140010U, + 0x019000U, 0x002300U, 0x020080U, 0x020081U, 0x020082U, 0x400C00U, 0x00C100U, 0x140008U, 0x202004U, 0x021400U, + 0x202002U, 0x000A20U, 0x202000U, 0x202001U, 0x140001U, 0x140000U, 0x400060U, 0x140002U, 0x020090U, 0x140004U, + 0x202008U, 0x094000U, 0x103000U, 0x004082U, 0x004081U, 0x004080U, 0x448000U, 0x000A10U, 0x010500U, 0x004084U, + 0x200900U, 0x088400U, 0x400050U, 0x004088U, 0x0200A0U, 0x012040U, 0x180200U, 0x241000U, 0x0B0000U, 0x000A04U, + 0x400048U, 0x004090U, 0x000A01U, 0x000A00U, 0x202020U, 0x000A02U, 0x400042U, 0x140020U, 0x400040U, 0x400041U, + 0x005400U, 0x000A08U, 0x400044U, 0x028100U, 0x000680U, 0x082010U, 0x201100U, 0x004060U, 0x020048U, 0x240800U, + 0x490000U, 0x108080U, 0x020044U, 0x408100U, 0x102800U, 0x050400U, 0x020040U, 0x020041U, 0x020042U, 0x001210U, + 0x082001U, 0x082000U, 0x068000U, 0x082002U, 0x100120U, 0x082004U, 0x004C00U, 0x001208U, 0x214000U, 0x082008U, + 0x4000A0U, 0x001204U, 0x020050U, 0x001202U, 0x001201U, 0x001200U, 0x018800U, 0x004042U, 0x004041U, 0x004040U, + 0x100110U, 0x401400U, 0x042200U, 0x004044U, 0x0C1000U, 0x300200U, 0x400090U, 0x004048U, 0x020060U, 0x012080U, + 0x208400U, 0x080900U, 0x100104U, 0x082020U, 0x400088U, 0x004050U, 0x100100U, 0x100101U, 0x100102U, 0x230000U, + 0x400082U, 0x020C00U, 0x400080U, 0x400081U, 0x100108U, 0x04C000U, 0x400084U, 0x001220U, 0x02000CU, 0x004022U, + 0x004021U, 0x004020U, 0x020008U, 0x020009U, 0x02000AU, 0x004024U, 0x020004U, 0x020005U, 0x020006U, 0x004028U, + 0x020000U, 0x020001U, 0x020002U, 0x020003U, 0x401800U, 0x082040U, 0x110200U, 0x004030U, 0x020018U, 0x018400U, + 0x202080U, 0x440100U, 0x020014U, 0x140080U, 0x080500U, 0x208800U, 0x020010U, 0x020011U, 0x020012U, 0x001240U, + 0x004003U, 0x004002U, 0x004001U, 0x004000U, 0x020028U, 0x004006U, 0x004005U, 0x004004U, 0x020024U, 0x00400AU, + 0x004009U, 0x004008U, 0x020020U, 0x020021U, 0x020022U, 0x00400CU, 0x240400U, 0x004012U, 0x004011U, 0x004010U, + 0x100140U, 0x000A80U, 0x089000U, 0x004014U, 0x00A200U, 0x011100U, 0x4000C0U, 0x004018U, 0x020030U, 0x680000U, + 0x050800U, 0x102400U, 0x000700U, 0x060020U, 0x201080U, 0x010810U, 0x402800U, 0x081040U, 0x044008U, 0x108100U, + 0x190000U, 0x408080U, 0x044004U, 0x002240U, 0x044002U, 0x200410U, 0x044000U, 0x044001U, 0x00C040U, 0x010802U, + 0x010801U, 0x010800U, 0x1000A0U, 0x200408U, 0x0A0200U, 0x010804U, 0x023000U, 0x200404U, 0x400120U, 0x010808U, + 0x200401U, 0x200400U, 0x044010U, 0x200402U, 0x060001U, 0x060000U, 0x08A000U, 0x060002U, 0x100090U, 0x060004U, + 0x010440U, 0x600200U, 0x200840U, 0x060008U, 0x400110U, 0x101400U, 0x009200U, 0x012100U, 0x044020U, 0x080880U, + 0x100084U, 0x060010U, 0x400108U, 0x010820U, 0x100080U, 0x100081U, 0x100082U, 0x007000U, 0x400102U, 0x084200U, + 0x400100U, 0x400101U, 0x100088U, 0x200420U, 0x400104U, 0x028040U, 0x00C010U, 0x081004U, 0x520000U, 0x002208U, + 0x081001U, 0x081000U, 0x010420U, 0x081002U, 0x200820U, 0x002202U, 0x002201U, 0x002200U, 0x020180U, 0x081008U, + 0x044040U, 0x002204U, 0x00C000U, 0x00C001U, 0x00C002U, 0x010840U, 0x00C004U, 0x081010U, 0x202100U, 0x440080U, + 0x00C008U, 0x140100U, 0x080480U, 0x002210U, 0x410200U, 0x200440U, 0x101800U, 0x028020U, 0x200808U, 0x060040U, + 0x010404U, 0x004180U, 0x010402U, 0x081020U, 0x010400U, 0x010401U, 0x200800U, 0x200801U, 0x200802U, 0x002220U, + 0x200804U, 0x504000U, 0x010408U, 0x028010U, 0x00C020U, 0x402400U, 0x041200U, 0x380000U, 0x1000C0U, 0x000B00U, + 0x010410U, 0x028008U, 0x200810U, 0x011080U, 0x400140U, 0x028004U, 0x0C2000U, 0x028002U, 0x028001U, 0x028000U, + 0x201002U, 0x408008U, 0x201000U, 0x201001U, 0x100030U, 0x014200U, 0x201004U, 0x022400U, 0x408001U, 0x408000U, + 0x201008U, 0x408002U, 0x020140U, 0x408004U, 0x044080U, 0x080820U, 0x100024U, 0x082100U, 0x201010U, 0x010880U, + 0x100020U, 0x100021U, 0x100022U, 0x440040U, 0x040A00U, 0x408010U, 0x080440U, 0x124000U, 0x100028U, 0x200480U, + 0x01A000U, 0x001300U, 0x100014U, 0x060080U, 0x201020U, 0x004140U, 0x100010U, 0x100011U, 0x100012U, 0x080808U, + 0x006400U, 0x408020U, 0x030200U, 0x080804U, 0x100018U, 0x080802U, 0x080801U, 0x080800U, 0x100004U, 0x100005U, + 0x100006U, 0x008600U, 0x100000U, 0x100001U, 0x100002U, 0x100003U, 0x10000CU, 0x011040U, 0x400180U, 0x242000U, + 0x100008U, 0x100009U, 0x10000AU, 0x080810U, 0x052000U, 0x100C00U, 0x201040U, 0x004120U, 0x020108U, 0x081080U, + 0x008A00U, 0x440010U, 0x020104U, 0x408040U, 0x080410U, 0x002280U, 0x020100U, 0x020101U, 0x020102U, 0x310000U, + 0x00C080U, 0x220200U, 0x080408U, 0x440004U, 0x100060U, 0x440002U, 0x440001U, 0x440000U, 0x080402U, 0x011020U, + 0x080400U, 0x080401U, 0x020110U, 0x006800U, 0x080404U, 0x440008U, 0x480200U, 0x004102U, 0x004101U, 0x004100U, + 0x100050U, 0x20A000U, 0x010480U, 0x004104U, 0x200880U, 0x011010U, 0x148000U, 0x004108U, 0x020120U, 0x040600U, + 0x403000U, 0x080840U, 0x100044U, 0x011008U, 0x022800U, 0x004110U, 0x100040U, 0x100041U, 0x100042U, 0x440020U, + 0x011001U, 0x011000U, 0x080420U, 0x011002U, 0x100048U, 0x011004U, 0x204200U, 0x028080U }; + +#define X22 0x00400000 /* vector representation of X^{22} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK12 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Decode Golay (23,12,7) FEC. +/// +/// +/// +uint32_t Golay24128::decode23127(uint32_t code) +{ + uint32_t syndrome = getSyndrome23127(code); + uint32_t error_pattern = DECODING_TABLE_23127[syndrome]; + + code ^= error_pattern; + + return code >> 11; +} + +/// +/// Decode Golay (24,12,8) FEC. +/// +/// +/// +uint32_t Golay24128::decode24128(uint32_t code) +{ + return decode23127(code >> 1); +} + +/// +/// Decode Golay (24,12,8) FEC. +/// +/// Golay FEC encoded data byte array +/// +uint32_t Golay24128::decode24128(uint8_t* bytes) +{ + assert(bytes != NULL); + + uint32_t code = bytes[0U]; + code <<= 8; + code |= bytes[1U]; + code <<= 8; + code |= bytes[2U]; + + return decode23127(code >> 1); +} + +/// +/// Decode Golay (24,12,8) FEC. +/// +/// Data decoded with Golay FEC. +/// Raw data to decode. +/// Length of data to decode. +/// +void Golay24128::decode24128(uint8_t* data, const uint8_t* raw, uint32_t msglen) +{ + uint32_t i = 0; // decoded byte counter + uint32_t j = 0; // encoded byte counter + uint32_t r0, r1, r2, r3, r4, r5; // six 8-bit bytes + uint32_t v0, v1; // two 24-bit encoded symbols + uint32_t m0_hat, m1_hat; // two 12-bit decoded symbols + + // determine remainder of input length / 3 + uint32_t r = msglen % 3; + + for (i = 0; i < msglen - r; i += 3) { + // strip six input bytes (two encoded symbols) + r0 = raw[j + 0]; + r1 = raw[j + 1]; + r2 = raw[j + 2]; + r3 = raw[j + 3]; + r4 = raw[j + 4]; + r5 = raw[j + 5]; + + // pack six 8-bit symbols into two 24-bit symbols + v0 = ((r0 << 16) & 0xff0000) | ((r1 << 8) & 0x00ff00) | ((r2) & 0x0000ff); + v1 = ((r3 << 16) & 0xff0000) | ((r4 << 8) & 0x00ff00) | ((r5 << 0) & 0x0000ff); + + // decode each symbol into a 12-bit symbol + m0_hat = decode24128(v0); + m1_hat = decode24128(v1); + + // unpack two 12-bit symbols into three 8-bit bytes + data[i + 0] = ((m0_hat >> 4) & 0xff); + data[i + 1] = ((m0_hat << 4) & 0xf0) | ((m1_hat >> 8) & 0x0f); + data[i + 2] = ((m1_hat) & 0xff); + + j += 6; + } + + // if input length isn't divisible by 3, decode last 1 or two bytes + for (i = msglen - r; i < msglen; i++) { + // strip last input symbol (three bytes) + r0 = raw[j + 0]; + r1 = raw[j + 1]; + r2 = raw[j + 2]; + + // pack three 8-bit symbols into one 24-bit symbol + v0 = ((r0 << 16) & 0xff0000) | ((r1 << 8) & 0x00ff00) | ((r2) & 0x0000ff); + + // decode into a 12-bit symbol + m0_hat = decode24128(v0); + + // retain last 8 bits of 12-bit symbol + data[i] = m0_hat & 0xff; + + j += 3; + } +} + +/// +/// Encode Golay (23,12,7) FEC. +/// +/// Data to encode with Golay FEC. +/// +uint32_t Golay24128::encode23127(uint32_t data) +{ + return ENCODING_TABLE_23127[data]; +} + +/// +/// Encode Golay (24,12,8) FEC. +/// +/// Data to encode with Golay FEC. +/// +uint32_t Golay24128::encode24128(uint32_t data) +{ + return ENCODING_TABLE_24128[data]; +} + +/// +/// Encode Golay (24,12,8) FEC. +/// +/// Data encoded with Golay FEC. +/// Raw data to encode. +/// Length of data to encode. +void Golay24128::encode24128(uint8_t* data, const uint8_t* raw, uint32_t msglen) +{ + uint32_t j = 0; + uint32_t s0, s1, s2; // three 8-bit symbols + uint32_t m0, m1; // two 12-bit symbols (uncoded) + uint32_t v0, v1; // two 24-bit symbols (encoded) + + // determine remainder of input length / 3 + uint32_t r = msglen % 3; + + for (uint32_t i = 0; i < msglen - r; i += 3) { + // strip three input bytes (two uncoded symbols) + s0 = raw[i + 0]; + s1 = raw[i + 1]; + s2 = raw[i + 2]; + + // pack into two 12-bit symbols + m0 = ((s0 << 4) & 0x0ff0) | ((s1 >> 4) & 0x000f); + m1 = ((s1 << 8) & 0x0f00) | ((s2) & 0x00ff); + + // encode each 12-bit symbol into a 24-bit symbol + v0 = encode24128(m0); + v1 = encode24128(m1); + + // unpack two 24-bit symbols into six 8-bit bytes + // retaining order of bits in output + data[j + 0] = (v0 >> 16) & 0xff; + data[j + 1] = (v0 >> 8) & 0xff; + data[j + 2] = (v0) & 0xff; + data[j + 3] = (v1 >> 16) & 0xff; + data[j + 4] = (v1 >> 8) & 0xff; + data[j + 5] = (v1) & 0xff; + + j += 6; + } + + // if input length isn't divisible by 3, encode last 1 or two bytes + for (uint32_t i = msglen - r; i < msglen; i++) { + // strip last input symbol + s0 = raw[i]; + + // extend as 12-bit symbol + m0 = s0; + + // encode into 24-bit symbol + v0 = encode24128(m0); + + // unpack one 24-bit symbol into three 8-bit bytes, and + // append to output array + data[j + 0] = (v0 >> 16) & 0xff; + data[j + 1] = (v0 >> 8) & 0xff; + data[j + 2] = (v0) & 0xff; + + j += 3; + } +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// Compute the syndrome corresponding to the given pattern, i.e., the +/// remainder after dividing the pattern (when considering it as the vector +/// representation of a polynomial) by the generator polynomial, GENPOL. +/// In the program this pattern has several meanings: (1) pattern = infomation +/// bits, when constructing the encoding table; (2) pattern = error pattern, +/// when constructing the decoding table; and (3) pattern = received vector, to +/// obtain its syndrome in decoding. +/// +/// +/// +uint32_t Golay24128::getSyndrome23127(uint32_t pattern) +{ + uint32_t aux = X22; + + if (pattern >= X11) { + while (pattern & MASK12) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} diff --git a/edac/Golay24128.h b/edac/Golay24128.h new file mode 100644 index 00000000..63c14b21 --- /dev/null +++ b/edac/Golay24128.h @@ -0,0 +1,68 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2010,2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__GOLAY24128_H__) +#define __GOLAY24128_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Golay (23,12,7) and Golay (24,12,8) forward error + // correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API Golay24128 { + public: + /// Decode Golay (23,12,7) FEC. + static uint32_t decode23127(uint32_t code); + /// Decode Golay (24,12,8) FEC. + static uint32_t decode24128(uint32_t code); + /// Decode Golay (24,12,8) FEC. + static uint32_t decode24128(uint8_t* bytes); + /// Decode Golay (24,12,8) FEC. + static void decode24128(uint8_t* data, const uint8_t* raw, uint32_t msglen); + + /// Encode Golay (23,12,7) FEC. + static uint32_t encode23127(uint32_t data); + /// Encode Golay (24,12,8) FEC. + static uint32_t encode24128(uint32_t data); + /// Encode Golay (24,12,8) FEC. + static void encode24128(uint8_t* data, const uint8_t* raw, uint32_t msglen); + + private: + /// + static uint32_t getSyndrome23127(uint32_t pattern); + }; +} // namespace edac + +#endif // __GOLAY24128_H__ diff --git a/edac/Hamming.cpp b/edac/Hamming.cpp new file mode 100644 index 00000000..f821d73a --- /dev/null +++ b/edac/Hamming.cpp @@ -0,0 +1,412 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "edac/Hamming.h" + +using namespace edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Decode Hamming (15,11,3). +/// +/// Boolean bit array. +/// True, if bit errors are detected, otherwise false. +bool Hamming::decode15113_1(bool* d) +{ + assert(d != NULL); + + // Calculate the parity it should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9]; + bool c2 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10]; + bool c3 = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10]; + + unsigned char n = 0U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + + // Data bit errors + case 0x0FU: d[0] = !d[0]; return true; + case 0x07U: d[1] = !d[1]; return true; + case 0x0BU: d[2] = !d[2]; return true; + case 0x03U: d[3] = !d[3]; return true; + case 0x0DU: d[4] = !d[4]; return true; + case 0x05U: d[5] = !d[5]; return true; + case 0x09U: d[6] = !d[6]; return true; + case 0x0EU: d[7] = !d[7]; return true; + case 0x06U: d[8] = !d[8]; return true; + case 0x0AU: d[9] = !d[9]; return true; + case 0x0CU: d[10] = !d[10]; return true; + + // No bit errors + default: return false; + } +} + +/// +/// Encode Hamming (15,11,3). +/// +/// Boolean bit array. +void Hamming::encode15113_1(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[6]; + d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[7] ^ d[8] ^ d[9]; + d[13] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[8] ^ d[10]; + d[14] = d[0] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[9] ^ d[10]; +} + +/// +/// Decode Hamming (15,11,3). +/// +/// Boolean bit array. +/// True, if bit errors are detected, otherwise false. +bool Hamming::decode15113_2(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + + unsigned char n = 0x00U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + + // Data bit errors + case 0x09U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x0FU: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x05U: d[5] = !d[5]; return true; + case 0x0AU: d[6] = !d[6]; return true; + case 0x0DU: d[7] = !d[7]; return true; + case 0x03U: d[8] = !d[8]; return true; + case 0x06U: d[9] = !d[9]; return true; + case 0x0CU: d[10] = !d[10]; return true; + + // No bit errors + default: return false; + } +} + +/// +/// Encode Hamming (15,11,3). +/// +/// Boolean bit array. +void Hamming::encode15113_2(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this row should have + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; +} + +/// +/// Decode Hamming (13,9,3). +/// +/// Boolean bit array. +/// True, if bit errors are detected, otherwise false. +bool Hamming::decode1393(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7]; + bool c2 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c3 = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8]; + + unsigned char n = 0x00U; + n |= (c0 != d[9]) ? 0x01U : 0x00U; + n |= (c1 != d[10]) ? 0x02U : 0x00U; + n |= (c2 != d[11]) ? 0x04U : 0x00U; + n |= (c3 != d[12]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[9] = !d[9]; return true; + case 0x02U: d[10] = !d[10]; return true; + case 0x04U: d[11] = !d[11]; return true; + case 0x08U: d[12] = !d[12]; return true; + + // Data bit erros + case 0x0FU: d[0] = !d[0]; return true; + case 0x07U: d[1] = !d[1]; return true; + case 0x0EU: d[2] = !d[2]; return true; + case 0x05U: d[3] = !d[3]; return true; + case 0x0AU: d[4] = !d[4]; return true; + case 0x0DU: d[5] = !d[5]; return true; + case 0x03U: d[6] = !d[6]; return true; + case 0x06U: d[7] = !d[7]; return true; + case 0x0CU: d[8] = !d[8]; return true; + + // No bit errors + default: return false; + } +} + +/// +/// Encode Hamming (13,9,3). +/// +/// Boolean bit array. +void Hamming::encode1393(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + d[9] = d[0] ^ d[1] ^ d[3] ^ d[5] ^ d[6]; + d[10] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7]; + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[0] ^ d[2] ^ d[4] ^ d[5] ^ d[8]; +} + +/// +/// Decode Hamming (10,6,3). +/// +/// Boolean bit array. +/// True, if bit errors are detected, otherwise false. +bool Hamming::decode1063(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[5]; + bool c1 = d[0] ^ d[1] ^ d[3] ^ d[5]; + bool c2 = d[0] ^ d[2] ^ d[3] ^ d[4]; + bool c3 = d[1] ^ d[2] ^ d[3] ^ d[4]; + + unsigned char n = 0x00U; + n |= (c0 != d[6]) ? 0x01U : 0x00U; + n |= (c1 != d[7]) ? 0x02U : 0x00U; + n |= (c2 != d[8]) ? 0x04U : 0x00U; + n |= (c3 != d[9]) ? 0x08U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[6] = !d[6]; return true; + case 0x02U: d[7] = !d[7]; return true; + case 0x04U: d[8] = !d[8]; return true; + case 0x08U: d[9] = !d[9]; return true; + + // Data bit erros + case 0x07U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x0DU: d[2] = !d[2]; return true; + case 0x0EU: d[3] = !d[3]; return true; + case 0x0CU: d[4] = !d[4]; return true; + case 0x03U: d[5] = !d[5]; return true; + + // No bit errors + default: return false; + } +} + +/// +/// Encode Hamming (10,6,3). +/// +/// Boolean bit array. +void Hamming::encode1063(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + d[6] = d[0] ^ d[1] ^ d[2] ^ d[5]; + d[7] = d[0] ^ d[1] ^ d[3] ^ d[5]; + d[8] = d[0] ^ d[2] ^ d[3] ^ d[4]; + d[9] = d[1] ^ d[2] ^ d[3] ^ d[4]; +} + +/// +/// Decode Hamming (16,11,4). +/// +/// Boolean bit array. +/// True, if bit errors are detected or no bit errors, otherwise false if unrecoverable errors are detected. +bool Hamming::decode16114(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + bool c1 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + bool c2 = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + bool c3 = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + bool c4 = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10]; + + // Compare these with the actual bits + unsigned char n = 0x00U; + n |= (c0 != d[11]) ? 0x01U : 0x00U; + n |= (c1 != d[12]) ? 0x02U : 0x00U; + n |= (c2 != d[13]) ? 0x04U : 0x00U; + n |= (c3 != d[14]) ? 0x08U : 0x00U; + n |= (c4 != d[15]) ? 0x10U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[11] = !d[11]; return true; + case 0x02U: d[12] = !d[12]; return true; + case 0x04U: d[13] = !d[13]; return true; + case 0x08U: d[14] = !d[14]; return true; + case 0x10U: d[15] = !d[15]; return true; + + // Data bit errors + case 0x19U: d[0] = !d[0]; return true; + case 0x0BU: d[1] = !d[1]; return true; + case 0x1FU: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x15U: d[5] = !d[5]; return true; + case 0x1AU: d[6] = !d[6]; return true; + case 0x0DU: d[7] = !d[7]; return true; + case 0x13U: d[8] = !d[8]; return true; + case 0x16U: d[9] = !d[9]; return true; + case 0x1CU: d[10] = !d[10]; return true; + + // No bit errors + case 0x00U: return true; + + // Unrecoverable errors + default: return false; + } +} + +/// +/// Encode Hamming (10,6,3). +/// +/// Boolean bit array. +void Hamming::encode16114(bool* d) +{ + assert(d != NULL); + + d[11] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[5] ^ d[7] ^ d[8]; + d[12] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[6] ^ d[8] ^ d[9]; + d[13] = d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[7] ^ d[9] ^ d[10]; + d[14] = d[0] ^ d[1] ^ d[2] ^ d[4] ^ d[6] ^ d[7] ^ d[10]; + d[15] = d[0] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[9] ^ d[10]; +} + +/// +/// Decode Hamming (17,12,3). +/// +/// Boolean bit array. +/// True, if bit errors are detected or no bit errors, otherwise false if unrecoverable errors are detected. +bool Hamming::decode17123(bool* d) +{ + assert(d != NULL); + + // Calculate the checksum this column should have + bool c0 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9]; + bool c1 = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10]; + bool c2 = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11]; + bool c3 = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10]; + bool c4 = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11]; + + // Compare these with the actual bits + unsigned char n = 0x00U; + n |= (c0 != d[12]) ? 0x01U : 0x00U; + n |= (c1 != d[13]) ? 0x02U : 0x00U; + n |= (c2 != d[14]) ? 0x04U : 0x00U; + n |= (c3 != d[15]) ? 0x08U : 0x00U; + n |= (c4 != d[16]) ? 0x10U : 0x00U; + + switch (n) { + // Parity bit errors + case 0x01U: d[12] = !d[12]; return true; + case 0x02U: d[13] = !d[13]; return true; + case 0x04U: d[14] = !d[14]; return true; + case 0x08U: d[15] = !d[15]; return true; + case 0x10U: d[16] = !d[16]; return true; + + // Data bit errors + case 0x1BU: d[0] = !d[0]; return true; + case 0x1FU: d[1] = !d[1]; return true; + case 0x17U: d[2] = !d[2]; return true; + case 0x07U: d[3] = !d[3]; return true; + case 0x0EU: d[4] = !d[4]; return true; + case 0x1CU: d[5] = !d[5]; return true; + case 0x11U: d[6] = !d[6]; return true; + case 0x0BU: d[7] = !d[7]; return true; + case 0x16U: d[8] = !d[8]; return true; + case 0x05U: d[9] = !d[9]; return true; + case 0x0AU: d[10] = !d[10]; return true; + case 0x14U: d[11] = !d[11]; return true; + + // No bit errors + case 0x00U: return true; + + // Unrecoverable errors + default: return false; + } +} + +/// +/// Encode Hamming (17,12,3). +/// +/// Boolean bit array. +void Hamming::encode17123(bool* d) +{ + assert(d != NULL); + + d[12] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[6] ^ d[7] ^ d[9]; + d[13] = d[0] ^ d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[7] ^ d[8] ^ d[10]; + d[14] = d[1] ^ d[2] ^ d[3] ^ d[4] ^ d[5] ^ d[8] ^ d[9] ^ d[11]; + d[15] = d[0] ^ d[1] ^ d[4] ^ d[5] ^ d[7] ^ d[10]; + d[16] = d[0] ^ d[1] ^ d[2] ^ d[5] ^ d[6] ^ d[8] ^ d[11]; +} diff --git a/edac/Hamming.h b/edac/Hamming.h new file mode 100644 index 00000000..5abe8ca4 --- /dev/null +++ b/edac/Hamming.h @@ -0,0 +1,77 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HAMMING_H__) +#define __HAMMING_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Hamming (15,11,3), (13,9,3), (10,6,3), (16,11,4) and + // (17, 12, 3) forward error correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API Hamming { + public: + /// Decode Hamming (15,11,3). + static bool decode15113_1(bool* d); + /// Encode Hamming (15,11,3). + static void encode15113_1(bool* d); + + /// Decode Hamming (15,11,3). + static bool decode15113_2(bool* d); + /// Encode Hamming (15,11,3). + static void encode15113_2(bool* d); + + /// Decode Hamming (13,9,3). + static bool decode1393(bool* d); + /// Encode Hamming (13,9,3). + static void encode1393(bool* d); + + /// Decode Hamming (10,6,3). + static bool decode1063(bool* d); + /// Encode Hamming (10,6,3). + static void encode1063(bool* d); + + /// Decode Hamming (16,11,4). + static bool decode16114(bool* d); + /// Encode Hamming (16,11,4). + static void encode16114(bool* d); + + /// Decode Hamming (17,12,3). + static bool decode17123(bool* d); + /// Encode Hamming (17,12,3). + static void encode17123(bool* d); + }; +} // namespace edac + +#endif // __HAMMING_H__ diff --git a/edac/QR1676.cpp b/edac/QR1676.cpp new file mode 100644 index 00000000..8cce71d1 --- /dev/null +++ b/edac/QR1676.cpp @@ -0,0 +1,152 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/QR1676.h" + +using namespace edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t ENCODING_TABLE_1676[] = { + 0x0000U, 0x0273U, 0x04E5U, 0x0696U, 0x09C9U, 0x0BBAU, 0x0D2CU, 0x0F5FU, 0x11E2U, 0x1391U, 0x1507U, 0x1774U, + 0x182BU, 0x1A58U, 0x1CCEU, 0x1EBDU, 0x21B7U, 0x23C4U, 0x2552U, 0x2721U, 0x287EU, 0x2A0DU, 0x2C9BU, 0x2EE8U, + 0x3055U, 0x3226U, 0x34B0U, 0x36C3U, 0x399CU, 0x3BEFU, 0x3D79U, 0x3F0AU, 0x411EU, 0x436DU, 0x45FBU, 0x4788U, + 0x48D7U, 0x4AA4U, 0x4C32U, 0x4E41U, 0x50FCU, 0x528FU, 0x5419U, 0x566AU, 0x5935U, 0x5B46U, 0x5DD0U, 0x5FA3U, + 0x60A9U, 0x62DAU, 0x644CU, 0x663FU, 0x6960U, 0x6B13U, 0x6D85U, 0x6FF6U, 0x714BU, 0x7338U, 0x75AEU, 0x77DDU, + 0x7882U, 0x7AF1U, 0x7C67U, 0x7E14U, 0x804FU, 0x823CU, 0x84AAU, 0x86D9U, 0x8986U, 0x8BF5U, 0x8D63U, 0x8F10U, + 0x91ADU, 0x93DEU, 0x9548U, 0x973BU, 0x9864U, 0x9A17U, 0x9C81U, 0x9EF2U, 0xA1F8U, 0xA38BU, 0xA51DU, 0xA76EU, + 0xA831U, 0xAA42U, 0xACD4U, 0xAEA7U, 0xB01AU, 0xB269U, 0xB4FFU, 0xB68CU, 0xB9D3U, 0xBBA0U, 0xBD36U, 0xBF45U, + 0xC151U, 0xC322U, 0xC5B4U, 0xC7C7U, 0xC898U, 0xCAEBU, 0xCC7DU, 0xCE0EU, 0xD0B3U, 0xD2C0U, 0xD456U, 0xD625U, + 0xD97AU, 0xDB09U, 0xDD9FU, 0xDFECU, 0xE0E6U, 0xE295U, 0xE403U, 0xE670U, 0xE92FU, 0xEB5CU, 0xEDCAU, 0xEFB9U, + 0xF104U, 0xF377U, 0xF5E1U, 0xF792U, 0xF8CDU, 0xFABEU, 0xFC28U, 0xFE5BU }; + +const uint32_t DECODING_TABLE_1576[] = { + 0x0000U, 0x0001U, 0x0002U, 0x0003U, 0x0004U, 0x0005U, 0x0006U, 0x4020U, 0x0008U, 0x0009U, 0x000AU, 0x000BU, + 0x000CU, 0x000DU, 0x2081U, 0x2080U, 0x0010U, 0x0011U, 0x0012U, 0x0013U, 0x0014U, 0x0C00U, 0x0016U, 0x0C02U, + 0x0018U, 0x0120U, 0x001AU, 0x0122U, 0x4102U, 0x0124U, 0x4100U, 0x4101U, 0x0020U, 0x0021U, 0x0022U, 0x4004U, + 0x0024U, 0x4002U, 0x4001U, 0x4000U, 0x0028U, 0x0110U, 0x1800U, 0x1801U, 0x002CU, 0x400AU, 0x4009U, 0x4008U, + 0x0030U, 0x0108U, 0x0240U, 0x0241U, 0x0034U, 0x4012U, 0x4011U, 0x4010U, 0x0101U, 0x0100U, 0x0103U, 0x0102U, + 0x0105U, 0x0104U, 0x1401U, 0x1400U, 0x0040U, 0x0041U, 0x0042U, 0x0043U, 0x0044U, 0x0045U, 0x0046U, 0x4060U, + 0x0048U, 0x0049U, 0x0301U, 0x0300U, 0x004CU, 0x1600U, 0x0305U, 0x0304U, 0x0050U, 0x0051U, 0x0220U, 0x0221U, + 0x3000U, 0x4200U, 0x3002U, 0x4202U, 0x0058U, 0x1082U, 0x1081U, 0x1080U, 0x3008U, 0x4208U, 0x2820U, 0x1084U, + 0x0060U, 0x0061U, 0x0210U, 0x0211U, 0x0480U, 0x0481U, 0x4041U, 0x4040U, 0x0068U, 0x2402U, 0x2401U, 0x2400U, + 0x0488U, 0x3100U, 0x2810U, 0x2404U, 0x0202U, 0x0880U, 0x0200U, 0x0201U, 0x0206U, 0x0884U, 0x0204U, 0x0205U, + 0x0141U, 0x0140U, 0x0208U, 0x0209U, 0x2802U, 0x0144U, 0x2800U, 0x2801U, 0x0080U, 0x0081U, 0x0082U, 0x0A00U, + 0x0084U, 0x0085U, 0x2009U, 0x2008U, 0x0088U, 0x0089U, 0x2005U, 0x2004U, 0x2003U, 0x2002U, 0x2001U, 0x2000U, + 0x0090U, 0x0091U, 0x0092U, 0x1048U, 0x0602U, 0x0C80U, 0x0600U, 0x0601U, 0x0098U, 0x1042U, 0x1041U, 0x1040U, + 0x2013U, 0x2012U, 0x2011U, 0x2010U, 0x00A0U, 0x00A1U, 0x00A2U, 0x4084U, 0x0440U, 0x0441U, 0x4081U, 0x4080U, + 0x6000U, 0x1200U, 0x6002U, 0x1202U, 0x6004U, 0x2022U, 0x2021U, 0x2020U, 0x0841U, 0x0840U, 0x2104U, 0x0842U, + 0x2102U, 0x0844U, 0x2100U, 0x2101U, 0x0181U, 0x0180U, 0x0B00U, 0x0182U, 0x5040U, 0x0184U, 0x2108U, 0x2030U, + 0x00C0U, 0x00C1U, 0x4401U, 0x4400U, 0x0420U, 0x0421U, 0x0422U, 0x4404U, 0x0900U, 0x0901U, 0x1011U, 0x1010U, + 0x0904U, 0x2042U, 0x2041U, 0x2040U, 0x0821U, 0x0820U, 0x1009U, 0x1008U, 0x4802U, 0x0824U, 0x4800U, 0x4801U, + 0x1003U, 0x1002U, 0x1001U, 0x1000U, 0x0501U, 0x0500U, 0x1005U, 0x1004U, 0x0404U, 0x0810U, 0x1100U, 0x1101U, + 0x0400U, 0x0401U, 0x0402U, 0x0403U, 0x040CU, 0x0818U, 0x1108U, 0x1030U, 0x0408U, 0x0409U, 0x040AU, 0x2060U, + 0x0801U, 0x0800U, 0x0280U, 0x0802U, 0x0410U, 0x0804U, 0x0412U, 0x0806U, 0x0809U, 0x0808U, 0x1021U, 0x1020U, + 0x5000U, 0x2200U, 0x5002U, 0x2202U }; + +#define X14 0x00004000 /* vector representation of X^{14} */ +#define X8 0x00000100 /* vector representation of X^{8} */ +#define MASK7 0xffffff00 /* auxiliary vector for testing */ +#define GENPOL 0x00000139 /* generator polinomial, g(x) */ + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Decode QR (16,7,6) FEC. +/// +/// +/// +uint8_t QR1676::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint32_t code = (data[0U] << 7) + (data[1U] >> 1); + uint32_t syndrome = getSyndrome1576(code); + uint32_t error_pattern = DECODING_TABLE_1576[syndrome]; + + code ^= error_pattern; + + return code >> 7; +} + +/// +/// Encode QR (16,7,6) FEC. +/// +/// Compute the EMB against a precomputed list of correct words. +/// +void QR1676::encode(uint8_t* data) +{ + assert(data != NULL); + + uint32_t value = (data[0U] >> 1) & 0x7FU; + uint32_t cksum = ENCODING_TABLE_1676[value]; + + data[0U] = cksum >> 8; + data[1U] = cksum & 0xFFU; +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// Compute the syndrome corresponding to the given pattern, i.e., the +/// remainder after dividing the pattern (when considering it as the vector +/// representation of a polynomial) by the generator polynomial, GENPOL. +/// In the program this pattern has several meanings: (1) pattern = infomation +/// bits, when constructing the encoding table; (2) pattern = error pattern, +/// when constructing the decoding table; and (3) pattern = received vector, to +/// obtain its syndrome in decoding. +/// +/// +/// +uint32_t QR1676::getSyndrome1576(uint32_t pattern) +{ + uint32_t aux = X14; + + if (pattern >= X8) { + while (pattern & MASK7) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X8) * GENPOL; + } + } + + return pattern; +} diff --git a/edac/QR1676.h b/edac/QR1676.h new file mode 100644 index 00000000..0b3106c7 --- /dev/null +++ b/edac/QR1676.h @@ -0,0 +1,55 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__QR1676_H__) +#define __QR1676_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Quadratic residue (16,7,6) forward error correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API QR1676 { + public: + /// Decode QR (16,7,6) FEC. + static uint8_t decode(const uint8_t* data); + /// Encode QR (16,7,6) FEC. + static void encode(uint8_t* data); + + private: + /// + static uint32_t getSyndrome1576(uint32_t pattern); + }; +} // namespace edac + +#endif // __QR1676_H__ diff --git a/edac/RS129.cpp b/edac/RS129.cpp new file mode 100644 index 00000000..19f3e160 --- /dev/null +++ b/edac/RS129.cpp @@ -0,0 +1,170 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/RS129.h" + +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t NPAR = 3U; + +/* Maximum degree of various polynomials. */ +const uint32_t MAXDEG = NPAR * 2U; + +/* Generator Polynomial */ +const uint8_t POLY[] = { 64U, 56U, 14U, 1U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U }; + +const uint8_t EXP_TABLE[] = { + 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U, + 0x4CU, 0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U, + 0x9DU, 0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U, + 0x46U, 0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U, + 0x5FU, 0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U, + 0xFDU, 0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U, + 0xD9U, 0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU, + 0x81U, 0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU, + 0x85U, 0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U, + 0xA8U, 0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U, + 0xE6U, 0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU, + 0xE3U, 0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U, + 0x82U, 0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U, + 0x51U, 0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U, + 0x12U, 0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U, + 0x2CU, 0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U, + 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1DU, 0x3AU, 0x74U, 0xE8U, 0xCDU, 0x87U, 0x13U, 0x26U, 0x4CU, + 0x98U, 0x2DU, 0x5AU, 0xB4U, 0x75U, 0xEAU, 0xC9U, 0x8FU, 0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0xC0U, 0x9DU, + 0x27U, 0x4EU, 0x9CU, 0x25U, 0x4AU, 0x94U, 0x35U, 0x6AU, 0xD4U, 0xB5U, 0x77U, 0xEEU, 0xC1U, 0x9FU, 0x23U, 0x46U, + 0x8CU, 0x05U, 0x0AU, 0x14U, 0x28U, 0x50U, 0xA0U, 0x5DU, 0xBAU, 0x69U, 0xD2U, 0xB9U, 0x6FU, 0xDEU, 0xA1U, 0x5FU, + 0xBEU, 0x61U, 0xC2U, 0x99U, 0x2FU, 0x5EU, 0xBCU, 0x65U, 0xCAU, 0x89U, 0x0FU, 0x1EU, 0x3CU, 0x78U, 0xF0U, 0xFDU, + 0xE7U, 0xD3U, 0xBBU, 0x6BU, 0xD6U, 0xB1U, 0x7FU, 0xFEU, 0xE1U, 0xDFU, 0xA3U, 0x5BU, 0xB6U, 0x71U, 0xE2U, 0xD9U, + 0xAFU, 0x43U, 0x86U, 0x11U, 0x22U, 0x44U, 0x88U, 0x0DU, 0x1AU, 0x34U, 0x68U, 0xD0U, 0xBDU, 0x67U, 0xCEU, 0x81U, + 0x1FU, 0x3EU, 0x7CU, 0xF8U, 0xEDU, 0xC7U, 0x93U, 0x3BU, 0x76U, 0xECU, 0xC5U, 0x97U, 0x33U, 0x66U, 0xCCU, 0x85U, + 0x17U, 0x2EU, 0x5CU, 0xB8U, 0x6DU, 0xDAU, 0xA9U, 0x4FU, 0x9EU, 0x21U, 0x42U, 0x84U, 0x15U, 0x2AU, 0x54U, 0xA8U, + 0x4DU, 0x9AU, 0x29U, 0x52U, 0xA4U, 0x55U, 0xAAU, 0x49U, 0x92U, 0x39U, 0x72U, 0xE4U, 0xD5U, 0xB7U, 0x73U, 0xE6U, + 0xD1U, 0xBFU, 0x63U, 0xC6U, 0x91U, 0x3FU, 0x7EU, 0xFCU, 0xE5U, 0xD7U, 0xB3U, 0x7BU, 0xF6U, 0xF1U, 0xFFU, 0xE3U, + 0xDBU, 0xABU, 0x4BU, 0x96U, 0x31U, 0x62U, 0xC4U, 0x95U, 0x37U, 0x6EU, 0xDCU, 0xA5U, 0x57U, 0xAEU, 0x41U, 0x82U, + 0x19U, 0x32U, 0x64U, 0xC8U, 0x8DU, 0x07U, 0x0EU, 0x1CU, 0x38U, 0x70U, 0xE0U, 0xDDU, 0xA7U, 0x53U, 0xA6U, 0x51U, + 0xA2U, 0x59U, 0xB2U, 0x79U, 0xF2U, 0xF9U, 0xEFU, 0xC3U, 0x9BU, 0x2BU, 0x56U, 0xACU, 0x45U, 0x8AU, 0x09U, 0x12U, + 0x24U, 0x48U, 0x90U, 0x3DU, 0x7AU, 0xF4U, 0xF5U, 0xF7U, 0xF3U, 0xFBU, 0xEBU, 0xCBU, 0x8BU, 0x0BU, 0x16U, 0x2CU, + 0x58U, 0xB0U, 0x7DU, 0xFAU, 0xE9U, 0xCFU, 0x83U, 0x1BU, 0x36U, 0x6CU, 0xD8U, 0xADU, 0x47U, 0x8EU, 0x01U, 0x00U }; + +const uint8_t LOG_TABLE[] = { + 0x00U, 0x00U, 0x01U, 0x19U, 0x02U, 0x32U, 0x1AU, 0xC6U, 0x03U, 0xDFU, 0x33U, 0xEEU, 0x1BU, 0x68U, 0xC7U, 0x4BU, + 0x04U, 0x64U, 0xE0U, 0x0EU, 0x34U, 0x8DU, 0xEFU, 0x81U, 0x1CU, 0xC1U, 0x69U, 0xF8U, 0xC8U, 0x08U, 0x4CU, 0x71U, + 0x05U, 0x8AU, 0x65U, 0x2FU, 0xE1U, 0x24U, 0x0FU, 0x21U, 0x35U, 0x93U, 0x8EU, 0xDAU, 0xF0U, 0x12U, 0x82U, 0x45U, + 0x1DU, 0xB5U, 0xC2U, 0x7DU, 0x6AU, 0x27U, 0xF9U, 0xB9U, 0xC9U, 0x9AU, 0x09U, 0x78U, 0x4DU, 0xE4U, 0x72U, 0xA6U, + 0x06U, 0xBFU, 0x8BU, 0x62U, 0x66U, 0xDDU, 0x30U, 0xFDU, 0xE2U, 0x98U, 0x25U, 0xB3U, 0x10U, 0x91U, 0x22U, 0x88U, + 0x36U, 0xD0U, 0x94U, 0xCEU, 0x8FU, 0x96U, 0xDBU, 0xBDU, 0xF1U, 0xD2U, 0x13U, 0x5CU, 0x83U, 0x38U, 0x46U, 0x40U, + 0x1EU, 0x42U, 0xB6U, 0xA3U, 0xC3U, 0x48U, 0x7EU, 0x6EU, 0x6BU, 0x3AU, 0x28U, 0x54U, 0xFAU, 0x85U, 0xBAU, 0x3DU, + 0xCAU, 0x5EU, 0x9BU, 0x9FU, 0x0AU, 0x15U, 0x79U, 0x2BU, 0x4EU, 0xD4U, 0xE5U, 0xACU, 0x73U, 0xF3U, 0xA7U, 0x57U, + 0x07U, 0x70U, 0xC0U, 0xF7U, 0x8CU, 0x80U, 0x63U, 0x0DU, 0x67U, 0x4AU, 0xDEU, 0xEDU, 0x31U, 0xC5U, 0xFEU, 0x18U, + 0xE3U, 0xA5U, 0x99U, 0x77U, 0x26U, 0xB8U, 0xB4U, 0x7CU, 0x11U, 0x44U, 0x92U, 0xD9U, 0x23U, 0x20U, 0x89U, 0x2EU, + 0x37U, 0x3FU, 0xD1U, 0x5BU, 0x95U, 0xBCU, 0xCFU, 0xCDU, 0x90U, 0x87U, 0x97U, 0xB2U, 0xDCU, 0xFCU, 0xBEU, 0x61U, + 0xF2U, 0x56U, 0xD3U, 0xABU, 0x14U, 0x2AU, 0x5DU, 0x9EU, 0x84U, 0x3CU, 0x39U, 0x53U, 0x47U, 0x6DU, 0x41U, 0xA2U, + 0x1FU, 0x2DU, 0x43U, 0xD8U, 0xB7U, 0x7BU, 0xA4U, 0x76U, 0xC4U, 0x17U, 0x49U, 0xECU, 0x7FU, 0x0CU, 0x6FU, 0xF6U, + 0x6CU, 0xA1U, 0x3BU, 0x52U, 0x29U, 0x9DU, 0x55U, 0xAAU, 0xFBU, 0x60U, 0x86U, 0xB1U, 0xBBU, 0xCCU, 0x3EU, 0x5AU, + 0xCBU, 0x59U, 0x5FU, 0xB0U, 0x9CU, 0xA9U, 0xA0U, 0x51U, 0x0BU, 0xF5U, 0x16U, 0xEBU, 0x7AU, 0x75U, 0x2CU, 0xD7U, + 0x4FU, 0xAEU, 0xD5U, 0xE9U, 0xE6U, 0xE7U, 0xADU, 0xE8U, 0x74U, 0xD6U, 0xF4U, 0xEAU, 0xA8U, 0x50U, 0x58U, 0xAFU }; + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Check RS (12,9) FEC. +/// +/// +/// +bool RS129::check(const uint8_t* in) +{ + assert(in != NULL); + + uint8_t parity[4U]; + encode(in, 9U, parity); + + return in[9U] == parity[2U] && in[10U] == parity[1U] && in[11U] == parity[0U]; +} + +/// +/// Encode RS (12,9) FEC. +/// +/// +/// Simulate a LFSR with generator polynomial for n byte RS code. +/// Pass in a pointer to the data array, and amount of data. +/// +/// The parity bytes are deposited into parity. +/// +/// +/// +/// +void RS129::encode(const uint8_t* msg, uint32_t nbytes, uint8_t* parity) +{ + assert(msg != NULL); + assert(parity != NULL); + + for (uint32_t i = 0U; i < NPAR + 1U; i++) + parity[i] = 0x00U; + + for (uint32_t i = 0U; i < nbytes; i++) { + uint8_t dbyte = msg[i] ^ parity[NPAR - 1U]; + + for (int j = NPAR - 1; j > 0; j--) + parity[j] = parity[j - 1] ^ gmult(POLY[j], dbyte); + + parity[0] = gmult(POLY[0], dbyte); + } +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// Multiplication using logarithms. +/// +/// +/// +uint8_t RS129::gmult(uint8_t a, uint8_t b) +{ + if (a == 0U || b == 0U) + return 0U; + + uint32_t i = LOG_TABLE[a]; + uint32_t j = LOG_TABLE[b]; + + return EXP_TABLE[i + j]; +} diff --git a/edac/RS129.h b/edac/RS129.h new file mode 100644 index 00000000..e6aef041 --- /dev/null +++ b/edac/RS129.h @@ -0,0 +1,56 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RS129_H__) +#define __RS129_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements Reed-Solomon (12,9) forward error correction. + // --------------------------------------------------------------------------- + + class HOST_SW_API RS129 { + public: + /// Check RS (12,9) FEC. + static bool check(const uint8_t* in); + + /// Encode RS (12,9) FEC. + static void encode(const uint8_t* msg, uint32_t nbytes, uint8_t* parity); + + private: + /// + static uint8_t gmult(uint8_t a, uint8_t b); + }; +} // namespace edac + +#endif // __RS129_H__ diff --git a/edac/RS634717.cpp b/edac/RS634717.cpp new file mode 100644 index 00000000..d7ca3013 --- /dev/null +++ b/edac/RS634717.cpp @@ -0,0 +1,576 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/RS634717.h" + +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t ENCODE_MATRIX[12U][24U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 062, 044, 003, 025, 014, 016, 027, 003, 053, 004, 036, 047 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 011, 012, 011, 011, 016, 064, 067, 055, 001, 076, 026, 073 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 003, 001, 005, 075, 014, 006, 020, 044, 066, 006, 070, 066 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 021, 070, 027, 045, 016, 067, 023, 064, 073, 033, 044, 021 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 030, 022, 003, 075, 015, 015, 033, 015, 051, 003, 053, 050 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 001, 041, 027, 056, 076, 064, 021, 053, 004, 025, 001, 012 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 061, 076, 021, 055, 076, 001, 063, 035, 030, 013, 064, 070 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 024, 022, 071, 056, 021, 035, 073, 042, 057, 074, 043, 076 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 072, 042, 005, 020, 043, 047, 033, 056, 001, 016, 013, 076 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 072, 014, 065, 054, 035, 025, 041, 016, 015, 040, 071, 026 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 073, 065, 036, 061, 042, 022, 017, 004, 044, 020, 025, 005 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 071, 005, 055, 003, 071, 034, 060, 011, 074, 002, 041, 050 } }; + +const uint8_t ENCODE_MATRIX_24169[16U][24U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 051, 045, 067, 015, 064, 067, 052, 012 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 057, 025, 063, 073, 071, 022, 040, 015 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 005, 001, 031, 004, 016, 054, 025, 076 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 073, 007, 047, 014, 041, 077, 047, 011 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 075, 015, 051, 051, 017, 067, 017, 057 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 020, 032, 014, 042, 075, 042, 070, 054 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 002, 075, 043, 005, 001, 040, 012, 064 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 024, 074, 015, 072, 024, 026, 074, 061 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 042, 064, 007, 022, 061, 020, 040, 065 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 032, 032, 055, 041, 057, 066, 021, 077 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 065, 036, 025, 007, 050, 016, 040, 051 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 064, 006, 054, 032, 076, 046, 014, 036 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 062, 063, 074, 070, 005, 027, 037, 046 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 055, 043, 034, 071, 057, 076, 050, 064 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 024, 023, 023, 005, 050, 070, 042, 023 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 067, 075, 045, 060, 057, 024, 006, 026 } }; + +const uint8_t ENCODE_MATRIX_362017[20U][36U] = { + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 037, 034, 006, 002, 007, 044, 064, 026, 014, 026, 044, 054, 013, 077, 005 }, + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 004, 017, 050, 024, 011, 005, 030, 057, 033, 003, 002, 002, 015, 016, 025, 026 }, + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 007, 023, 037, 046, 056, 075, 043, 045, 055, 021, 050, 031, 045, 027, 071, 062 }, + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 026, 005, 007, 063, 063, 027, 063, 040, 006, 004, 040, 045, 047, 030, 075, 007 }, + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 023, 073, 073, 041, 072, 034, 021, 051, 067, 016, 031, 074, 011, 021, 012, 021 }, + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 024, 051, 025, 023, 022, 041, 074, 066, 074, 065, 070, 036, 067, 045, 064, 001 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 052, 033, 014, 002, 020, 006, 014, 025, 052, 023, 035, 074, 075, 075, 043, 027 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 055, 062, 056, 025, 073, 060, 015, 030, 013, 017, 020, 002, 070, 055, 014, 047 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 051, 032, 065, 077, 012, 054, 013, 035, 032, 056, 012, 075, 001, 072, 063 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 074, 041, 030, 041, 043, 022, 051, 006, 064, 033, 003, 047, 027, 012, 055, 047 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 054, 070, 011, 003, 013, 022, 016, 057, 003, 045, 072, 031, 030, 056, 035, 022 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 051, 007, 072, 030, 065, 054, 006, 021, 036, 063, 050, 061, 064, 052, 001, 060 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 001, 065, 032, 070, 013, 044, 073, 024, 012, 052, 021, 055, 012, 035, 014, 072 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 011, 070, 005, 010, 065, 024, 015, 077, 022, 024, 024, 074, 007, 044, 007, 046 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 006, 002, 065, 011, 041, 020, 045, 042, 046, 054, 035, 012, 040, 064, 065, 033 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 034, 031, 001, 015, 044, 064, 016, 024, 052, 016, 006, 062, 020, 013, 055, 057 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 063, 043, 025, 044, 077, 063, 017, 017, 064, 014, 040, 074, 031, 072, 054, 006 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 071, 021, 070, 044, 056, 004, 030, 074, 004, 023, 071, 070, 063, 045, 056, 043 }, + { 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 uint32_t rsGFexp[64] = { + 1, 2, 4, 8, 16, 32, 3, 6, + 12, 24, 48, 35, 5, 10, 20, 40, + 19, 38, 15, 30, 60, 59, 53, 41, + 17, 34, 7, 14, 28, 56, 51, 37, + 9, 18, 36, 11, 22, 44, 27, 54, + 47, 29, 58, 55, 45, 25, 50, 39, + 13, 26, 52, 43, 21, 42, 23, 46, + 31, 62, 63, 61, 57, 49, 33, 0 +}; + +const uint32_t rsGFlog[64] = { + 63, 0, 1, 6, 2, 12, 7, 26, + 3, 32, 13, 35, 8, 48, 27, 18, + 4, 24, 33, 16, 14, 52, 36, 54, + 9, 45, 49, 38, 28, 41, 19, 56, + 5, 62, 25, 11, 34, 31, 17, 47, + 15, 23, 53, 51, 37, 44, 55, 40, + 10, 61, 46, 30, 50, 22, 39, 43, + 29, 60, 42, 21, 20, 59, 57, 58 +}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RS634717 class. +/// +RS634717::RS634717() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the RS634717 class. +/// +RS634717::~RS634717() +{ + /* stub */ +} + +/// +/// Decode RS (24,12,13) FEC. +/// +/// Reed-Solomon FEC encoded data to decode. +/// True, if data was decoded, otherwise false. +bool RS634717::decode241213(uint8_t* data) +{ + return decode(data, 24U, 39, 12); +} + +/// +/// Encode RS (24,12,13) FEC. +/// +/// Raw data to encode with Reed-Solomon FEC. +void RS634717::encode241213(uint8_t* data) +{ + assert(data != NULL); + + uint8_t codeword[24U]; + + for (uint32_t i = 0U; i < 24U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 12U; j++, offset += 6U) { + uint8_t hexbit = bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 24U; i++, offset += 6U) + hex2Bin(codeword[i], data, offset); +} + +/// +/// Decode RS (24,16,9) FEC. +/// +/// Reed-Solomon FEC encoded data to decode. +/// True, if data was decoded, otherwise false. +bool RS634717::decode24169(uint8_t* data) +{ + return decode(data, 24U, 39, 8); +} + +/// +/// Encode RS (24,16,9) FEC. +/// +/// Raw data to encode with Reed-Solomon FEC. +void RS634717::encode24169(uint8_t* data) +{ + assert(data != NULL); + + uint8_t codeword[24U]; + + for (uint32_t i = 0U; i < 24U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 16U; j++, offset += 6U) { + uint8_t hexbit = bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_24169[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 24U; i++, offset += 6U) + hex2Bin(codeword[i], data, offset); +} + +/// +/// Decode RS (36,20,17) FEC. +/// +/// Reed-Solomon FEC encoded data to decode. +/// True, if data was decoded, otherwise false. +bool RS634717::decode362017(uint8_t* data) +{ + return decode(data, 36U, 27, 16); +} + +/// +/// Encode RS (36,20,17) FEC. +/// +/// Raw data to encode with Reed-Solomon FEC. +void RS634717::encode362017(uint8_t* data) +{ + assert(data != NULL); + + uint8_t codeword[36U]; + + for (uint32_t i = 0U; i < 36U; i++) { + codeword[i] = 0x00U; + + uint32_t offset = 0U; + for (uint32_t j = 0U; j < 20U; j++, offset += 6U) { + uint8_t hexbit = bin2Hex(data, offset); + codeword[i] ^= gf6Mult(hexbit, ENCODE_MATRIX_362017[j][i]); + } + } + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 36U; i++, offset += 6U) + hex2Bin(codeword[i], data, offset); +} + +// --------------------------------------------------------------------------- +// Private Static Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +/// +uint8_t RS634717::bin2Hex(const uint8_t* input, uint32_t offset) +{ + uint8_t output = 0x00U; + + output |= READ_BIT(input, offset + 0U) ? 0x20U : 0x00U; + output |= READ_BIT(input, offset + 1U) ? 0x10U : 0x00U; + output |= READ_BIT(input, offset + 2U) ? 0x08U : 0x00U; + output |= READ_BIT(input, offset + 3U) ? 0x04U : 0x00U; + output |= READ_BIT(input, offset + 4U) ? 0x02U : 0x00U; + output |= READ_BIT(input, offset + 5U) ? 0x01U : 0x00U; + + return output; +} + +/// +/// +/// +/// +/// +/// +/// +void RS634717::hex2Bin(uint8_t input, uint8_t* output, uint32_t offset) +{ + WRITE_BIT(output, offset + 0U, input & 0x20U); + WRITE_BIT(output, offset + 1U, input & 0x10U); + WRITE_BIT(output, offset + 2U, input & 0x08U); + WRITE_BIT(output, offset + 3U, input & 0x04U); + WRITE_BIT(output, offset + 4U, input & 0x02U); + WRITE_BIT(output, offset + 5U, input & 0x01U); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// GF(2 ^ 6) multiply (for Reed-Solomon encoder). +/// +/// +/// +uint8_t RS634717::gf6Mult(uint8_t a, uint8_t b) const +{ + uint8_t p = 0x00U; + + for (uint32_t i = 0U; i < 6U; i++) { + if ((b & 0x01U) == 0x01U) + p ^= a; + + a <<= 1; + + if ((a & 0x40U) == 0x40U) + a ^= 0x43U; // primitive polynomial : x ^ 6 + x + 1 + + b >>= 1; + } + + return p; +} + +/// +/// Decode variable length Reed-Solomon FEC. +/// +/// +/// +/// +/// +/// +bool RS634717::decode(uint8_t* data, const uint32_t bitLength, const int firstData, const int roots) +{ + assert(data != NULL); + + uint8_t HB[63U]; + ::memset(HB, 0x00U, 63U); + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < bitLength; i++, offset += 6) + HB[i] = bin2Hex(data, offset); + + // RS (63,63-nroots,nroots+1) decoder where nroots = number of parity bits + // rsDec(8, 39) rsDec(16, 27) rsDec(12, 39) + + const int nroots = roots; + int lambda[18]; // Err+Eras Locator poly + int S[17]; // syndrome poly + int b[18]; + int t[18]; + int omega[18]; + int root[17]; + int reg[18]; + int locn[17]; + + int i, j, count, r, el, SynError, DiscrR, q, DegOmega, tmp, num1, num2, den, DegLambda; + + // form the syndromes; i.e., evaluate HB(x) at roots of g(x) + for (i = 0; i <= nroots - 1; i++) { + S[i] = HB[0]; + } + + for (j = 1; j <= 62; j++) { + for (i = 0; i <= nroots - 1; i++) { + if (S[i] == 0) { + S[i] = HB[j]; + } + else { + S[i] = HB[j] ^ rsGFexp[(rsGFlog[S[i]] + i + 1) % 63]; + } + } + } + + // convert syndromes to index form, checking for nonzero condition + SynError = 0; + + for (i = 0; i <= nroots - 1; i++) { + SynError = SynError | S[i]; + S[i] = rsGFlog[S[i]]; + } + + if (SynError == 0) { + // if syndrome is zero, rsData[] is a codeword and there are + // no errors to correct. So return rsData[] unmodified + count = 0; + return true; + } + + for (i = 1; i <= nroots; i++) { + lambda[i] = 0; + } + + lambda[0] = 1; + + for (i = 0; i <= nroots; i++) { + b[i] = rsGFlog[lambda[i]]; + } + + // begin Berlekamp-Massey algorithm to determine error+erasure + // locator polynomial + r = 0; + el = 0; + while (++r <= nroots) { + // r is the step number + //r = r + 1; + // compute discrepancy at the r-th step in poly-form + DiscrR = 0; + + for (i = 0; i <= r - 1; i++) { + if ((lambda[i] != 0) && (S[r - i - 1] != 63)) { + DiscrR = DiscrR ^ rsGFexp[(rsGFlog[lambda[i]] + S[r - i - 1]) % 63]; + } + } + + DiscrR = rsGFlog[DiscrR]; // index form + + if (DiscrR == 63) { + // shift elements upward one step + for (i = nroots; i >= 1; i += -1) { + b[i] = b[i - 1]; + } + + b[0] = 63; + } + else { + // t(x) <-- lambda(x) - DiscrR*x*b(x) + t[0] = lambda[0]; + + for (i = 0; i <= nroots - 1; i++) { + if (b[i] != 63) { + t[i + 1] = lambda[i + 1] ^ rsGFexp[(DiscrR + b[i]) % 63]; + } + else { + t[i + 1] = lambda[i + 1]; + } + } + + if (2 * el <= r - 1) { + el = r - el; + // b(x) <-- inv(DiscrR) * lambda(x) + + for (i = 0; i <= nroots; i++) { + if (lambda[i]) { + b[i] = (rsGFlog[lambda[i]] - DiscrR + 63) % 63; + } + else { + b[i] = 63; + } + } + } + else { + // shift elements upward one step + for (i = nroots; i >= 1; i += -1) { + b[i] = b[i - 1]; + } + + b[0] = 63; + } + + for (i = 0; i <= nroots; i++) { + lambda[i] = t[i]; + } + } + } /* end while() */ + + // convert lambda to index form and compute deg(lambda(x)) + DegLambda = 0; + for (i = 0; i <= nroots; i++) { + lambda[i] = rsGFlog[lambda[i]]; + + if (lambda[i] != 63) { + DegLambda = i; + } + } + + // find roots of the error+erasure locator polynomial by Chien search + for (i = 1; i <= nroots; i++) { + reg[i] = lambda[i]; + } + + count = 0; // number of roots of lambda(x) + + for (i = 1; i <= 63; i++) { + q = 1; // lambda[0] is always 0 + + for (j = DegLambda; j >= 1; j += -1) { + if (reg[j] != 63) { + reg[j] = (reg[j] + j) % 63; + q = q ^ rsGFexp[reg[j]]; + } + } + + // it is a root + if (q == 0) { + // store root (index-form) and error location number + root[count] = i; + locn[count] = i - 40; + + // if we have max possible roots, abort search to save time + count = count + 1; + + if (count == DegLambda) { + break; + } + } + } + + if (DegLambda != count) { + // deg(lambda) unequal to number of roots => uncorrectable error detected + return false; + } + + // compute err+eras evaluator poly omega(x) + // = s(x) * lambda(x) (modulo x**nroots). in index form. Also find deg(omega). + DegOmega = 0; + for (i = 0; i <= nroots - 1; i++) { + tmp = 0; + if (DegLambda < i) { + j = DegLambda; + } + else { + j = i; + } + + for (/* j = j */; j >= 0; j += -1) { + if ((S[i - j] != 63) && (lambda[j] != 63)) { + tmp = tmp ^ rsGFexp[(S[i - j] + lambda[j]) % 63]; + } + } + + if (tmp) { + DegOmega = i; + } + + omega[i] = rsGFlog[tmp]; + } + + omega[nroots] = 63; + + // compute error values in poly-form: + // num1 = omega(inv(X(l))) + // num2 = inv(X(l))**(FCR - 1) + // den = lambda_pr(inv(X(l))) + for (j = count - 1; j >= 0; j += -1) { + num1 = 0; + + for (i = DegOmega; i >= 0; i += -1) { + if (omega[i] != 63) { + num1 = num1 ^ rsGFexp[(omega[i] + i * root[j]) % 63]; + } + } + + num2 = rsGFexp[0]; + den = 0; + + // lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] + if (DegLambda < nroots) { + i = DegLambda; + } + else { + i = nroots; + } + + for (i = i & ~1; i >= 0; i += -2) { + if (lambda[i + 1] != 63) { + den = den ^ rsGFexp[(lambda[i + 1] + i * root[j]) % 63]; + } + } + + if (den == 0) { + return false; + } + + // apply error to data + if (num1 != 0) { + if (locn[j] < firstData) + return false; + HB[locn[j]] = HB[locn[j]] ^ (rsGFexp[(rsGFlog[num1] + rsGFlog[num2] + 63 - rsGFlog[den]) % 63]); + } + } + + offset = 0U; + for (uint32_t i = 0U; i < (uint32_t)nroots; i++, offset += 6) + hex2Bin(HB[i], data, offset); + + return true; +} diff --git a/edac/RS634717.h b/edac/RS634717.h new file mode 100644 index 00000000..e179ecb4 --- /dev/null +++ b/edac/RS634717.h @@ -0,0 +1,80 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RS634717_H__) +#define __RS634717_H__ + +#include "Defines.h" + +namespace edac +{ + // --------------------------------------------------------------------------- + // Class Declaration + // 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. + // --------------------------------------------------------------------------- + + class HOST_SW_API RS634717 { + public: + /// Initializes a new instance of the RS634717 class. + RS634717(); + /// Finalizes a instance of the RS634717 class. + ~RS634717(); + + /// Decode RS (24,12,13) FEC. + bool decode241213(uint8_t* data); + /// Encode RS (24,12,13) FEC. + void encode241213(uint8_t* data); + + /// Decode RS (24,16,9) FEC. + bool decode24169(uint8_t* data); + /// Encode RS (24,16,9) FEC. + void encode24169(uint8_t* data); + + /// Decode RS (36,20,17) FEC. + bool decode362017(uint8_t* data); + /// Encode RS (36,20,17) FEC. + void encode362017(uint8_t* data); + + private: + /// + static uint8_t bin2Hex(const uint8_t* input, uint32_t offset); + /// + static void hex2Bin(uint8_t input, uint8_t* output, uint32_t offset); + + /// + uint8_t gf6Mult(uint8_t a, uint8_t b) const; + /// Decode variable length Reed-Solomon FEC. + bool decode(uint8_t* data, const uint32_t bitLength, const int firstData, const int roots); + }; +} // namespace edac + +#endif // __RS634717_H__ diff --git a/edac/SHA256.cpp b/edac/SHA256.cpp new file mode 100644 index 00000000..5d83b486 --- /dev/null +++ b/edac/SHA256.cpp @@ -0,0 +1,438 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2005, 2006, 2008 Free Software Foundation, Inc. +* Copyright (C) 2011,2015 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/SHA256.h" + +using namespace edac; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#endif + +#define BLOCKSIZE 4096 +#if BLOCKSIZE % 64 != 0 +# error "invalid BLOCKSIZE" +#endif + +// Round functions. +#define F2(A,B,C) ( ( A & B ) | ( C & ( A | B ) ) ) +#define F1(E,F,G) ( G ^ ( E & ( F ^ G ) ) ) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/* + * This array contains the bytes used to pad the buffer to the next 64-byte + * boundary. + */ +static const uint8_t fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + +/* + * SHA256 round constants + */ +#define K(I) roundConstants[I] +static const uint32_t roundConstants[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +// --------------------------------------------------------------------------- +// Global Functions +// --------------------------------------------------------------------------- + +/* + * Copy the value from v into the memory location pointed to by *cp, + * If your architecture allows unaligned access this is equivalent to + * (uint32_t *) cp = v + */ +static inline void set_uint32(uint8_t* cp, uint32_t v) +{ + assert(cp != NULL); + ::memcpy(cp, &v, sizeof v); +} + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the SHA256 class. +/// +/// +/// Takes a pointer to a 256 bit block of data (eight 32 bit ints) and +/// intializes it to the start constants of the SHA256 algorithm. This +/// must be called before using hash in the call to sha256_hash +/// +SHA256::SHA256() : + m_state(NULL), + m_total(NULL), + m_buflen(0U), + m_buffer(NULL) +{ + m_state = new uint32_t[8U]; + m_total = new uint32_t[2U]; + m_buffer = new uint32_t[32U]; + + init(); +} + +/// +/// Finalizes a instance of the SHA256 class. +/// +SHA256::~SHA256() +{ + delete[] m_state; + delete[] m_total; + delete[] m_buffer; +} + +/// +/// Starting with the result of former calls of this function (or the initialization +/// function update the context for the next LEN bytes starting at BUFFER. It is +/// necessary that LEN is a multiple of 64!!! +/// +/// +/// Process LEN bytes of BUFFER, accumulating context into CTX. +/// It is assumed that LEN % 64 == 0. +/// Most of this code comes from GnuPG's cipher/sha1.c. +/// +/// +/// +void SHA256::processBlock(const uint8_t* buffer, uint32_t len) +{ + assert(buffer != NULL); + + const uint32_t *words = (uint32_t *)buffer; + uint32_t nwords = len / sizeof(uint32_t); + const uint32_t *endp = words + nwords; + uint32_t x[16]; + uint32_t a = m_state[0]; + uint32_t b = m_state[1]; + uint32_t c = m_state[2]; + uint32_t d = m_state[3]; + uint32_t e = m_state[4]; + uint32_t f = m_state[5]; + uint32_t g = m_state[6]; + uint32_t h = m_state[7]; + + // First increment the byte count. FIPS PUB 180-2 specifies the possible + // length of the file up to 2^64 bits. Here we only compute the + // number of bytes. Do a double word increment. + m_total[0] += len; + if (m_total[0] < len) + ++m_total[1]; + +#define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define S0(x) (rol(x, 25) ^ rol(x, 14) ^ (x >> 3)) +#define S1(x) (rol(x, 15) ^ rol(x, 13) ^ (x >> 10)) +#define SS0(x) (rol(x, 30) ^ rol(x, 19) ^ rol(x, 10)) +#define SS1(x) (rol(x, 26) ^ rol(x, 21) ^ rol(x, 7)) + +#define M(I) (tm = S1(x[(I - 2) & 0x0f]) + x[(I - 7) & 0x0f] + S0(x[(I - 15) & 0x0f]) + x[I & 0x0f], x[I & 0x0f] = tm) + +#define R(A,B,C,D,E,F,G,H,K,M) do { t0 = SS0(A) + F2(A,B,C); \ + t1 = H + SS1(E) + F1(E,F,G) + K + M; \ + D += t1; H = t0 + t1; \ + } while(0) + + while (words < endp) { + uint32_t tm; + uint32_t t0, t1; + // FIXME: see sha1.c for a better implementation. + for (uint32_t t = 0U; t < 16U; t++) { + x[t] = SWAP(*words); + words++; + } + + R(a, b, c, d, e, f, g, h, K(0), x[0]); + R(h, a, b, c, d, e, f, g, K(1), x[1]); + R(g, h, a, b, c, d, e, f, K(2), x[2]); + R(f, g, h, a, b, c, d, e, K(3), x[3]); + R(e, f, g, h, a, b, c, d, K(4), x[4]); + R(d, e, f, g, h, a, b, c, K(5), x[5]); + R(c, d, e, f, g, h, a, b, K(6), x[6]); + R(b, c, d, e, f, g, h, a, K(7), x[7]); + R(a, b, c, d, e, f, g, h, K(8), x[8]); + R(h, a, b, c, d, e, f, g, K(9), x[9]); + R(g, h, a, b, c, d, e, f, K(10), x[10]); + R(f, g, h, a, b, c, d, e, K(11), x[11]); + R(e, f, g, h, a, b, c, d, K(12), x[12]); + R(d, e, f, g, h, a, b, c, K(13), x[13]); + R(c, d, e, f, g, h, a, b, K(14), x[14]); + R(b, c, d, e, f, g, h, a, K(15), x[15]); + R(a, b, c, d, e, f, g, h, K(16), M(16)); + R(h, a, b, c, d, e, f, g, K(17), M(17)); + R(g, h, a, b, c, d, e, f, K(18), M(18)); + R(f, g, h, a, b, c, d, e, K(19), M(19)); + R(e, f, g, h, a, b, c, d, K(20), M(20)); + R(d, e, f, g, h, a, b, c, K(21), M(21)); + R(c, d, e, f, g, h, a, b, K(22), M(22)); + R(b, c, d, e, f, g, h, a, K(23), M(23)); + R(a, b, c, d, e, f, g, h, K(24), M(24)); + R(h, a, b, c, d, e, f, g, K(25), M(25)); + R(g, h, a, b, c, d, e, f, K(26), M(26)); + R(f, g, h, a, b, c, d, e, K(27), M(27)); + R(e, f, g, h, a, b, c, d, K(28), M(28)); + R(d, e, f, g, h, a, b, c, K(29), M(29)); + R(c, d, e, f, g, h, a, b, K(30), M(30)); + R(b, c, d, e, f, g, h, a, K(31), M(31)); + R(a, b, c, d, e, f, g, h, K(32), M(32)); + R(h, a, b, c, d, e, f, g, K(33), M(33)); + R(g, h, a, b, c, d, e, f, K(34), M(34)); + R(f, g, h, a, b, c, d, e, K(35), M(35)); + R(e, f, g, h, a, b, c, d, K(36), M(36)); + R(d, e, f, g, h, a, b, c, K(37), M(37)); + R(c, d, e, f, g, h, a, b, K(38), M(38)); + R(b, c, d, e, f, g, h, a, K(39), M(39)); + R(a, b, c, d, e, f, g, h, K(40), M(40)); + R(h, a, b, c, d, e, f, g, K(41), M(41)); + R(g, h, a, b, c, d, e, f, K(42), M(42)); + R(f, g, h, a, b, c, d, e, K(43), M(43)); + R(e, f, g, h, a, b, c, d, K(44), M(44)); + R(d, e, f, g, h, a, b, c, K(45), M(45)); + R(c, d, e, f, g, h, a, b, K(46), M(46)); + R(b, c, d, e, f, g, h, a, K(47), M(47)); + R(a, b, c, d, e, f, g, h, K(48), M(48)); + R(h, a, b, c, d, e, f, g, K(49), M(49)); + R(g, h, a, b, c, d, e, f, K(50), M(50)); + R(f, g, h, a, b, c, d, e, K(51), M(51)); + R(e, f, g, h, a, b, c, d, K(52), M(52)); + R(d, e, f, g, h, a, b, c, K(53), M(53)); + R(c, d, e, f, g, h, a, b, K(54), M(54)); + R(b, c, d, e, f, g, h, a, K(55), M(55)); + R(a, b, c, d, e, f, g, h, K(56), M(56)); + R(h, a, b, c, d, e, f, g, K(57), M(57)); + R(g, h, a, b, c, d, e, f, K(58), M(58)); + R(f, g, h, a, b, c, d, e, K(59), M(59)); + R(e, f, g, h, a, b, c, d, K(60), M(60)); + R(d, e, f, g, h, a, b, c, K(61), M(61)); + R(c, d, e, f, g, h, a, b, K(62), M(62)); + R(b, c, d, e, f, g, h, a, K(63), M(63)); + + a = m_state[0] += a; + b = m_state[1] += b; + c = m_state[2] += c; + d = m_state[3] += d; + e = m_state[4] += e; + f = m_state[5] += f; + g = m_state[6] += g; + h = m_state[7] += h; + } +} + +/// +/// Starting with the result of former calls of this function (or the initialization +/// function update the context for the next LEN bytes starting at BUFFER. It is NOT +/// required that LEN is a multiple of 64. +/// +/// +/// +void SHA256::processBytes(const uint8_t* buffer, uint32_t len) +{ + assert(buffer != NULL); + + // When we already have some bits in our internal buffer concatenate + // both inputs first. + if (m_buflen != 0U) { + uint32_t left_over = m_buflen; + uint32_t add = 128U - left_over > len ? len : 128U - left_over; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, add); + m_buflen += add; + + if (m_buflen > 64U) { + processBlock((uint8_t*)m_buffer, m_buflen & ~63U); + + m_buflen &= 63U; + + // The regions in the following copy operation cannot overlap. + ::memcpy(m_buffer, &((char*)m_buffer)[(left_over + add) & ~63U], m_buflen); + } + + buffer += add; + len -= add; + } + + // Process available complete blocks. + if (len >= 64U) { + processBlock(buffer, len & ~63U); + buffer += (len & ~63U); + len &= 63U; + } + + // Move remaining bytes in internal buffer. + if (len > 0U) { + uint32_t left_over = m_buflen; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, len); + left_over += len; + + if (left_over >= 64U) { + processBlock((uint8_t*)m_buffer, 64U); + left_over -= 64U; + ::memcpy(m_buffer, &m_buffer[16], left_over); + } + + m_buflen = left_over; + } +} + +/// +/// Process the remaining bytes in the bufferand put result from context +/// in first 32 bytes following buffer. The result is always in little +/// endian byte order, so that a byte - wise output yields to the wanted +/// ASCII representation of the message digest. +/// +/// +/// +uint8_t* SHA256::finish(uint8_t* buffer) +{ + assert(buffer != NULL); + + conclude(); + + return read(buffer); +} + +/// +/// Put result from context in first 32 bytes following buffer. The result is +/// always in little endian byte order, so that a byte - wise output yields +/// to the wanted ASCII representation of the message digest. +/// +/// +/// +uint8_t* SHA256::read(uint8_t* buffer) +{ + assert(buffer != NULL); + + for (uint32_t i = 0U; i < 8U; i++) + set_uint32(buffer + i * sizeof(m_state[0]), SWAP(m_state[i])); + + return buffer; +} + +/// +/// Compute SHA256 message digest for the length bytes beginning at buffer. The +/// result is always in little endian byte order, so that a byte-wise +/// output yields to the wanted ASCII representation of the message +/// digest. +/// +/// +/// +/// +/// +uint8_t* SHA256::buffer(const uint8_t* buffer, uint32_t len, uint8_t* resblock) +{ + assert(buffer != NULL); + assert(resblock != NULL); + + // Initialize the computation context. + init(); + + // Process whole buffer but last len % 64 bytes. + processBytes(buffer, len); + + // Put result in desired memory area. + return finish(resblock); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +void SHA256::init() +{ + m_state[0] = 0x6a09e667UL; + m_state[1] = 0xbb67ae85UL; + m_state[2] = 0x3c6ef372UL; + m_state[3] = 0xa54ff53aUL; + m_state[4] = 0x510e527fUL; + m_state[5] = 0x9b05688cUL; + m_state[6] = 0x1f83d9abUL; + m_state[7] = 0x5be0cd19UL; + + m_total[0] = m_total[1] = 0; + m_buflen = 0; +} + +/// +/// Process the remaining bytes in the internal buffer and the usual +/// prolog according to the standard and write the result to the buffer. +/// +void SHA256::conclude() +{ + // Take yet unprocessed bytes into account. + uint32_t bytes = m_buflen; + uint32_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + // Now count remaining bytes. + m_total[0] += bytes; + if (m_total[0] < bytes) + ++m_total[1]; + + // Put the 64-bit file length in *bits* at the end of the buffer. + // Use set_uint32 rather than a simple assignment, to avoid risk of + // unaligned access. + set_uint32((uint8_t*)& m_buffer[size - 2], SWAP((m_total[1] << 3) | (m_total[0] >> 29))); + set_uint32((uint8_t*)& m_buffer[size - 1], SWAP(m_total[0] << 3)); + + ::memcpy(&((char*)m_buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + // Process last bytes. + processBlock((uint8_t*)m_buffer, size * 4); +} diff --git a/edac/SHA256.h b/edac/SHA256.h new file mode 100644 index 00000000..05251f97 --- /dev/null +++ b/edac/SHA256.h @@ -0,0 +1,99 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2005, 2006, 2008, 2009 Free Software Foundation, Inc. +* Copyright (C) 2011,2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SHA256_H__) +#define __SHA256_H__ + +#include "Defines.h" + +#include + +namespace edac +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum { + SHA256_DIGEST_SIZE = 256 / 8 + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements SHA-256 hashing. + // --------------------------------------------------------------------------- + + class HOST_SW_API SHA256 { + public: + /// Initializes a new instance of the SHA256 class. + SHA256(); + /// Finalizes a instance of the SHA256 class. + ~SHA256(); + + /// Starting with the result of former calls of this function (or the initialization + /// function update the context for the next LEN bytes starting at BUFFER. It is necessary + /// that LEN is a multiple of 64!!! + void processBlock(const uint8_t* buffer, uint32_t len); + + /// Starting with the result of former calls of this function (or the initialization + /// function update the context for the next LEN bytes starting at BUFFER. It is NOT required + /// that LEN is a multiple of 64. + void processBytes(const uint8_t* buffer, uint32_t len); + + /// Process the remaining bytes in the bufferand put result from context in first 32 + /// bytes following buffer. The result is always in little endian byte order, so that a byte-wise + /// output yields to the wanted ASCII representation of the message digest. + uint8_t* finish(uint8_t* buffer); + + /// Put result from context in first 32 bytes following buffer. The result is always + /// in little endian byte order, so that a byte - wise output yields to the wanted ASCII + /// representation of the message digest. + uint8_t* read(uint8_t* buffer); + + /// Compute SHA256 message digest for the length bytes beginning at buffer. The result + /// is always in little endian byte order, so that a byte-wise output yields to the wanted ASCII + /// representation of the message digest. + uint8_t* buffer(const uint8_t* buffer, uint32_t len, uint8_t* resblock); + + private: + uint32_t* m_state; + uint32_t* m_total; + uint32_t m_buflen; + uint32_t* m_buffer; + + /// + void init(); + /// Process the remaining bytes in the internal buffer and the usual prolog according to + /// the standard and write the result to the buffer. + void conclude(); + }; +} // namespace edac + +#endif // __SHA256_H__ diff --git a/host/Host.cpp b/host/Host.cpp new file mode 100644 index 00000000..53023764 --- /dev/null +++ b/host/Host.cpp @@ -0,0 +1,1465 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/Control.h" +#include "p25/Control.h" +#include "modem/SerialController.h" +#include "lookups/RSSIInterpolator.h" +#include "host/Host.h" +#include "HostMain.h" +#include "Log.h" +#include "StopWatch.h" +#include "Thread.h" +#include "Utils.h" + +using namespace network; +using namespace modem; +using namespace lookups; + +#include +#include +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Host class. +/// +/// Full-path to the configuration file. +Host::Host(const std::string& confFile) : + m_confFile(confFile), + m_conf(), + m_modem(NULL), + m_network(NULL), + m_mode(STATE_IDLE), + m_modeTimer(1000U), + m_dmrTXTimer(1000U), + m_cwIdTimer(1000U), + m_dmrEnabled(false), + m_p25Enabled(false), + m_p25CtrlBcstContinuous(false), + m_duplex(false), + m_fixedMode(false), + m_timeout(180U), + m_rfModeHang(10U), + m_netModeHang(3U), + m_netTalkgroupHang(10U), + m_identity(), + m_cwCallsign(), + m_cwIdTime(0U), + m_latitude(0.0F), + m_longitude(0.0F), + m_height(0), + m_power(0U), + m_location(), + m_rxFrequency(0U), + m_txFrequency(0U), + m_channelId(0U), + m_idenTable(NULL), + m_ridLookup(NULL), + m_tidLookup(NULL), + m_remoteControl(NULL) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Host class. +/// +Host::~Host() +{ + /* stub */ +} + +/// +/// Executes the main modem host processing loop. +/// +/// Zero if successful, otherwise error occurred. +int Host::run() +{ + bool ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + + bool m_daemon = m_conf["daemon"].as(false); + if (m_daemon && g_foreground) + m_daemon = false; + + // initialize system logging + yaml::Node logConf = m_conf["log"]; + ret = ::LogInitialise(logConf["filePath"].as(), logConf["fileRoot"].as(), + logConf["fileLevel"].as(0U), logConf["displayLevel"].as(0U)); + if (!ret) { + ::fatal("unable to open the log file\n"); + } + + ret = ::ActivityLogInitialise(logConf["activityFilePath"].as(), logConf["fileRoot"].as()); + if (!ret) { + ::fatal("unable to open the activity log file\n"); + } + +#if !defined(_WIN32) && !defined(_WIN64) + // handle POSIX process forking + if (m_daemon) { + // create new process + pid_t pid = ::fork(); + if (pid == -1) { + ::fprintf(stderr, "%s: Couldn't fork() , exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + else if (pid != 0) { + ::LogFinalise(); + ::ActivityLogFinalise(); + exit(EXIT_SUCCESS); + } + + // create new session and process group + if (::setsid() == -1) { + ::fprintf(stderr, "%s: Couldn't setsid(), exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + + // set the working directory to the root directory + if (::chdir("/") == -1) { + ::fprintf(stderr, "%s: Couldn't cd /, exiting\n", g_progExe.c_str()); + ::LogFinalise(); + ::ActivityLogFinalise(); + return EXIT_FAILURE; + } + + ::close(STDIN_FILENO); + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + } +#endif + + getHostVersion(); + ::LogInfo(">> Modem Controller"); + + // read base parameters from configuration + ret = readParams(); + if (!ret) + return EXIT_FAILURE; + + // initialize modem + ret = createModem(); + if (!ret) + return EXIT_FAILURE; + + yaml::Node systemConf = m_conf["system"]; + + // try to load radio IDs table + std::string ridLookupFile = systemConf["radio_id"]["file"].as(); + uint32_t ridReloadTime = systemConf["radio_id"]["time"].as(0U); + bool ridAcl = systemConf["radio_id"]["acl"].as(false); + + LogInfo("Radio Id Lookups"); + LogInfo(" File: %s", ridLookupFile.length() > 0U ? ridLookupFile.c_str() : "None"); + if (ridReloadTime > 0U) + LogInfo(" Reload: %u mins", ridReloadTime); + LogInfo(" ACL: %s", ridAcl ? "yes" : "no"); + + m_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, ridAcl); + m_ridLookup->read(); + + // try to load talkgroup IDs table + std::string tidLookupFile = systemConf["talkgroup_id"]["file"].as(); + uint32_t tidReloadTime = systemConf["talkgroup_id"]["time"].as(0U); + bool tidAcl = systemConf["talkgroup_id"]["acl"].as(false); + + LogInfo("Talkgroup Id Lookups"); + LogInfo(" File: %s", tidLookupFile.length() > 0U ? tidLookupFile.c_str() : "None"); + if (tidReloadTime > 0U) + LogInfo(" Reload: %u mins", tidReloadTime); + LogInfo(" ACL: %s", tidAcl ? "yes" : "no"); + + m_tidLookup = new TalkgroupIdLookup(tidLookupFile, tidReloadTime, tidAcl); + m_tidLookup->read(); + + // initialize networking + if (m_conf["network"]["enable"].as(false)) { + ret = createNetwork(); + if (!ret) + return EXIT_FAILURE; + } + + // set CW parameters + if (systemConf["cwId"]["enable"].as(false)) { + uint32_t time = systemConf["cwId"]["time"].as(10U); + m_cwCallsign = systemConf["cwId"]["callsign"].as(); + + LogInfo("CW Id Parameters"); + LogInfo(" Time: %u mins", time); + LogInfo(" Callsign: %s", m_cwCallsign.c_str()); + + m_cwIdTime = time * 60U; + + m_cwIdTimer.setTimeout(m_cwIdTime / 2U); + m_cwIdTimer.start(); + } + + // for all modes we handle RSSI + std::string rssiMappingFile = systemConf["modem"]["rssiMappingFile"].as(); + + RSSIInterpolator* rssi = new RSSIInterpolator; + if (!rssiMappingFile.empty()) { + LogInfo("RSSI"); + LogInfo(" Mapping File: %s", rssiMappingFile.c_str()); + rssi->load(rssiMappingFile); + } + + yaml::Node protocolConf = m_conf["protocols"]; + + StopWatch stopWatch; + stopWatch.start(); + + // initialize DMR + Timer dmrBeaconIntervalTimer(1000U); + Timer dmrBeaconDurationTimer(1000U); + + dmr::Control* dmr = NULL; + LogInfo("DMR Parameters"); + LogInfo(" Enabled: %s", m_dmrEnabled ? "yes" : "no"); + if (m_dmrEnabled) { + yaml::Node dmrProtocol = protocolConf["dmr"]; + m_dmrBeacons = dmrProtocol["beacons"]["enable"].as(false); + bool embeddedLCOnly = dmrProtocol["embeddedLCOnly"].as(false); + bool dmrDumpDataPacket = dmrProtocol["dumpDataPacket"].as(false); + bool dmrRepeatDataPacket = dmrProtocol["repeatDataPacket"].as(true); + bool dumpTAData = dmrProtocol["dumpTAData"].as(true); + uint32_t callHang = dmrProtocol["callHang"].as(3U); + uint32_t txHang = dmrProtocol["txHang"].as(4U); + uint32_t dmrQueueSize = dmrProtocol["queueSize"].as(5120U); + bool dmrVerbose = dmrProtocol["verbose"].as(true); + bool dmrDebug = dmrProtocol["debug"].as(false); + + uint32_t jitter = m_conf["network"]["jitter"].as(360U); + + if (txHang > m_rfModeHang) + txHang = m_rfModeHang; + if (txHang > m_netModeHang) + txHang = m_netModeHang; + + if (callHang > txHang) + callHang = txHang; + + LogInfo(" Embedded LC Only: %s", embeddedLCOnly ? "yes" : "no"); + LogInfo(" Dump Talker Alias Data: %s", dumpTAData ? "yes" : "no"); + LogInfo(" Dump Packet Data: %s", dmrDumpDataPacket ? "yes" : "no"); + LogInfo(" Repeat Packet Data: %s", dmrRepeatDataPacket ? "yes" : "no"); + LogInfo(" Call Hang: %us", callHang); + LogInfo(" TX Hang: %us", txHang); + LogInfo(" Queue Size: %u", dmrQueueSize); + + LogInfo(" Roaming Beacons: %s", m_dmrBeacons ? "yes" : "no"); + if (m_dmrBeacons) { + uint32_t dmrBeaconInterval = dmrProtocol["beacons"]["interval"].as(60U); + uint32_t dmrBeaconDuration = dmrProtocol["beacons"]["duration"].as(3U); + + LogInfo(" Roaming Beacon Interval: %us", dmrBeaconInterval); + LogInfo(" Roaming Beacon Duration: %us", dmrBeaconDuration); + + dmrBeaconDurationTimer.setTimeout(dmrBeaconDuration); + + dmrBeaconIntervalTimer.setTimeout(dmrBeaconInterval); + dmrBeaconIntervalTimer.start(); + + g_fireDMRBeacon = true; + } + + dmr = new dmr::Control(m_dmrColorCode, callHang, dmrQueueSize, embeddedLCOnly, dumpTAData, m_timeout, m_netTalkgroupHang, + m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDebug, dmrVerbose); + m_dmrTXTimer.setTimeout(txHang); + + if (dmrVerbose) { + LogInfo(" Verbose: yes"); + } + if (dmrDebug) { + LogInfo(" Debug: yes"); + } + } + + // initialize P25 + Timer p25CCIntervalTimer(1000U); + Timer p25CCDurationTimer(1000U); + + p25::Control* p25 = NULL; + LogInfo("P25 Parameters"); + LogInfo(" Enabled: %s", m_p25Enabled ? "yes" : "no"); + if (m_p25Enabled) { + yaml::Node p25Protocol = protocolConf["p25"]; + uint32_t preambleCount = p25Protocol["preambleCount"].as(4U); + m_controlData = p25Protocol["control"]["enable"].as(false); + bool controlBcstContinuous = p25Protocol["control"]["continuous"].as(false); + bool p25DumpDataPacket = p25Protocol["dumpDataPacket"].as(false); + bool p25RepeatDataPacket = p25Protocol["repeatDataPacket"].as(true); + uint32_t callHang = p25Protocol["callHang"].as(3U); + uint32_t p25QueueSize = p25Protocol["queueSize"].as(8192U); + bool p25Verbose = p25Protocol["verbose"].as(true); + bool p25Debug = p25Protocol["debug"].as(false); + + LogInfo(" Preamble Count: %u", preambleCount); + LogInfo(" Dump Packet Data: %s", p25DumpDataPacket ? "yes" : "no"); + LogInfo(" Repeat Packet Data: %s", p25RepeatDataPacket ? "yes" : "no"); + LogInfo(" Call Hang: %us", callHang); + LogInfo(" Queue Size: %u", p25QueueSize); + + LogInfo(" Control: %s", m_controlData ? "yes" : "no"); + + uint32_t p25ControlBcstInterval = p25Protocol["control"]["interval"].as(60U); + uint32_t p25ControlBcstDuration = p25Protocol["control"]["duration"].as(1U); + if (m_controlData) { + LogInfo(" Control Broadcast Continuous: %s", controlBcstContinuous ? "yes" : "no"); + if (controlBcstContinuous) { + p25ControlBcstInterval = 30U; + p25ControlBcstDuration = 120U; + m_p25CtrlBcstContinuous = controlBcstContinuous; + } + else { + LogInfo(" Control Broadcast Interval: %us", p25ControlBcstInterval); + LogInfo(" Control Broadcast Duration: %us", p25ControlBcstDuration); + } + + p25CCIntervalTimer.setTimeout(p25ControlBcstInterval); + p25CCIntervalTimer.start(); + + p25CCDurationTimer.setTimeout(p25ControlBcstDuration); + + g_fireP25Control = true; + g_interruptP25Control = false; + } + + p25 = new p25::Control(m_p25NAC, callHang, p25QueueSize, m_modem, m_network, m_timeout, m_netTalkgroupHang, + p25ControlBcstInterval, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket, p25Debug, p25Verbose); + p25->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_p25PatchSuperGroup, m_p25NetId, m_p25SysId, m_p25RfssId, + m_p25SiteId, m_channelId, m_channelNo, true); + + if (p25Verbose) { + LogInfo(" Verbose: yes"); + } + if (p25Debug) { + LogInfo(" Debug: yes"); + } + } + + if (!m_dmrEnabled && !m_p25Enabled) { + ::LogError(LOG_HOST, "No modes enabled? DMR and/or P25 must be enabled!"); + g_killed = true; + } + + if (m_dmrEnabled && m_p25CtrlBcstContinuous) { + ::LogError(LOG_HOST, "Cannot have DMR enabled when using dedicated P25 control!"); + g_killed = true; + } + + if (m_fixedMode && m_dmrEnabled && m_p25Enabled) { + ::LogError(LOG_HOST, "Cannot have DMR enabled and P25 enabled when using fixed mode! Choose one protocol for fixed mode operation."); + g_killed = true; + } + + if (m_dmrBeacons && m_controlData) { + ::LogError(LOG_HOST, "Cannot have DMR roaming becaons and P25 control at the same time."); + g_killed = true; + } + + if (!m_duplex && m_controlData) { + ::LogError(LOG_HOST, "Cannot have P25 control and simplex mode at the same time."); + g_killed = true; + } + + if (!g_killed) { + setMode(STATE_IDLE); + ::LogInfoEx(LOG_HOST, "Host is performing late initialization and warmup"); + + // perform early pumping of the modem clock (this is so the DSP has time to setup its buffers), + // and clock the network (so it may perform early connect) + uint32_t elapsedMs = 0U; + while (!g_killed) { + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + elapsedMs += ms; + m_modem->clock(ms); + + if (m_network != NULL) + m_network->clock(ms); + + if (ms < 2U) + Thread::sleep(1U); + + if (elapsedMs > 15000U) + break; + } + + ::LogInfoEx(LOG_HOST, "Host is up and running"); + stopWatch.start(); + } + + bool killed = false; + bool hasTxShutdown = false; + + #define INTERRUPT_P25_CONTROL \ + if (g_interruptP25Control) { \ + p25CCDurationTimer.stop(); \ + if (p25CCDurationTimer.isRunning() && !p25CCDurationTimer.hasExpired()) { \ + LogDebug(LOG_HOST, "traffic interrupts P25 CC, g_interruptP25Control = %u", g_interruptP25Control); \ + m_modem->clearP25Data(); \ + p25->reset(); \ + } \ + } + + // main execution loop + while (!killed) { + if (m_modem->hasLockout() && m_mode != HOST_STATE_LOCKOUT) + setMode(HOST_STATE_LOCKOUT); + else if (!m_modem->hasLockout() && m_mode == HOST_STATE_LOCKOUT) + setMode(STATE_IDLE); + + if (m_modem->hasError() && m_mode != HOST_STATE_ERROR) + setMode(HOST_STATE_ERROR); + else if (!m_modem->hasError() && m_mode == HOST_STATE_ERROR) + setMode(STATE_IDLE); + + uint32_t ms = stopWatch.elapsed(); + if (ms > 1U) + m_modem->clock(ms); + + uint8_t data[220U]; + uint32_t len; + bool ret; + bool hasCw = false; + + // ------------------------------------------------------ + // -- Read from Modem Processing -- + // ------------------------------------------------------ + + /** DMR */ + if (dmr != NULL) { + // read DMR slot 1 frames from the modem, and if there is any + // write those frames to the DMR controller + len = m_modem->readDMRData1(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + // if the modem is in duplex -- process wakeup CSBKs + if (m_duplex) { + bool ret = dmr->processWakeup(data); + if (ret) { + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_DMR); + + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + } + } + else { + // in simplex directly process slot 1 frames + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_DMR); + dmr->processFrame1(data, len); + + dmrBeaconDurationTimer.stop(); + p25CCDurationTimer.stop(); + } + } + else if (m_mode == STATE_DMR) { + // if the modem is in duplex, and hasn't started transmitting + // process wakeup CSBKs + if (m_duplex && !m_modem->hasTX()) { + bool ret = dmr->processWakeup(data); + if (ret) { + m_modem->writeDMRStart(true); + m_dmrTXTimer.start(); + } + } + else { + // process slot 1 frames + bool ret = dmr->processFrame1(data, len); + if (ret) { + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + + m_modeTimer.start(); + if (m_duplex) + m_dmrTXTimer.start(); + } + } + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "DMR modem data received, mode = %u", m_mode); + } + } + + // read DMR slot 2 frames from the modem, and if there is any + // write those frames to the DMR controller + len = m_modem->readDMRData2(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + // if the modem is in duplex -- process wakeup CSBKs + if (m_duplex) { + bool ret = dmr->processWakeup(data); + if (ret) { + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_DMR); + + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + } + } + else { + // in simplex -- directly process slot 2 frames + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_DMR); + dmr->processFrame2(data, len); + + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + } + } + else if (m_mode == STATE_DMR) { + // if the modem is in duplex, and hasn't started transmitting + // process wakeup CSBKs + if (m_duplex && !m_modem->hasTX()) { + bool ret = dmr->processWakeup(data); + if (ret) { + m_modem->writeDMRStart(true); + m_dmrTXTimer.start(); + } + } + else { + // process slot 2 frames + bool ret = dmr->processFrame2(data, len); + if (ret) { + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + + m_modeTimer.start(); + if (m_duplex) + m_dmrTXTimer.start(); + } + } + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "DMR modem data received, mode = %u", m_mode); + } + } + } + + /** P25 */ + // read P25 frames from modem, and if there are frames + // write those frames to the P25 controller + if (p25 != NULL) { + len = m_modem->readP25Data(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + bool ret = p25->processFrame(data, len); + if (ret) { + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_P25); + + dmrBeaconDurationTimer.stop(); + INTERRUPT_P25_CONTROL; + } + else { + ret = p25->writeEndRF(); + if (ret) { + dmrBeaconDurationTimer.stop(); + + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_P25); + } + + if (m_mode == STATE_P25) { + m_modeTimer.start(); + } + + // if the modem is in duplex -- handle P25 CC burst control + if (m_duplex) { + if (p25CCDurationTimer.isPaused() && !g_interruptP25Control) { + LogDebug(LOG_HOST, "traffic complete, resume P25 CC, g_interruptP25Control = %u", g_interruptP25Control); + p25CCDurationTimer.resume(); + } + + if (g_interruptP25Control) { + g_fireP25Control = true; + } + + if (g_fireP25Control) { + m_modeTimer.stop(); + } + } + else { + p25CCDurationTimer.stop(); + g_interruptP25Control = false; + } + } + } + } + else if (m_mode == STATE_P25) { + bool ret = p25->processFrame(data, len); + if (ret) { + m_modeTimer.start(); + INTERRUPT_P25_CONTROL; + } + else { + ret = p25->writeEndRF(); + if (ret) { + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_rfModeHang); + setMode(STATE_P25); + } + + if (m_mode == STATE_P25) { + m_modeTimer.start(); + } + } + } + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "P25 modem data received, mode = %u", m_mode); + } + } + } + + // ------------------------------------------------------ + // -- Write to Modem Processing -- + // ------------------------------------------------------ + + if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) { + if (!m_fixedMode) { + setMode(STATE_IDLE); + } else { + if (dmr != NULL) + setMode(STATE_DMR); + if (p25 != NULL) + setMode(STATE_P25); + } + } + + /** DMR */ + if (dmr != NULL) { + // check if there is space on the modem for DMR slot 1 frames, + // if there is read frames from the DMR controller and write it + // to the modem + ret = m_modem->hasDMRSpace1(); + if (ret) { + len = dmr->getFrame1(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_netModeHang); + setMode(STATE_DMR); + } + if (m_mode == STATE_DMR) { + // if the modem is in duplex -- write DMR sync start + if (m_duplex) { + m_modem->writeDMRStart(true); + m_dmrTXTimer.start(); + } + + m_modem->writeDMRData1(data, len); + + dmrBeaconDurationTimer.stop(); + if (g_interruptP25Control && p25CCDurationTimer.isRunning()) { + p25CCDurationTimer.pause(); + } + m_modeTimer.start(); + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "DMR data received, mode = %u", m_mode); + } + } + } + + // check if there is space on the modem for DMR slot 2 frames, + // if there is read frames from the DMR controller and write it + // to the modem + ret = m_modem->hasDMRSpace2(); + if (ret) { + len = dmr->getFrame2(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_netModeHang); + setMode(STATE_DMR); + } + if (m_mode == STATE_DMR) { + // if the modem is in duplex -- write DMR sync start + if (m_duplex) { + m_modem->writeDMRStart(true); + m_dmrTXTimer.start(); + } + + m_modem->writeDMRData2(data, len); + + dmrBeaconDurationTimer.stop(); + if (g_interruptP25Control && p25CCDurationTimer.isRunning()) { + p25CCDurationTimer.pause(); + } + m_modeTimer.start(); + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "DMR data received, mode = %u", m_mode); + } + } + } + } + + /** P25 */ + // check if there is space on the modem for P25 frames, + // if there is read frames from the P25 controller and write it + // to the modem + if (p25 != NULL) { + ret = m_modem->hasP25Space(); + if (ret) { + len = p25->getFrame(data); + if (len > 0U) { + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_netModeHang); + setMode(STATE_P25); + } + + if (m_mode == STATE_P25) { + m_modem->writeP25Data(data, len); + + dmrBeaconDurationTimer.stop(); + if (g_interruptP25Control && p25CCDurationTimer.isRunning()) { + p25CCDurationTimer.pause(); + } + + m_modeTimer.start(); + } + else if (m_mode != HOST_STATE_LOCKOUT) { + LogWarning(LOG_HOST, "P25 data received, mode = %u", m_mode); + } + } + else { + if (m_mode == STATE_IDLE || m_mode == STATE_P25) { + // P25 control data, if control data is being transmitted + if (p25CCDurationTimer.isRunning() && !p25CCDurationTimer.hasExpired()) { + p25->setCCRunning(true); + p25->writeControlRF(); + } + + // P25 status data, tail on idle + ret = p25->writeEndRF(); + if (ret) { + if (m_mode == STATE_IDLE) { + m_modeTimer.setTimeout(m_netModeHang); + setMode(STATE_P25); + } + + if (m_mode == STATE_P25) { + m_modeTimer.start(); + } + } + } + } + + + // if the modem is in duplex -- handle P25 CC burst control + if (m_duplex) { + if (p25CCDurationTimer.isPaused() && !g_interruptP25Control) { + LogDebug(LOG_HOST, "traffic complete, resume P25 CC, g_interruptP25Control = %u", g_interruptP25Control); + p25CCDurationTimer.resume(); + } + + if (g_interruptP25Control) { + g_fireP25Control = true; + } + + if (g_fireP25Control) { + m_modeTimer.stop(); + } + } + } + } + + // ------------------------------------------------------ + // -- Remote Control Processing -- + // ------------------------------------------------------ + + if (m_remoteControl != NULL) { + m_remoteControl->process(this, dmr, p25); + } + + // ------------------------------------------------------ + // -- Modem, DMR, P25 and Network Clocking -- + // ------------------------------------------------------ + + ms = stopWatch.elapsed(); + stopWatch.start(); + + m_modem->clock(ms); + + if (dmr != NULL) + dmr->clock(); + if (p25 != NULL) + p25->clock(ms); + + if (m_network != NULL) + m_network->clock(ms); + + // ------------------------------------------------------ + // -- Timer Clocking -- + // ------------------------------------------------------ + + // clock and check CW timer + m_cwIdTimer.clock(ms); + if (m_cwIdTimer.isRunning() && m_cwIdTimer.hasExpired()) { + if (dmrBeaconDurationTimer.isRunning() || p25CCDurationTimer.isRunning()) { + LogDebug(LOG_HOST, "CW, beacon or CC timer running, ceasing"); + + setMode(STATE_IDLE); + + dmrBeaconDurationTimer.stop(); + p25CCDurationTimer.stop(); + //g_interruptP25Control = true; + } + + if (m_mode == STATE_IDLE && !m_modem->hasTX()) { + hasCw = true; + m_modem->sendCWId(m_cwCallsign); + + m_cwIdTimer.setTimeout(m_cwIdTime); + m_cwIdTimer.start(); + } + } + + /** DMR */ + if (dmr != NULL) { + // clock and check DMR roaming beacon interval timer + dmrBeaconIntervalTimer.clock(ms); + if ((dmrBeaconIntervalTimer.isRunning() && dmrBeaconIntervalTimer.hasExpired()) || g_fireDMRBeacon) { + if (hasCw) { + g_fireDMRBeacon = false; + dmrBeaconIntervalTimer.start(); + } + else { + if ((m_mode == STATE_IDLE || m_mode == STATE_DMR) && !m_modem->hasTX()) { + if (m_modeTimer.isRunning()) { + m_modeTimer.stop(); + } + + if (m_mode != STATE_DMR) + setMode(STATE_DMR); + + g_fireDMRBeacon = false; + LogDebug(LOG_HOST, "DMR, roaming beacon burst"); + dmrBeaconIntervalTimer.start(); + dmrBeaconDurationTimer.start(); + } + } + } + + // clock and check DMR roaming beacon duration timer + dmrBeaconDurationTimer.clock(ms); + if (dmrBeaconDurationTimer.isRunning() && dmrBeaconDurationTimer.hasExpired()) { + dmrBeaconDurationTimer.stop(); + + if (m_mode == STATE_DMR && !m_modeTimer.isRunning()) { + m_modeTimer.setTimeout(m_rfModeHang); + m_modeTimer.start(); + } + } + + // clock and check DMR Tx timer + m_dmrTXTimer.clock(ms); + if (m_dmrTXTimer.isRunning() && m_dmrTXTimer.hasExpired()) { + m_modem->writeDMRStart(false); + m_dmrTXTimer.stop(); + } + } + + /** P25 */ + if (p25 != NULL) { + // clock and check P25 CC broadcast interval timer + p25CCIntervalTimer.clock(ms); + if ((p25CCIntervalTimer.isRunning() && p25CCIntervalTimer.hasExpired()) || g_fireP25Control) { + if (hasCw) { + g_fireP25Control = false; + p25CCIntervalTimer.start(); + } + else { + if ((m_mode == STATE_IDLE || m_mode == STATE_P25) && !m_modem->hasTX()) { + if (m_modeTimer.isRunning()) { + m_modeTimer.stop(); + } + + if (m_mode != STATE_P25) + setMode(STATE_P25); + + if (g_interruptP25Control) { + g_interruptP25Control = false; + LogDebug(LOG_HOST, "traffic complete, restart P25 CC broadcast, g_interruptP25Control = %u", g_interruptP25Control); + } + + p25->writeAdjSSNetwork(); + p25->setCCRunning(true); + + // hide this message for continuous CC -- otherwise display every time we process + if (!m_p25CtrlBcstContinuous) { + LogMessage(LOG_HOST, "P25, start CC broadcast"); + } + + g_fireP25Control = false; + p25CCIntervalTimer.start(); + p25CCDurationTimer.start(); + + // if the CC is continuous -- clock one cycle into the duration timer + if (m_p25CtrlBcstContinuous) { + p25CCDurationTimer.clock(ms); + } + } + } + } + + // if the CC is continuous -- we don't clock the CC duration timer (which results in the CC + // broadcast running infinitely until stopped) + if (!m_p25CtrlBcstContinuous) { + // clock and check P25 CC broadcast duration timer + p25CCDurationTimer.clock(ms); + if (p25CCDurationTimer.isRunning() && p25CCDurationTimer.hasExpired()) { + p25CCDurationTimer.stop(); + + p25->writeControlEndRF(); + p25->setCCRunning(false); + + if (m_mode == STATE_P25 && !m_modeTimer.isRunning()) { + m_modeTimer.setTimeout(m_rfModeHang); + m_modeTimer.start(); + } + } + + if (p25CCDurationTimer.isPaused()) { + p25CCDurationTimer.resume(); + } + } + } + + if (g_killed) { + if (p25 != NULL) { + if (m_p25CtrlBcstContinuous && !hasTxShutdown) { + m_modem->clearP25Data(); + p25->reset(); + + p25->writeControlEndRF(); + p25->setCCRunning(false); + + p25CCDurationTimer.stop(); + p25CCIntervalTimer.stop(); + } + } + + hasTxShutdown = true; + if (!m_modem->hasTX()) { + killed = true; + } + } + + m_modeTimer.clock(ms); + + if (ms < 2U) + Thread::sleep(1U); + } + + ::ActivityLog("DVM", true, "Host is down and stopping"); + setMode(HOST_STATE_QUIT); + + delete dmr; + delete p25; + + return EXIT_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Reads basic configuration parameters from the INI. +/// +bool Host::readParams() +{ + yaml::Node protocolConf = m_conf["protocols"]; + m_dmrEnabled = protocolConf["dmr"]["enable"].as(false); + m_p25Enabled = protocolConf["p25"]["enable"].as(false); + + yaml::Node systemConf = m_conf["system"]; + m_duplex = systemConf["duplex"].as(true); + + m_timeout = systemConf["timeout"].as(120U); + m_rfModeHang = systemConf["rfModeHang"].as(10U); + m_netModeHang = systemConf["netModeHang"].as(3U); + if (!systemConf["modeHang"].isNone()) { + m_rfModeHang = m_netModeHang = systemConf["modeHang"].as(); + } + + m_identity = systemConf["identity"].as(); + m_fixedMode = systemConf["fixedMode"].as(false); + + removeLockFile(); + + LogInfo("General Parameters"); + LogInfo(" DMR: %s", m_dmrEnabled ? "enabled" : "disabled"); + LogInfo(" P25: %s", m_p25Enabled ? "enabled" : "disabled"); + LogInfo(" Duplex: %s", m_duplex ? "yes" : "no"); + LogInfo(" Timeout: %us", m_timeout); + LogInfo(" RF Mode Hang: %us", m_rfModeHang); + LogInfo(" Net Mode Hang: %us", m_netModeHang); + LogInfo(" Identity: %s", m_identity.c_str()); + LogInfo(" Fixed Mode: %s", m_fixedMode ? "yes" : "no"); + LogInfo(" Lock Filename: %s", g_lockFile.c_str()); + + yaml::Node systemInfo = systemConf["info"]; + m_latitude = systemInfo["latitude"].as(0.0F); + m_longitude = systemInfo["longitude"].as(0.0F); + m_height = systemInfo["height"].as(0); + m_power = systemInfo["power"].as(0U); + m_location = systemInfo["location"].as(); + + LogInfo("System Info Parameters"); + LogInfo(" Latitude: %fdeg N", m_latitude); + LogInfo(" Longitude: %fdeg E", m_longitude); + LogInfo(" Height: %um", m_height); + LogInfo(" Power: %uW", m_power); + LogInfo(" Location: \"%s\"", m_location.c_str()); + + // try to load bandplan identity table + std::string idenLookupFile = systemConf["iden_table"]["file"].as(); + uint32_t idenReloadTime = systemConf["iden_table"]["time"].as(0U); + + if (idenLookupFile.length() <= 0U) { + ::LogError(LOG_HOST, "No bandplan identity table? This must be defined!"); + return false; + } + + LogInfo("Iden Table Lookups"); + LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None"); + if (idenReloadTime > 0U) + LogInfo(" Reload: %u mins", idenReloadTime); + + m_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime); + m_idenTable->read(); + + yaml::Node rfssConfig = systemConf["config"]; + m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); + if (m_channelId > 15U) { // clamp to 15 + m_channelId = 15U; + } + + IdenTable entry = m_idenTable->find(m_channelId); + if (entry.baseFrequency() == 0U) { + ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); + return false; + } + + if (entry.txOffsetMhz() == 0U) { + ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); + return false; + } + + uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); + float calcTxOffset = entry.txOffsetMhz() * 1000000; + + m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); + if (m_channelNo == 0U) { // clamp to 1 + m_channelNo = 1U; + } + if (m_channelNo > 4095U) { // clamp to 4095 + m_channelNo = 4095U; + } + + m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset); + m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); + + yaml::Node& voiceChList = rfssConfig["voiceChNo"]; + for (size_t i = 0; i < voiceChList.size(); i++) { + uint32_t chNo = (uint32_t)::strtoul(voiceChList[i].as("1").c_str(), NULL, 16); + m_voiceChNo.push_back(chNo); + } + + std::string strVoiceChNo = ""; + for (auto it = m_voiceChNo.begin(); it != m_voiceChNo.end(); ++it) { + int decVal = ::atoi(std::to_string(*it).c_str()); + char hexStr[8]; + + ::sprintf(hexStr, "$%04X", decVal); + + strVoiceChNo.append(std::string(hexStr)); + strVoiceChNo.append(","); + } + strVoiceChNo.erase(strVoiceChNo.find_last_of(",")); + + m_dmrColorCode = rfssConfig["colorCode"].as(2U); + + m_p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("293").c_str(), NULL, 16); + m_p25PatchSuperGroup = (uint32_t)::strtoul(rfssConfig["pSuperGroup"].as("FFFF").c_str(), NULL, 16); + m_p25NetId = (uint32_t)::strtoul(rfssConfig["netId"].as("BB800").c_str(), NULL, 16); + if (m_p25NetId == 0U) { // clamp to 1 + m_p25NetId = 1U; + } + if (m_p25NetId > 0xFFFFEU) { // clamp to $FFFFE + m_p25NetId = 0xFFFFEU; + } + m_p25SysId = (uint32_t)::strtoul(rfssConfig["sysId"].as("001").c_str(), NULL, 16); + if (m_p25SysId == 0U) { // clamp to 1 + m_p25SysId = 1U; + } + if (m_p25SysId > 0xFFEU) { // clamp to $FFE + m_p25SysId = 0xFFEU; + } + m_p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as("1").c_str(), NULL, 16); + if (m_p25RfssId == 0U) { // clamp to 1 + m_p25RfssId = 1U; + } + if (m_p25RfssId > 0xFEU) { // clamp to $FE + m_p25RfssId = 0xFEU; + } + m_p25SiteId = (uint8_t)::strtoul(rfssConfig["siteId"].as("1").c_str(), NULL, 16); + if (m_p25SiteId == 0U) { // clamp to 1 + m_p25SiteId = 1U; + } + if (m_p25SiteId > 0xFEU) { // clamp to $FE + m_p25SiteId = 0xFEU; + } + + LogInfo("System Config Parameters"); + LogInfo(" RX Frequency: %uHz", m_rxFrequency); + LogInfo(" TX Frequency: %uHz", m_txFrequency); + LogInfo(" Base Frequency: %uHz", entry.baseFrequency()); + LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz()); + LogInfo(" Bandwidth: %fKHz", entry.chBandwidthKhz()); + LogInfo(" Channel Spacing: %fKHz", entry.chSpaceKhz()); + LogInfo(" Channel Id: %u", m_channelId); + LogInfo(" Channel No.: $%04X", m_channelNo); + LogInfo(" Voice Channel No(s).: %s", strVoiceChNo.c_str()); + LogInfo(" DMR Color Code: %u", m_dmrColorCode); + LogInfo(" P25 NAC: $%03X", m_p25NAC); + LogInfo(" P25 Patch Super Group: $%04X", m_p25PatchSuperGroup); + LogInfo(" P25 Network Id: $%05X", m_p25NetId); + LogInfo(" P25 System Id: $%03X", m_p25SysId); + LogInfo(" P25 RFSS Id: $%02X", m_p25RfssId); + LogInfo(" P25 Site Id: $%02X", m_p25SiteId); + + return true; +} + +/// +/// Initializes the modem DSP. +/// +bool Host::createModem() +{ + yaml::Node modemConf = m_conf["system"]["modem"]; + std::string port = modemConf["port"].as(); + bool rxInvert = modemConf["rxInvert"].as(false); + bool txInvert = modemConf["txInvert"].as(false); + bool pttInvert = modemConf["pttInvert"].as(false); + bool dcBlocker = modemConf["dcBlocker"].as(true); + bool cosLockout = modemConf["cosLockout"].as(false); + uint32_t txDelay = modemConf["txDelay"].as(1U); + uint32_t dmrDelay = modemConf["dmrDelay"].as(7U); + int rxDCOffset = modemConf["rxDCOffset"].as(0); + int txDCOffset = modemConf["txDCOffset"].as(0); + float rxLevel = modemConf["rxLevel"].as(50.0F); + float cwIdTXLevel = modemConf["cwIdTxLevel"].as(50.0F); + float dmrTXLevel = modemConf["dmrTxLevel"].as(50.0F); + float p25TXLevel = modemConf["p25TxLevel"].as(50.0F); + if (!modemConf["txLevel"].isNone()) { + cwIdTXLevel = dmrTXLevel = p25TXLevel = modemConf["txLevel"].as(50.0F); + } + bool disableOFlowReset = modemConf["disableOFlowReset"].as(false); + bool trace = modemConf["trace"].as(false); + bool debug = modemConf["debug"].as(false); + + LogInfo("Modem Parameters"); + LogInfo(" Port: %s", port.c_str()); + LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no"); + LogInfo(" TX Invert: %s", txInvert ? "yes" : "no"); + LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no"); + LogInfo(" DC Blocker: %s", dcBlocker ? "yes" : "no"); + LogInfo(" COS Lockout: %s", cosLockout ? "yes" : "no"); + LogInfo(" TX Delay: %u (%ums)", txDelay, (txDelay * 10)); + LogInfo(" RX DC Offset: %d", rxDCOffset); + LogInfo(" TX DC Offset: %d", txDCOffset); + LogInfo(" DMR Delay: %u (%.1fms)", dmrDelay, float(dmrDelay) * 0.0416666F); + LogInfo(" RX Level: %.1f%%", rxLevel); + LogInfo(" CW Id TX Level: %.1f%%", cwIdTXLevel); + LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel); + LogInfo(" P25 TX Level: %.1f%%", p25TXLevel); + LogInfo(" Disable Overflow Reset: %s", disableOFlowReset ? "yes" : "no"); + + if (debug) { + LogInfo(" Debug: yes"); + } + + m_modem = Modem::createModem(port, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, txDelay, dmrDelay, disableOFlowReset, trace, debug); + m_modem->setModeParams(m_dmrEnabled, m_p25Enabled); + m_modem->setLevels(rxLevel, cwIdTXLevel, dmrTXLevel, p25TXLevel); + m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); + m_modem->setDMRParams(m_dmrColorCode); + + bool ret = m_modem->open(); + if (!ret) { + delete m_modem; + m_modem = NULL; + return false; + } + + return true; +} + +/// +/// Initializes network connectivity. +/// +bool Host::createNetwork() +{ + yaml::Node networkConf = m_conf["network"]; + std::string address = networkConf["address"].as(); + uint32_t port = networkConf["port"].as(TRAFFIC_DEFAULT_PORT); + uint32_t local = networkConf["local"].as(0U); + std::string rconAddress = networkConf["rconAddress"].as("127.0.0.1"); + uint32_t rconPort = networkConf["rconPort"].as(RCON_DEFAULT_PORT); + uint32_t id = networkConf["id"].as(0U); + uint32_t jitter = networkConf["talkgroupHang"].as(360U); + m_netTalkgroupHang = networkConf["talkgroupHang"].as(10U); + std::string password = networkConf["password"].as(); + bool slot1 = networkConf["slot1"].as(true); + bool slot2 = networkConf["slot2"].as(true); + bool transferActivityLog = networkConf["transferActivityLog"].as(false); + bool updateLookup = networkConf["updateLookups"].as(false); + bool debug = networkConf["debug"].as(false); + + IdenTable entry = m_idenTable->find(m_channelId); + + LogInfo("Network Parameters"); + LogInfo(" Peer Id: %u", id); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + LogInfo(" RCON Address: %s", rconAddress.c_str()); + LogInfo(" RCON Port: %u", rconPort); + LogInfo(" DMR Jitter: %ums", jitter); + LogInfo(" Talkgroup Hang: %us", m_netTalkgroupHang); + LogInfo(" Slot 1: %s", slot1 ? "enabled" : "disabled"); + LogInfo(" Slot 2: %s", slot2 ? "enabled" : "disabled"); + LogInfo(" Transfer Activity Log: %s", transferActivityLog ? "enabled" : "disabled"); + LogInfo(" Update Lookups: %s", updateLookup ? "enabled" : "disabled"); + + if (debug) { + LogInfo(" Debug: yes"); + } + + m_network = new Network(address, port, local, id, password, m_duplex, debug, slot1, slot2, transferActivityLog, updateLookup); + + m_network->setLookups(m_ridLookup, m_tidLookup); + m_network->setConfig(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_power, + m_latitude, m_longitude, m_height, m_location); + + bool ret = m_network->open(); + if (!ret) { + delete m_network; + m_network = NULL; + LogError(LOG_HOST, "failed to initialize traffic networking!"); + return false; + } + + m_network->enable(true); + ::ActivityLogSetNetwork(m_network); + + // initialize network remote command + m_remoteControl = new RemoteControl(rconAddress, rconPort); + m_remoteControl->setLookups(m_ridLookup, m_tidLookup); + ret = m_remoteControl->open(); + if (!ret) { + delete m_remoteControl; + m_remoteControl = NULL; + LogError(LOG_HOST, "failed to initialize remote command networking! remote command control will be unavailable!"); + // remote command control failing isn't fatal -- we'll allow this to return normally + } + + return true; +} + +/// +/// Helper to set the host/modem running state. +/// +/// Mode enumeration to switch the host/modem state to. +void Host::setMode(uint8_t mode) +{ + assert(m_modem != NULL); + + if (m_mode != mode) { + LogDebug(LOG_HOST, "setMode, m_mode = %u, mode = %u", m_mode, mode); + } + + switch (mode) { + case STATE_DMR: + m_modem->setMode(STATE_DMR); + + // if the modem is in duplex -- write DMR start sync + if (m_duplex) { + m_modem->writeDMRStart(true); + m_dmrTXTimer.start(); + } + + m_mode = STATE_DMR; + m_modeTimer.start(); + //m_cwIdTimer.stop(); + createLockFile("DMR"); + break; + + case STATE_P25: + m_modem->setMode(STATE_P25); + m_mode = STATE_P25; + m_modeTimer.start(); + //m_cwIdTimer.stop(); + createLockFile("P25"); + break; + + case HOST_STATE_LOCKOUT: + LogWarning(LOG_HOST, "Mode change, MODE_LOCKOUT"); + if (m_network != NULL) + m_network->enable(false); + + if (m_mode == STATE_DMR && m_duplex && m_modem->hasTX()) { + m_modem->writeDMRStart(false); + m_dmrTXTimer.stop(); + } + + m_modem->setMode(STATE_IDLE); + m_mode = HOST_STATE_LOCKOUT; + m_modeTimer.stop(); + //m_cwIdTimer.stop(); + removeLockFile(); + break; + + case HOST_STATE_ERROR: + LogWarning(LOG_HOST, "Mode change, MODE_ERROR"); + if (m_network != NULL) + m_network->enable(false); + + if (m_mode == STATE_DMR && m_duplex && m_modem->hasTX()) { + m_modem->writeDMRStart(false); + m_dmrTXTimer.stop(); + } + + m_mode = HOST_STATE_ERROR; + m_modeTimer.stop(); + m_cwIdTimer.stop(); + removeLockFile(); + break; + + default: + if (m_network != NULL) + m_network->enable(true); + + if (m_mode == STATE_DMR && m_duplex && m_modem->hasTX()) { + m_modem->writeDMRStart(false); + m_dmrTXTimer.stop(); + } + + m_modem->setMode(STATE_IDLE); + + if (m_mode == HOST_STATE_ERROR) { + m_modem->sendCWId(m_cwCallsign); + + m_cwIdTimer.setTimeout(m_cwIdTime); + m_cwIdTimer.start(); + } + + removeLockFile(); + m_modeTimer.stop(); + + if (m_mode == HOST_STATE_QUIT) { + m_modem->close(); + delete m_modem; + + if (m_tidLookup != NULL) { + m_tidLookup->stop(); + delete m_tidLookup; + } + if (m_ridLookup != NULL) { + m_ridLookup->stop(); + delete m_ridLookup; + } + + if (m_network != NULL) { + m_network->close(); + delete m_network; + } + + if (m_remoteControl != NULL) { + m_remoteControl->close(); + delete m_remoteControl; + } + } + else { + m_mode = STATE_IDLE; + } + break; + } +} + +/// +/// +/// +/// +void Host::createLockFile(const char* mode) const +{ + FILE* fp = ::fopen(g_lockFile.c_str(), "wt"); + if (fp != NULL) { + ::fprintf(fp, "%s\n", mode); + ::fclose(fp); + } +} + +/// +/// +/// +void Host::removeLockFile() const +{ + ::remove(g_lockFile.c_str()); +} diff --git a/host/Host.h b/host/Host.h new file mode 100644 index 00000000..c8528bb3 --- /dev/null +++ b/host/Host.h @@ -0,0 +1,142 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HOST_H__) +#define __HOST_H__ + +#include "Defines.h" +#include "network/Network.h" +#include "network/RemoteControl.h" +#include "modem/Modem.h" +#include "Timer.h" +#include "lookups/IdenTableLookup.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" +#include "yaml/Yaml.h" + +#include + +// --------------------------------------------------------------------------- +// Class Prototypes +// --------------------------------------------------------------------------- +class HOST_SW_API RemoteControl; + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the core host service logic. +// --------------------------------------------------------------------------- + +class HOST_SW_API Host { +public: + /// Initializes a new instance of the Host class. + Host(const std::string& confFile); + /// Finalizes a instance of the Host class. + ~Host(); + + /// Executes the main modem host processing loop. + int run(); + +private: + const std::string& m_confFile; + yaml::Node m_conf; + + modem::Modem* m_modem; + network::Network* m_network; + + uint8_t m_mode; + + Timer m_modeTimer; + Timer m_dmrTXTimer; + Timer m_cwIdTimer; + + bool m_dmrEnabled; + bool m_p25Enabled; + + bool m_p25CtrlBcstContinuous; + + bool m_duplex; + bool m_fixedMode; + + uint32_t m_timeout; + uint32_t m_rfModeHang; + uint32_t m_netModeHang; + uint32_t m_netTalkgroupHang; + + std::string m_identity; + std::string m_cwCallsign; + uint32_t m_cwIdTime; + + float m_latitude; + float m_longitude; + int m_height; + uint32_t m_power; + std::string m_location; + + uint32_t m_rxFrequency; + uint32_t m_txFrequency; + uint8_t m_channelId; + uint32_t m_channelNo; + std::vector m_voiceChNo; + + lookups::IdenTableLookup* m_idenTable; + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + bool m_dmrBeacons; + bool m_controlData; + + uint32_t m_dmrColorCode; + uint32_t m_p25NAC; + uint32_t m_p25PatchSuperGroup; + uint32_t m_p25NetId; + uint32_t m_p25SysId; + uint8_t m_p25RfssId; + uint8_t m_p25SiteId; + + friend class RemoteControl; + RemoteControl* m_remoteControl; + + /// Reads basic configuration parameters from the INI. + bool readParams(); + /// Initializes the modem DSP. + bool createModem(); + /// Initializes network connectivity. + bool createNetwork(); + + /// Helper to set the host/modem running state. + void setMode(uint8_t mode); + + /// + void createLockFile(const char* mode) const; + /// + void removeLockFile() const; +}; + +#endif // __HOST_H__ diff --git a/host/calibrate/Console.cpp b/host/calibrate/Console.cpp new file mode 100644 index 00000000..2959f60d --- /dev/null +++ b/host/calibrate/Console.cpp @@ -0,0 +1,176 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "host/calibrate/Console.h" + +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#include +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// Initializes a new instance of the Console class. +/// +Console::Console() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Console class. +/// +Console::~Console() +{ + /* stub */ +} + +/// +/// Opens the terminal console. +/// +bool Console::open() +{ + return true; +} + +/// +/// Retrieves a character input on the keyboard. +/// +/// +int Console::getChar() +{ + if (::_kbhit() == 0) + return -1; + + return ::_getch(); +} + +/// +/// Closes the terminal console. +/// +void Console::close() +{ + /* stub */ +} +#else +/// +/// Initializes a new instance of the Console class. +/// +Console::Console() : + m_termios() +{ + ::memset(&m_termios, 0x00U, sizeof(termios)); +} + +/// +/// Finalizes a instance of the Console class. +/// +Console::~Console() +{ + /* stub */ +} + +/// +/// Opens the terminal console. +/// +bool Console::open() +{ + termios tios; + + int n = ::tcgetattr(STDIN_FILENO, &tios); + if (n != 0) { + ::fprintf(stderr, "tcgetattr: returned %d\r\n", n); + return -1; + } + + m_termios = tios; + + ::cfmakeraw(&tios); + + n = ::tcsetattr(STDIN_FILENO, TCSANOW, &tios); + if (n != 0) { + ::fprintf(stderr, "tcsetattr: returned %d\r\n", n); + return -1; + } + + return true; +} + +/// +/// Retrieves a character input on the keyboard. +/// +/// +int Console::getChar() +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + int n = ::select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); + if (n <= 0) { + if (n < 0) + ::fprintf(stderr, "select: returned %d\r\n", n); + return -1; + } + + char c; + n = ::read(STDIN_FILENO, &c, 1); + if (n <= 0) { + if (n < 0) + ::fprintf(stderr, "read: returned %d\r\n", n); + return -1; + } + + return c; +} + +/// +/// Closes the terminal console. +/// +void Console::close() +{ + int n = ::tcsetattr(STDIN_FILENO, TCSANOW, &m_termios); + if (n != 0) + ::fprintf(stderr, "tcsetattr: returned %d\r\n", n); +} +#endif diff --git a/host/calibrate/Console.h b/host/calibrate/Console.h new file mode 100644 index 00000000..563566ae --- /dev/null +++ b/host/calibrate/Console.h @@ -0,0 +1,67 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMCal project. (https://github.com/g4klx/MMDVMCal) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__CONSOLE_H__) +#define __CONSOLE_H__ + +#include "Defines.h" + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#endif + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements cross-platform handling of the terminal console. This is +// mainly used for the calibration mode. +// --------------------------------------------------------------------------- + +class HOST_SW_API Console { +public: + /// Initializes a new instance of the Console class. + Console(); + /// Finalizes a instance of the Console class. + ~Console(); + + /// Opens the terminal console. + bool open(); + + /// Retrieves a character input on the keyboard. + int getChar(); + + /// Closes the terminal console. + void close(); + +private: +#if !defined(_WIN32) && !defined(_WIN64) + termios m_termios; +#endif +}; + +#endif // __CONSOLE_H__ diff --git a/host/calibrate/HostCal.cpp b/host/calibrate/HostCal.cpp new file mode 100644 index 00000000..6985899f --- /dev/null +++ b/host/calibrate/HostCal.cpp @@ -0,0 +1,1656 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMCal project. (https://github.com/g4klx/MMDVMCal) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "host/calibrate/HostCal.h" +#include "dmr/DMRDefines.h" +#include "p25/P25Defines.h" +#include "p25/data/DataHeader.h" +#include "p25/lc/LC.h" +#include "p25/P25Utils.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace modem; + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#endif + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define DMR_CAL_STR "[Tx] DMR 1200 Hz Tone Mode (2.75Khz Deviation)" +#define P25_CAL_STR "[Tx] P25 1200 Hz Tone Mode (2.83Khz Deviation)" +#define LF_CAL_STR "[Tx] DMR Low Frequency Mode (80 Hz square wave)" +#define DMR_CAL_1K_STR "[Tx] DMR BS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" +#define DMR_DMO_CAL_1K_STR "[Tx] DMR MS 1031 Hz Test Pattern (TS2 CC1 ID1 TG9)" +#define P25_CAL_1K_STR "[Tx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define DMR_FEC_STR "[Rx] DMR MS FEC BER Test Mode" +#define DMR_FEC_1K_STR "[Rx] DMR MS 1031 Hz Test Pattern (CC1 ID1 TG9)" +#define P25_FEC_STR "[Rx] P25 FEC BER Test Mode" +#define P25_FEC_1K_STR "[Rx] P25 1011 Hz Test Pattern (NAC293 ID1 TG1)" +#define RSSI_CAL_STR "RSSI Calibration Mode" + +#define DMR_SYM_LA_TST_STR "[Tx] DMR Symbol Test (Level A [+3])" +#define P25_SYM_LA_TST_STR "[Tx] P25 Symbol Test (Level A [+3])" +#define DMR_SYM_LB_TST_STR "[Tx] DMR Symbol Test (Level B [+1])" +#define P25_SYM_LB_TST_STR "[Tx] P25 Symbol Test (Level B [+1])" +#define DMR_SYM_LC_TST_STR "[Tx] DMR Symbol Test (Level C [-1])" +#define P25_SYM_LC_TST_STR "[Tx] P25 Symbol Test (Level C [-1])" +#define DMR_SYM_LD_TST_STR "[Tx] DMR Symbol Test (Level D [-3])" +#define P25_SYM_LD_TST_STR "[Tx] P25 Symbol Test (Level D [-3])" + +// Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VH_DMO1K[] = { + 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, + 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, + 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; + +// Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VT_DMO1K[] = { + 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, + 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, + 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; + +// Voice coding data + FEC, 1031 Hz Test Pattern +const uint8_t VOICE_1K[] = { + 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, + 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, + 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU }; + +// Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) +// NAC: 0x293, srcID: 1, dstID: TG1 +unsigned char LDU1_1K[] = { + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x39, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x6D, 0xE5, 0xD5, 0x48, + 0xAD, 0xE3, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x3B, 0xA1, 0x41, 0xC2, 0xD2, 0xBA, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x94, 0xC8, 0xFB, 0x02, 0x35, 0xA4, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; + +unsigned char LDU2_1K[] = { + 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, + 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, + 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, + 0x89, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, + 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x4A, 0x12, 0x40, + 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xE0, 0xE0, 0x00, 0x00, + 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, + 0xD8, 0x39, 0xAE, 0x8B, 0x48, 0xB6, 0x49, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, + 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB9, 0xA8, 0xF4, 0xF1, 0xFD, 0x60, 0xE2, 0x4A, 0x12, 0x43, 0x50, + 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, + 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the HostCal class. +/// +/// Full-path to the configuration file. +HostCal::HostCal(const std::string& confFile) : + m_confFile(confFile), + m_conf(), + m_port(), + m_serial(), + m_console(), + m_fec(), + m_transmit(false), + m_duplex(true), + m_txInvert(false), + m_rxInvert(false), + m_pttInvert(false), + m_dcBlocker(true), + m_txLevel(50.0F), + m_rxLevel(50.0F), + m_dmrEnabled(false), + m_dmrRx1K(false), + m_p25Enabled(false), + m_p25Rx1K(false), + m_txDCOffset(0), + m_rxDCOffset(0), + m_txDelay(1U), + m_dmrDelay(7U), + m_debug(false), + m_mode(STATE_DMR_CAL), + m_modeStr(DMR_CAL_STR), + m_berFrames(0U), + m_berBits(0U), + m_berErrs(0U), + m_berUndecodableLC(0U), + m_berUncorrectable(0U), + m_timeout(300U), + m_timer(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the HostCal class. +/// +HostCal::~HostCal() +{ + /* stub */ +} + +/// +/// Executes the calibration processing loop. +/// +/// Zero if successful, otherwise error occurred. +int HostCal::run() +{ + bool ret = yaml::Parse(m_conf, m_confFile.c_str()); + if (!ret) { + ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); + } + + yaml::Node modemConf = m_conf["system"]["modem"]; + m_port = modemConf["port"].as(); + m_serial = CSerialController(m_port, SERIAL_115200); + + // initialize system logging + ret = ::LogInitialise("", "", 0U, 2U); + if (!ret) { + ::fprintf(stderr, "unable to open the log file\n"); + return 1; + } + + getHostVersion(); + ::LogInfo(">> Modem Calibration"); + + if (m_port == NULL_MODEM) { + ::LogError(LOG_HOST, "Calibration mode is unsupported with the null modem!"); + return 2; + } + + // open serial connection to modem DSP and initialize + ret = m_serial.open(); + if (!ret) { + ::LogError(LOG_CAL, "Failed to open serial device"); + return 1; + } + + ret = initModem(); + if (!ret) { + ::LogError(LOG_CAL, "Modem is unresponsive"); + m_serial.close(); + return 1; + } + + // open terminal console + ret = m_console.open(); + if (!ret) { + m_serial.close(); + return 1; + } + + displayHelp(); + + m_rxInvert = modemConf["rxInvert"].as(false); + m_txInvert = modemConf["txInvert"].as(false); + m_pttInvert = modemConf["pttInvert"].as(false); + m_dcBlocker = modemConf["dcBlocker"].as(true); + + m_rxDCOffset = modemConf["rxDCOffset"].as(0); + m_txDCOffset = modemConf["txDCOffset"].as(0); + m_rxLevel = modemConf["rxLevel"].as(50.0F); + m_txLevel = modemConf["txLevel"].as(50.0F); + + m_txDelay = modemConf["txDelay"].as(1U); + m_dmrDelay = modemConf["dmrDelay"].as(7U); + + writeConfig(); + + printStatus(); + + bool end = false; + while (!end) { + int c = m_console.getChar(); + switch (c) { + /** Level Adjustment Commands */ + case 'I': + { + m_txInvert = !m_txInvert; + LogMessage(LOG_CAL, " - TX Invert: %s", m_txInvert ? "On" : "Off"); + writeConfig(); + } + break; + case 'i': + { + m_rxInvert = !m_rxInvert; + LogMessage(LOG_CAL, " - RX Invert: %s", m_rxInvert ? "On" : "Off"); + writeConfig(); + } + break; + case 'p': + { + m_pttInvert = !m_pttInvert; + LogMessage(LOG_CAL, " - PTT Invert: %s", m_pttInvert ? "On" : "Off"); + writeConfig(); + } + break; + case 'd': + { + m_dcBlocker = !m_dcBlocker; + LogMessage(LOG_CAL, " - DC Blocker: %s", m_dcBlocker ? "On" : "Off"); + writeConfig(); + } + break; + case 'R': + setRXLevel(1); + break; + case 'r': + setRXLevel(-1); + break; + case 'T': + setTXLevel(1); + break; + case 't': + setTXLevel(-1); + break; + case 'c': + setRXDCOffset(-1); + break; + case 'C': + setRXDCOffset(1); + break; + case 'o': + setTXDCOffset(-1); + break; + case 'O': + setTXDCOffset(1); + break; + + case '1': + { + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + if (m_mode == STATE_DMR_CAL) { + m_modeStr = DMR_SYM_LA_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_DMR_LEVELA); + } + else if (m_mode == STATE_P25_CAL) { + m_modeStr = P25_SYM_LA_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_P25_LEVELA); + } + } + break; + case '2': + { + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + if (m_mode == STATE_DMR_CAL) { + m_modeStr = DMR_SYM_LB_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_DMR_LEVELB); + } + else if (m_mode == STATE_P25_CAL) { + m_modeStr = P25_SYM_LB_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_P25_LEVELB); + } + } + break; + case '3': + { + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + if (m_mode == STATE_DMR_CAL) { + m_modeStr = DMR_SYM_LC_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_DMR_LEVELC); + } + else if (m_mode == STATE_P25_CAL) { + m_modeStr = P25_SYM_LC_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_P25_LEVELC); + } + } + break; + case '4': + { + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + if (m_mode == STATE_DMR_CAL) { + m_modeStr = DMR_SYM_LD_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_DMR_LEVELD); + } + else if (m_mode == STATE_P25_CAL) { + m_modeStr = P25_SYM_LD_TST_STR; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(STATE_P25_LEVELD); + } + } + break; + + /** Mode Commands */ + case 'Z': + { + m_mode = STATE_DMR_CAL; + m_modeStr = DMR_CAL_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'z': + { + m_mode = STATE_P25_CAL; + m_modeStr = P25_CAL_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'l': + { + m_mode = STATE_LF_CAL; + m_modeStr = LF_CAL_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'M': + { + m_mode = STATE_DMR_CAL_1K; + m_modeStr = DMR_CAL_1K_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'm': + { + m_mode = STATE_DMR_DMO_CAL_1K; + m_modeStr = DMR_DMO_CAL_1K_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'P': + { + m_mode = STATE_P25_CAL_1K; + m_modeStr = P25_CAL_1K_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'B': + case 'J': + { + m_mode = STATE_DMR; + if (c == 'J') { + m_modeStr = DMR_FEC_1K_STR; + m_dmrRx1K = true; + } + else { + m_modeStr = DMR_FEC_STR; + m_dmrRx1K = false; + } + m_duplex = false; + m_dmrEnabled = true; + m_p25Enabled = false; + m_debug = true; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'b': + case 'j': + { + m_mode = STATE_P25; + if (c == 'j') { + m_modeStr = P25_FEC_1K_STR; + m_p25Rx1K = true; + } + else { + m_modeStr = P25_FEC_STR; + m_p25Rx1K = false; + } + m_duplex = false; + m_dmrEnabled = false; + m_p25Enabled = true; + m_debug = true; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + case 'x': + { + m_mode = STATE_RSSI_CAL; + m_modeStr = RSSI_CAL_STR; + m_duplex = true; + m_dmrEnabled = false; + m_dmrRx1K = false; + m_p25Enabled = false; + m_p25Rx1K = false; + m_debug = false; + + LogMessage(LOG_CAL, " - %s", m_modeStr.c_str()); + writeConfig(); + } + break; + + /** General Commands */ + case ' ': + setTransmit(); + break; + case '`': + printStatus(); + break; + case 'V': + getHostVersion(); + break; + case 'v': + getFirmwareVersion(); + break; + case 'H': + case 'h': + displayHelp(); + break; + case 'S': + case 's': + { + 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()); + } + break; + case 'Q': + case 'q': + end = true; + break; + + case -1: + break; + default: + LogError(LOG_CAL, "Unknown command - %c (H/h for help)", c); + break; + } + + uint8_t buffer[200U]; + readModem(buffer, 200U); + + timerClock(); + sleep(5U); + } + + if (m_transmit) + setTransmit(); + + m_serial.close(); + m_console.close(); + return 0; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Helper to print the calibration help to the console. +/// +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, " 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"); + LogMessage(LOG_CAL, " Q/q Quit"); + LogMessage(LOG_CAL, "Level Adjustment Commands:"); + 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"); + LogMessage(LOG_CAL, " R/r Increase/Decrease receive level"); + LogMessage(LOG_CAL, " T/t Increase/Decrease transmit level"); + LogMessage(LOG_CAL, " C/c Increase/Decrease RX DC offset level"); + LogMessage(LOG_CAL, " O/o Increase/Decrease TX DC offset 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", 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, " 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, " x %s", RSSI_CAL_STR); +} + +/// +/// Helper to change the Rx level. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setTXLevel(int incr) +{ + if (incr > 0 && m_txLevel < 100.0F) { + m_txLevel += 0.25F; + + // clamp values + if (m_txLevel > 100.0F) + m_txLevel = 100.0F; + + LogMessage(LOG_CAL, " - TX Level: %.1f%%", m_txLevel); + return writeConfig(); + } + + if (incr < 0 && m_txLevel > 0.0F) { + m_txLevel -= 0.25F; + + // clamp values + if (m_txLevel < 0.0F) + m_txLevel = 0.0F; + + LogMessage(LOG_CAL, " - TX Level: %.1f%%", m_txLevel); + return writeConfig(); + } + + return true; +} + +/// +/// Helper to change the Rx level. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setRXLevel(int incr) +{ + if (incr > 0 && m_rxLevel < 100.0F) { + m_rxLevel += 0.25F; + + // clamp values + if (m_rxLevel > 100.0F) + m_rxLevel = 100.0F; + + LogMessage(LOG_CAL, " - RX Level: %.1f%%", m_rxLevel); + return writeConfig(); + } + + if (incr < 0 && m_rxLevel > 0.0F) { + m_rxLevel -= 0.25F; + + // clamp values + if (m_rxLevel < 0.0F) + m_rxLevel = 0.0F; + + LogMessage(LOG_CAL, " - RX Level: %.1f%%", m_rxLevel); + return writeConfig(); + } + + return true; +} + +/// +/// Helper to change the Tx DC offset. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setTXDCOffset(int incr) +{ + if (incr > 0 && m_txDCOffset < 127) { + m_txDCOffset++; + LogMessage(LOG_CAL, " - TX DC Offset: %d", m_txDCOffset); + return writeConfig(); + } + + if (incr < 0 && m_txDCOffset > -127) { + m_txDCOffset--; + LogMessage(LOG_CAL, " - TX DC Offset: %d", m_txDCOffset); + return writeConfig(); + } + + return true; +} + +/// +/// Helper to change the Rx DC offset. +/// +/// Amount to change. +/// True, if setting was applied, otherwise false. +bool HostCal::setRXDCOffset(int incr) +{ + if (incr > 0 && m_rxDCOffset < 127) { + m_rxDCOffset++; + LogMessage(LOG_CAL, " - RX DC Offset: %d", m_rxDCOffset); + return writeConfig(); + } + + if (incr < 0 && m_rxDCOffset > -127) { + m_rxDCOffset--; + LogMessage(LOG_CAL, " - RX DC Offset: %d", m_rxDCOffset); + return writeConfig(); + } + + return true; +} + +/// +/// Helper to toggle modem transmit mode. +/// +/// True, if setting was applied, otherwise false. +bool HostCal::setTransmit() +{ + if (m_dmrEnabled || m_p25Enabled) { + LogError(LOG_CAL, "No transmit allowed in a BER Test mode"); + return false; + } + + m_transmit = !m_transmit; + + uint8_t buffer[50U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_CAL_DATA; + buffer[3U] = m_transmit ? 0x01U : 0x00U; + + int ret = m_serial.write(buffer, 4U); + if (ret <= 0) + return false; + + sleep(25U); + + if (m_transmit) + LogMessage(LOG_CAL, " - Modem start transmitting"); + else + LogMessage(LOG_CAL, " - Modem stop transmitting"); + + ret = readModem(buffer, 50U); + if (ret <= 0) + return false; + + if (buffer[2U] == CMD_NAK) { + LogError(LOG_CAL, "Got a NAK from the modem"); + return false; + } + + if (buffer[2U] != CMD_ACK) { + Utils::dump("Invalid response", buffer, ret); + return false; + } + + return true; +} + +/// +/// Initializes the modem DSP. +/// +/// True, if modem DSP is initialized, otherwise false. +bool HostCal::initModem() +{ + LogMessage(LOG_CAL, " - Initializing modem"); + sleep(2000U); + + if (!getFirmwareVersion()) + return false; + + bool ret = writeConfig(); + if (!ret) { + LogMessage(LOG_CAL, " - Modem unresponsive, retrying..."); + sleep(2500U); + ret = writeConfig(); + if (!ret) { + LogError(LOG_CAL, "Modem unresponsive to configuration set after 2 attempts, calibration may fail."); + } + } + + LogMessage(LOG_CAL, " - Modem Ready"); + return true; +} + +/// +/// Read data frames from the modem DSP. +/// +/// +/// +/// Zero if no data was read, otherwise returns length of data read. +int HostCal::readModem(uint8_t *buffer, uint32_t length) +{ + int n = m_serial.read(buffer + 0U, 1U); + if (n <= 0) + return n; + + if (buffer[0U] != DVM_FRAME_START) + return 0; + + n = 0; + for (uint32_t i = 0U; i < 20U && n == 0; i++) { + n = m_serial.read(buffer + 1U, 1U); + if (n < 0) + return n; + if (n == 0) + sleep(10U); + } + + if (n == 0) + return -1; + + uint32_t len = buffer[1U]; + + uint32_t offset = 2U; + for (uint32_t i = 0U; i < 20U && offset < len; i++) { + n = m_serial.read(buffer + offset, len - offset); + if (n < 0) + return n; + if (n == 0) + sleep(10U); + if (n > 0) + offset += n; + } + + if (len > 0) { + switch (buffer[2U]) { + case CMD_CAL_DATA: + { + bool inverted = (buffer[3U] == 0x80U); + short high = buffer[4U] << 8 | buffer[5U]; + 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); + } + break; + case CMD_RSSI_DATA: + { + unsigned short max = buffer[3U] << 8 | buffer[4U]; + unsigned short min = buffer[5U] << 8 | buffer[6U]; + unsigned short ave = buffer[7U] << 8 | buffer[8U]; + LogMessage(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); + } + break; + + case CMD_DMR_DATA1: + case CMD_DMR_DATA2: + processDMRBER(buffer + 4U, buffer[3]); + break; + + case CMD_DMR_LOST1: + case CMD_DMR_LOST2: + { + 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)); + + if (m_dmrEnabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + } + break; + + case CMD_P25_DATA: + processP25BER(buffer + 3U); + break; + + case CMD_P25_LOST: + { + 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)); + + if (m_p25Enabled) { + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + } + break; + + // These should not be received, but don't complain if we do + case CMD_GET_STATUS: + case CMD_GET_VERSION: + case CMD_ACK: + break; + + case CMD_NAK: + LogWarning(LOG_MODEM, "NAK, command = 0x%02X, reason = %u", buffer[3U], buffer[4U]); + break; + + case CMD_DEBUG1: + case CMD_DEBUG2: + case CMD_DEBUG3: + case CMD_DEBUG4: + case CMD_DEBUG5: + printDebug(buffer, len); + break; + + default: + LogWarning(LOG_MODEM, "Unknown message, type = %02X", buffer[2U]); + Utils::dump("Buffer dump", buffer, len); + break; + } + } + + return len; +} + +/// +/// Process DMR Rx BER. +/// +/// Buffer containing DMR audio frames +/// DMR Audio Sequence +void HostCal::processDMRBER(const uint8_t* buffer, uint8_t seq) +{ + if (seq == 65U) { + timerStart(); + + LogMessage(LOG_CAL, "DMR voice header received"); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + 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)); + } + + timerStop(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + + timerStart(); + + uint32_t errs = m_fec.measureDMRBER(buffer); + + 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); + else { + LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); + m_berUncorrectable++; + } + + m_berBits += 141U; + m_berErrs += errs; + m_berFrames++; +} + +/// +/// Process DMR Tx 1011hz BER. +/// +/// Buffer containing DMR audio frames +/// DMR Audio Sequence +void HostCal::processDMR1KBER(const uint8_t* buffer, uint8_t seq) +{ + uint32_t errs = 0U; + + if (seq == 65U) { + timerStart(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VH_DMO1K[i]); + + 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); + } + else if (seq == 66U) { + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VT_DMO1K[i]); + + m_berErrs += errs; + m_berBits += 264; + 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)); + } + + timerStop(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + + timerStart(); + + uint8_t dmrSeq = seq & 0x0FU; + if (dmrSeq > 5U) + dmrSeq = 5U; + + errs = 0U; + for (uint32_t i = 0U; i < 33U; i++) + errs += countErrs(buffer[i], VOICE_1K[i]); + + float ber = float(errs) / 2.64F; + + m_berErrs += errs; + m_berBits += 264; + 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); + else { + LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); + m_berUncorrectable++; + } +} + +/// +/// Process P25 Rx BER. +/// +/// Buffer containing P25 audio frames +void HostCal::processP25BER(const uint8_t* buffer) +{ + using namespace p25; + + uint8_t nid[P25_NID_LENGTH_BYTES]; + P25Utils::decode(buffer + 1U, nid, 48U, 114U); + uint8_t duid = nid[1U] & 0x0FU; + + uint32_t errs = 0U; + uint8_t imbe[18U]; + lc::LC lc = lc::LC(); + data::DataHeader dataHeader = data::DataHeader(); + + if (duid == P25_DUID_HDU) { + timerStart(); + + bool ret = lc.decodeHDU(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_HDU (Header), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_RF, "P25_DUID_HDU (Header), dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + } + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + else if (duid == P25_DUID_TDU) { + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, "P25_DUID_TDU (Terminator Data Unit), 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(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + else if (duid == P25_DUID_LDU1) { + timerStart(); + + bool ret = lc.decodeLDU1(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1) 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()); + } + + P25Utils::decode(buffer + 1U, imbe, 114U, 262U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 262U, 410U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 452U, 600U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 640U, 788U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 830U, 978U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); + errs += m_fec.measureP25BER(imbe); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), uncorrectable audio"); + m_berUncorrectable++; + } + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_LDU2) { + timerStart(); + + bool ret = lc.decodeLDU2(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2) LC, mfId = $%02X, algo = %X, kid = %X", + lc.getMFId(), lc.getAlgId(), lc.getKId()); + } + + P25Utils::decode(buffer + 1U, imbe, 114U, 262U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 262U, 410U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 452U, 600U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 640U, 788U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 830U, 978U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1020U, 1168U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1208U, 1356U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1398U, 1546U); + errs += m_fec.measureP25BER(imbe); + + P25Utils::decode(buffer + 1U, imbe, 1578U, 1726U); + errs += m_fec.measureP25BER(imbe); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), uncorrectable audio"); + m_berUncorrectable++; + } + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_PDU) { + timerStop(); + + // note: for the calibrator we will only process the PDU header -- and not the PDU data + uint8_t pduBuffer[P25_LDU_FRAME_LENGTH_BYTES]; + uint32_t bits = P25Utils::decode(buffer + 1U, pduBuffer, 0, P25_LDU_FRAME_LENGTH_BITS); + + uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + uint32_t rfPDUBits = 0U; + + for (uint32_t i = 0U; i < bits; i++, rfPDUBits++) { + bool b = READ_BIT(buffer, i); + WRITE_BIT(rfPDU, rfPDUBits, b); + } + + bool ret = dataHeader.decode(rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES); + if (!ret) { + LogWarning(LOG_RF, "P25_DUID_PDU (Packet Data Unit), unfixable RF 1/2 rate header data"); + Utils::dump(1U, "Unfixable PDU Data", rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES, P25_PDU_HEADER_LENGTH_BYTES); + } + else { + LogMessage(LOG_CAL, "P25_DUID_PDU (Packet Data Unit), fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u", + dataHeader.getFormat(), dataHeader.getSAP(), dataHeader.getFullMessage(), dataHeader.getBlocksToFollow(), dataHeader.getPadCount(), + dataHeader.getN(), dataHeader.getSeqNo()); + } + + delete[] rfPDU; + } + else if (duid == P25_DUID_TSDU) { + timerStop(); + + lc::TSBK tsbk = lc::TSBK(); + bool ret = tsbk.decode(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_TSDU (Trunking System Data Unit), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, "P25_DUID_TSDU (Trunking System Data Unit), mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, status = %u, message = %u, extFunc = %u, netId = %u, sysId = %u", + tsbk.getMFId(), tsbk.getLCO(), tsbk.getSrcId(), tsbk.getDstId(), tsbk.getService(), tsbk.getStatus(), tsbk.getMessage(), tsbk.getExtendedFunction(), + tsbk.getNetId(), tsbk.getSysId()); + } + } +} + +/// +/// Process P25 Tx 1011hz BER. +/// +/// Buffer containing P25 audio frames +void HostCal::processP251KBER(const uint8_t* buffer) +{ + using namespace p25; + + uint8_t nid[P25_NID_LENGTH_BYTES]; + P25Utils::decode(buffer + 1U, nid, 48U, 114U); + uint8_t duid = nid[1U] & 0x0FU; + + uint32_t errs = 0U; + lc::LC lc = lc::LC(); + + if (duid == P25_DUID_HDU) { + timerStart(); + + bool ret = lc.decodeHDU(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_HDU (Header), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_RF, "P25_DUID_HDU (Header), dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); + } + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + } + else if (duid == P25_DUID_TDU) { + if (m_berFrames != 0U) { + LogMessage(LOG_CAL, "P25_DUID_TDU (Terminator Data Unit), 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(); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + return; + } + else if (duid == P25_DUID_LDU1) { + timerStart(); + + bool ret = lc.decodeLDU1(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1) 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()); + } + + for (uint32_t i = 0U; i < 216U; i++) + errs += countErrs(buffer[i + 1U], LDU1_1K[i]); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, "P25_DUID_LDU1 (Logical Data Unit 1), uncorrectable audio"); + m_berUncorrectable++; + } + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } + else if (duid == P25_DUID_LDU2) { + timerStart(); + + bool ret = lc.decodeLDU2(buffer + 1U); + if (!ret) { + LogWarning(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), undecodable LC"); + m_berUndecodableLC++; + } + else { + LogMessage(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2) LC, mfId = $%02X, algo = %X, kid = %X", + lc.getMFId(), lc.getAlgId(), lc.getKId()); + } + + for (uint32_t i = 0U; i < 216U; i++) + errs += countErrs(buffer[i + 1U], LDU2_1K[i]); + + float ber = float(errs) / 12.33F; + if (ber < 10.0F) + LogMessage(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); + else { + LogWarning(LOG_CAL, "P25_DUID_LDU2 (Logical Data Unit 2), uncorrectable audio"); + m_berUncorrectable++; + } + + m_berBits += 1233U; + m_berErrs += errs; + m_berFrames++; + } +} + +/// +/// Retrieve the modem DSP version. +/// +/// True, if firmware version was recieved, otherwise false. +bool HostCal::getFirmwareVersion() +{ + uint8_t buffer[150U]; + + int ret = 0; + for (uint32_t i = 0U; i < 5U && ret <= 0; i++) { + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = CMD_GET_VERSION; + + ret = m_serial.write(buffer, 3U); + if (ret <= 0) + return false; + + sleep(100U); + + ret = readModem(buffer, 50U); + if (ret < 0) + return false; + if (ret == 0) + sleep(1000U); + } + + if (ret <= 0) { + LogError(LOG_CAL, "Unable to read the firmware version after 6 attempts"); + return false; + } + + if (buffer[2U] != CMD_GET_VERSION) { + Utils::dump("Invalid response", buffer, ret); + return false; + } + + LogMessage(LOG_CAL, MODEM_VERSION_STR, buffer[1U] - 4, buffer + 4U, buffer[3U]); + return true; +} + +/// +/// Write configuration to the modem DSP. +/// +/// True, if configuration is written, otherwise false. +bool HostCal::writeConfig() +{ + return writeConfig(m_mode); +} + +/// +/// Write configuration to the modem DSP. +/// +/// +/// True, if configuration is written, otherwise false. +bool HostCal::writeConfig(uint8_t modeOverride) +{ + uint8_t buffer[50U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 16U; + buffer[2U] = CMD_SET_CONFIG; + + buffer[3U] = 0x00U; + m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_rxInvert); + if (m_rxInvert) + buffer[3U] |= 0x01U; + m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_txInvert); + if (m_txInvert) + buffer[3U] |= 0x02U; + m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_pttInvert); + if (m_pttInvert) + buffer[3U] |= 0x04U; + if (m_debug) + buffer[3U] |= 0x10U; + if (!m_duplex) + buffer[3U] |= 0x80U; + + buffer[4U] = 0x00U; + m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_dcBlocker); + if (m_dcBlocker) + buffer[4U] |= 0x01U; + + if (m_dmrEnabled) + buffer[4U] |= 0x02U; + if (m_p25Enabled) + buffer[4U] |= 0x08U; + + buffer[5U] = m_txDelay; + + buffer[6U] = modeOverride; + + m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_rxLevel); + buffer[7U] = (uint8_t)(m_rxLevel * 2.55F + 0.5F); + + m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_txLevel); + buffer[8U] = (uint8_t)(m_txLevel * 2.55F + 0.5F); + + buffer[9U] = 1U; + + buffer[10U] = m_dmrDelay; + buffer[11U] = 128U; + + buffer[13U] = (uint8_t)(m_txLevel * 2.55F + 0.5F); + buffer[15U] = (uint8_t)(m_txLevel * 2.55F + 0.5F); + + m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_txDCOffset); + buffer[16U] = (uint8_t)(m_txDCOffset + 128); + m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_rxDCOffset); + buffer[17U] = (uint8_t)(m_rxDCOffset + 128); + + int ret = m_serial.write(buffer, 16U); + if (ret <= 0) + return false; + + sleep(10U); + + ret = readModem(buffer, 50U); + if (ret <= 0) + return false; + + if (buffer[2U] == CMD_NAK) { + LogError(LOG_CAL, "Got a NAK from the modem"); + return false; + } + + if (buffer[2U] != CMD_ACK) { + Utils::dump("Invalid response", buffer, ret); + return false; + } + + return true; +} + +/// +/// +/// +/// +void HostCal::sleep(uint32_t ms) +{ +#if defined(_WIN32) || defined(_WIN64) + ::Sleep(ms); +#else + ::usleep(ms * 1000); +#endif +} + +/// +/// +/// +void HostCal::timerClock() +{ + if (m_timer > 0U && m_timeout > 0U) { + 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)); + + m_berBits = 0U; + m_berErrs = 0U; + m_berFrames = 0U; + m_berUndecodableLC = 0U; + m_berUncorrectable = 0U; + + timerStop(); + } + } +} + +/// +/// +/// +void HostCal::timerStart() +{ + if (m_timeout > 0U) + m_timer = 1U; +} + +/// +/// +/// +void HostCal::timerStop() +{ + m_timer = 0U; +} + +/// +/// Prints the current status of the calibration. +/// +void HostCal::printStatus() +{ + LogMessage(LOG_CAL, " - PTT Invert: %s, RX Invert: %s, TX Invert: %s, DC Blocker: %s, RX Level: %.1f%%, TX Level: %.1f%%, TX DC Offset: %d, RX DC Offset: %d", + m_pttInvert ? "yes" : "no", m_rxInvert ? "yes" : "no", m_txInvert ? "yes" : "no", m_dcBlocker ? "yes" : "no", + m_rxLevel, m_txLevel, m_txDCOffset, m_rxDCOffset); + LogMessage(LOG_CAL, " - TX Delay: %u (%ums), DMR Delay: %u (%.1fms)", m_txDelay, (m_txDelay * 10), m_dmrDelay, float(m_dmrDelay) * 0.0416666F); + LogMessage(LOG_CAL, " - Operating Mode: %s", m_modeStr.c_str()); + + uint8_t buffer[50U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_GET_STATUS; + + int ret = m_serial.write(buffer, 4U); + if (ret <= 0) + return; + + sleep(25U); + + ret = readModem(buffer, 50U); + if (ret <= 0) + return; + + if (buffer[2U] == CMD_NAK) { + LogError(LOG_CAL, "Got a NAK from the modem"); + return; + } + + if (buffer[2U] != CMD_GET_STATUS) { + Utils::dump("Invalid response", buffer, ret); + return; + } + + uint8_t modemState = buffer[4U]; + bool tx = (buffer[5U] & 0x01U) == 0x01U; + + bool adcOverflow = (buffer[5U] & 0x02U) == 0x02U; + bool rxOverflow = (buffer[5U] & 0x04U) == 0x04U; + bool txOverflow = (buffer[5U] & 0x08U) == 0x08U; + bool dacOverflow = (buffer[5U] & 0x20U) == 0x20U; + + LogMessage(LOG_CAL, " - Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d]", + modemState, tx, adcOverflow, rxOverflow, txOverflow, dacOverflow); +} + +/// +/// +/// +/// +/// +void HostCal::printDebug(const uint8_t* buffer, uint32_t length) +{ + if (buffer[2U] == CMD_DEBUG1) { + LogMessage(LOG_MODEM, "M: %.*s", length - 3U, buffer + 3U); + } + else if (buffer[2U] == CMD_DEBUG2) { + short val1 = (buffer[length - 2U] << 8) | buffer[length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d", length - 5U, buffer + 3U, val1); + } + else if (buffer[2U] == CMD_DEBUG3) { + short val1 = (buffer[length - 4U] << 8) | buffer[length - 3U]; + short val2 = (buffer[length - 2U] << 8) | buffer[length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d", length - 7U, buffer + 3U, val1, val2); + } + else if (buffer[2U] == CMD_DEBUG4) { + short val1 = (buffer[length - 6U] << 8) | buffer[length - 5U]; + short val2 = (buffer[length - 4U] << 8) | buffer[length - 3U]; + short val3 = (buffer[length - 2U] << 8) | buffer[length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d %d", length - 9U, buffer + 3U, val1, val2, val3); + } + else if (buffer[2U] == CMD_DEBUG5) { + short val1 = (buffer[length - 8U] << 8) | buffer[length - 7U]; + short val2 = (buffer[length - 6U] << 8) | buffer[length - 5U]; + short val3 = (buffer[length - 4U] << 8) | buffer[length - 3U]; + short val4 = (buffer[length - 2U] << 8) | buffer[length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d %d %d", length - 11U, buffer + 3U, val1, val2, val3, val4); + } +} + +/// +/// +/// +/// +/// +/// +unsigned char HostCal::countErrs(unsigned char a, unsigned char b) +{ + int cnt = 0; + unsigned char tmp = a ^ b; + while (tmp) { + if (tmp % 2 == 1) + cnt++; + tmp /= 2; + } + return cnt; +} diff --git a/host/calibrate/HostCal.h b/host/calibrate/HostCal.h new file mode 100644 index 00000000..243d6fc0 --- /dev/null +++ b/host/calibrate/HostCal.h @@ -0,0 +1,157 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMCal project. (https://github.com/g4klx/MMDVMCal) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2018 by Andy Uribe CA6JAU +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__HOST_CAL_H__) +#define __HOST_CAL_H__ + +#include "Defines.h" +#include "edac/AMBEFEC.h" +#include "modem/Modem.h" +#include "modem/SerialController.h" +#include "host/calibrate/Console.h" +#include "host/Host.h" +#include "yaml/Yaml.h" + +#include + +// --------------------------------------------------------------------------- +// Class Declaration +// This class implements the interactive calibration mode. +// --------------------------------------------------------------------------- + +class HOST_SW_API HostCal { +public: + /// Initializes a new instance of the HostCal class. + HostCal(const std::string& confFile); + /// Finalizes a instance of the HostCal class. + ~HostCal(); + + /// Executes the calibration processing loop. + int run(); + +private: + const std::string& m_confFile; + yaml::Node m_conf; + + std::string m_port; + modem::CSerialController m_serial; + + Console m_console; + edac::AMBEFEC m_fec; + bool m_transmit; + + bool m_duplex; + + bool m_txInvert; + bool m_rxInvert; + bool m_pttInvert; + + bool m_dcBlocker; + + float m_txLevel; + float m_rxLevel; + + bool m_dmrEnabled; + bool m_dmrRx1K; + bool m_p25Enabled; + bool m_p25Rx1K; + int m_txDCOffset; + int m_rxDCOffset; + + uint32_t m_txDelay; + uint32_t m_dmrDelay; + + bool m_debug; + + uint8_t m_mode; + std::string m_modeStr; + + uint32_t m_berFrames; + uint32_t m_berBits; + uint32_t m_berErrs; + uint32_t m_berUndecodableLC; + uint32_t m_berUncorrectable; + + uint32_t m_timeout; + uint32_t m_timer; + + /// Helper to print the calibration help to the console. + void displayHelp(); + + /// Helper to change the Tx level. + bool setTXLevel(int incr); + /// Helper to change the Rx level. + bool setRXLevel(int incr); + /// Helper to change the Tx DC offset. + bool setTXDCOffset(int incr); + /// Helper to change the Rx DC offset. + bool setRXDCOffset(int incr); + /// Helper to toggle modem transmit mode. + bool setTransmit(); + + /// Initializes the modem DSP. + bool initModem(); + /// Read data frames from the modem DSP. + int readModem(uint8_t* buffer, uint32_t length); + /// Process DMR Rx BER. + void processDMRBER(const uint8_t* buffer, uint8_t seq); + /// Process DMR Tx 1011hz BER. + void processDMR1KBER(const uint8_t* buffer, uint8_t seq); + /// Process P25 Rx BER. + void processP25BER(const uint8_t* buffer); + /// Process P25 Tx 1011hz BER. + void processP251KBER(const uint8_t* buffer); + /// Retrieve the modem DSP version. + bool getFirmwareVersion(); + /// Write configuration to the modem DSP. + bool writeConfig(); + /// Write configuration to the modem DSP. + bool writeConfig(uint8_t modeOverride); + /// + void sleep(uint32_t ms); + + /// + void timerClock(); + /// + void timerStart(); + /// + void timerStop(); + + /// Prints the current status of the calibration. + void printStatus(); + /// + void printDebug(const uint8_t* buffer, uint32_t length); + + /// + unsigned char countErrs(unsigned char a, unsigned char b); +}; + +#endif // __HOST_CAL_H__ diff --git a/iden_table.dat b/iden_table.dat new file mode 100644 index 00000000..4dfac5e8 --- /dev/null +++ b/iden_table.dat @@ -0,0 +1,8 @@ +# +# This file sets the valid P25 bandplan identity table. +# +# ChId,Base Freq,Spacing (khz),Input Offset (mhz),Bandwidth (khz), +0,851006250,6.25,-45.000,12.5, +1,762006250,6.25,30.000,12.5, +15,935001250,6.25,-39.00000,12.5, +2,450000000,6.25,5.000,12.5, diff --git a/lookups/IdenTableLookup.cpp b/lookups/IdenTableLookup.cpp new file mode 100644 index 00000000..bb5f23cb --- /dev/null +++ b/lookups/IdenTableLookup.cpp @@ -0,0 +1,157 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "lookups/IdenTableLookup.h" +#include "Log.h" +#include "Timer.h" + +using namespace lookups; + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the IdenTableLookup class. +/// +/// Full-path to the channel identity table file. +/// Interval of time to reload the channel identity table. +IdenTableLookup::IdenTableLookup(const std::string& filename, uint32_t reloadTime) : LookupTable(filename, reloadTime) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the IdenTableLookup class. +/// +IdenTableLookup::~IdenTableLookup() +{ + /* stub */ +} + +/// +/// Finds a table entry in this lookup table. +/// +/// Unique identifier for table entry. +/// Table entry. +IdenTable IdenTableLookup::find(uint32_t id) +{ + IdenTable entry; + + m_mutex.lock(); + { + try { + entry = m_table.at(id); + } catch (...) { + /* stub */ + } + } + m_mutex.unlock(); + + float chBandwidthKhz = entry.chBandwidthKhz(); + if (chBandwidthKhz == 0.0F) + chBandwidthKhz = 12.5F; + float chSpaceKhz = entry.chSpaceKhz(); + if (chSpaceKhz < 2.5F) // clamp to 2.5 + chSpaceKhz = 2.5F; + if (chSpaceKhz > 6.25F) // clamp to 6.25 + chSpaceKhz = 6.25F; + + return IdenTable(entry.channelId(), entry.baseFrequency(), chSpaceKhz, entry.txOffsetMhz(), chBandwidthKhz); +} + +/// +/// Returns the list of entries in this lookup table. +/// +/// List of all entries in the lookup table. +std::vector IdenTableLookup::list() +{ + std::vector list = std::vector(); + if (m_table.size() > 0) { + for (auto it = m_table.begin(); it != m_table.end(); ++it) { + IdenTable entry = it->second; + list.push_back(entry); + } + } + + return list; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Parses a table entry from the passed comma delimited string. +/// +/// Comma delimited string to process into table entry. +/// Table entry. +IdenTable IdenTableLookup::parse(std::string tableEntry) +{ + std::string next; + std::vector parsed; + char delim = ','; + + for (auto it = tableEntry.begin(); it != tableEntry.end(); it++) { + if (*it == delim) { + if (!next.empty()) { + parsed.push_back(next); + next.clear(); + } + } + else + next += *it; + } + if (!next.empty()) + parsed.push_back(next); + + uint8_t channelId = (uint8_t)::atoi(parsed[0].c_str()); + uint32_t baseFrequency = (uint32_t)::atoi(parsed[1].c_str()); + float chSpaceKhz = float(::atof(parsed[2].c_str())); + float txOffsetMhz = float(::atof(parsed[3].c_str())); + float chBandwidthKhz = float(::atof(parsed[4].c_str())); + + if (chSpaceKhz == 0.0F) + chSpaceKhz = chBandwidthKhz / 2; + if (chSpaceKhz < 2.5F) // clamp to 2.5 + chSpaceKhz = 2.5F; + if (chSpaceKhz > 6.25F) // clamp to 6.25 + chSpaceKhz = 6.25F; + + IdenTable entry = IdenTable(channelId, baseFrequency, chSpaceKhz, txOffsetMhz, chBandwidthKhz); + + LogMessage(LOG_HOST, "Channel Id %u: BaseFrequency = %uHz, TXOffsetMhz = %fMHz, BandwidthKhz = %fKHz, SpaceKhz = %fKHz", + entry.channelId(), entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); + + return entry; +} diff --git a/lookups/IdenTableLookup.h b/lookups/IdenTableLookup.h new file mode 100644 index 00000000..d7078f1f --- /dev/null +++ b/lookups/IdenTableLookup.h @@ -0,0 +1,130 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__IDEN_TABLE_LOOKUP_H__) +#define __IDEN_TABLE_LOOKUP_H__ + +#include "Defines.h" +#include "lookups/LookupTable.h" +#include "Thread.h" +#include "Mutex.h" + +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an individual entry in the bandplan identity table. + // --------------------------------------------------------------------------- + + class HOST_SW_API IdenTable { + public: + /// Initializes a new insatnce of the IdenTable class. + IdenTable() : + m_channelId(0U), + m_baseFrequency(0U), + m_chSpaceKhz(0.0F), + m_txOffsetMhz(0.0F), + m_chBandwidthKhz(0.0F) + { + /* stub */ + } + /// Initializes a new insatnce of the IdenTable class. + /// + /// + /// + /// + /// + IdenTable(uint8_t channelId, uint32_t baseFrequency, float chSpaceKhz, float txOffsetMhz, float chBandwidthKhz) : + m_channelId(channelId), + m_baseFrequency(baseFrequency), + m_chSpaceKhz(chSpaceKhz), + m_txOffsetMhz(txOffsetMhz), + m_chBandwidthKhz(chBandwidthKhz) + { + /* stub */ + } + + /// Equals operator. + /// + /// + IdenTable& operator=(const IdenTable& data) + { + if (this != &data) { + m_channelId = data.m_channelId; + m_baseFrequency = data.m_baseFrequency; + m_chSpaceKhz = data.m_chSpaceKhz; + m_txOffsetMhz = data.m_txOffsetMhz; + m_chBandwidthKhz = data.m_chBandwidthKhz; + } + + return *this; + } + + public: + /// Channel ID for this entry. + __READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId); + /// Base frequency for this entry. + __READONLY_PROPERTY_PLAIN(uint32_t, baseFrequency, baseFrequency); + /// Channel spacing in kHz for this entry. + __READONLY_PROPERTY_PLAIN(float, chSpaceKhz, chSpaceKhz); + /// Channel transmit offset in MHz for this entry. + __READONLY_PROPERTY_PLAIN(float, txOffsetMhz, txOffsetMhz); + /// Channel bandwith in kHz for this entry. + __READONLY_PROPERTY_PLAIN(float, chBandwidthKhz, chBandwidthKhz); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a threading lookup table class that contains the bandplan + // identity table. + // --------------------------------------------------------------------------- + + class HOST_SW_API IdenTableLookup : public LookupTable { + public: + /// Initializes a new instance of the IdenTableLookup class. + IdenTableLookup(const std::string& filename, uint32_t reloadTime); + /// Finalizes a instance of the IdenTableLookup class. + virtual ~IdenTableLookup(); + + /// Finds a table entry in this lookup table. + virtual IdenTable find(uint32_t id); + /// Returns the list of entries in this lookup table. + std::vector list(); + + private: + /// Parses a table entry from the passed comma delimited string. + IdenTable parse(std::string tableEntry); + }; +} // namespace lookups + +#endif // __IDEN_TABLE_LOOKUP_H__ diff --git a/lookups/LookupTable.h b/lookups/LookupTable.h new file mode 100644 index 00000000..c4c15709 --- /dev/null +++ b/lookups/LookupTable.h @@ -0,0 +1,212 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__LOOKUP_TABLE_H__) +#define __LOOKUP_TABLE_H__ + +#include "Defines.h" +#include "Log.h" +#include "Thread.h" +#include "Timer.h" +#include "Mutex.h" + +#include +#include +#include +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a abstract threading class that contains base logic for + // building tables of data. + // --------------------------------------------------------------------------- + + template + class HOST_SW_API LookupTable : public Thread { + public: + /// Initializes a new instance of the LookupTable class. + /// Full-path to the lookup table file. + /// Interval of time to reload the channel identity table. + LookupTable(const std::string& filename, uint32_t reloadTime) : + Thread(), + m_filename(filename), + m_reloadTime(reloadTime) + { + /* stub */ + } + /// Finalizes a instance of the LookupTable class. + virtual ~LookupTable() + { + /* stub */ + } + + /// + virtual void entry() + { + Timer timer(1U, 60U * m_reloadTime); + timer.start(); + + while (!m_stop) { + sleep(1000U); + + timer.clock(); + if (timer.hasExpired()) { + load(); + timer.start(); + } + } + } + + /// Stops and unloads this lookup table. + virtual void stop() + { + if (m_reloadTime == 0U) { + delete this; + return; + } + + m_stop = true; + + wait(); + } + + /// Reads the lookup table from the specified lookup table file. + /// True, if lookup table was read, otherwise false. + virtual bool read() + { + bool ret = load(); + + if (m_reloadTime > 0U) + run(); + + return ret; + } + + /// Clears all entries from the lookup table. + virtual void clear() + { + m_mutex.lock(); + { + m_table.clear(); + } + m_mutex.unlock(); + } + + /// Helper to check if this lookup table has the specified unique ID. + /// Unique ID to check for. + /// True, if the lookup table has an entry by the specified unique ID, otherwise false. + virtual bool hasEntry(uint32_t id) + { + m_mutex.lock(); + { + try { + m_table.at(id); + return true; + } + catch (...) { + return false; + } + } + m_mutex.unlock(); + + return false; + } + + /// Finds a table entry in this lookup table. + /// Unique identifier for table entry. + /// Table entry. + virtual T find(uint32_t id) = 0; + + protected: + std::string m_filename; + uint32_t m_reloadTime; + std::unordered_map m_table; + Mutex m_mutex; + bool m_stop; + + bool m_acl; + + /// Parses a table entry from the passed comma delimited string. + /// Comma delimited string to process into table entry. + /// Table entry. + virtual T parse(std::string tableEntry) = 0; + + /// Loads the table from the passed lookup table file. + /// True, if lookup table was loaded, otherwise false. + virtual bool load() + { + if (strlen(m_filename.c_str()) <= 0) { + return false; + } + + FILE* fp = ::fopen(m_filename.c_str(), "rt"); + if (fp == NULL) { + LogError(LOG_HOST, "Cannot open the lookup file - %s", m_filename.c_str()); + return false; + } + + // clear table + clear(); + + m_mutex.lock(); + { + char buffer[100U]; + while (::fgets(buffer, 100U, fp) != NULL) { + if (buffer[0U] == '#') + continue; + + std::string strbuf = buffer; + char* p1 = ::strtok(buffer, ",\r\n"); + + if (p1 != NULL) { + uint32_t id = (uint32_t)::atoi(p1); + m_table[id] = parse(strbuf); + } + } + } + m_mutex.unlock(); + + ::fclose(fp); + + size_t size = m_table.size(); + if (size == 0U) + return false; + + LogInfoEx(LOG_HOST, "Loaded %u entries into lookup table", size); + + return true; + } + }; +} // namespace lookups + +#endif // __LOOKUP_TABLE_H__ diff --git a/lookups/RSSIInterpolator.cpp b/lookups/RSSIInterpolator.cpp new file mode 100644 index 00000000..b72d7d9f --- /dev/null +++ b/lookups/RSSIInterpolator.cpp @@ -0,0 +1,123 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "lookups/RSSIInterpolator.h" +#include "Log.h" + +using namespace lookups; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RSSIInterpolator class. +/// +RSSIInterpolator::RSSIInterpolator() : + m_map() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the RSSIInterpolator class. +/// +RSSIInterpolator::~RSSIInterpolator() +{ + m_map.clear(); +} + +/// +/// Loads the table from the passed RSSI mapping file. +/// +/// Full-path to the RSSI mapping file. +/// True, if RSSI mapping was loaded, otherwise false. +bool RSSIInterpolator::load(const std::string& filename) +{ + FILE* fp = ::fopen(filename.c_str(), "rt"); + if (fp == NULL) { + LogError(LOG_HOST, "Cannot open the RSSI data file - %s", filename.c_str()); + return false; + } + + char buffer[100U]; + while (::fgets(buffer, 100, fp) != NULL) { + if (buffer[0U] == '#') + continue; + + char* p1 = ::strtok(buffer, " \t\r\n"); + char* p2 = ::strtok(NULL, " \t\r\n"); + + if (p1 != NULL && p2 != NULL) { + uint16_t raw = uint16_t(::atoi(p1)); + int rssi = ::atoi(p2); + m_map.insert(std::pair(raw, rssi)); + } + } + + ::fclose(fp); + + LogInfoEx(LOG_HOST, "Loaded %u RSSI data mapping points from %s", m_map.size(), filename.c_str()); + + return true; +} + +/// +/// Interoplates the given raw RSSI value with the lookup map. +/// +/// Raw RSSI value from modem DSP. +/// Interpolated RSSI value. +int RSSIInterpolator::interpolate(uint16_t val) const +{ + if (m_map.empty()) + return 0; + + std::map::const_iterator it = m_map.lower_bound(val); + + if (it == m_map.end()) + return m_map.rbegin()->second; + + if (it == m_map.begin()) + return it->second; + + uint16_t x2 = it->first; + int y2 = it->second; + + --it; + uint16_t x1 = it->first; + int y1 = it->second; + + float p = float(val - x1) / float(x2 - x1); + + return int((1.0F - p) * float(y1) + p * float(y2)); +} diff --git a/lookups/RSSIInterpolator.h b/lookups/RSSIInterpolator.h new file mode 100644 index 00000000..57ea335e --- /dev/null +++ b/lookups/RSSIInterpolator.h @@ -0,0 +1,64 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RSSI_INTERPOLATOR_H__) +#define __RSSI_INTERPOLATOR_H__ + +#include "Defines.h" + +#include +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class HOST_SW_API RSSIInterpolator { + public: + /// Initializes a new instance of the RSSIInterpolator class. + RSSIInterpolator(); + /// Finalizes a instance of the RSSIInterpolator class. + ~RSSIInterpolator(); + + /// Loads the table from the passed RSSI mapping file. + bool load(const std::string& filename); + + /// Interoplates the given raw RSSI value with the lookup map. + int interpolate(uint16_t raw) const; + + private: + std::map m_map; + }; +} // namespace lookups + +#endif // __RSSI_INTERPOLATOR_H__ diff --git a/lookups/RadioIdLookup.cpp b/lookups/RadioIdLookup.cpp new file mode 100644 index 00000000..9656f0d1 --- /dev/null +++ b/lookups/RadioIdLookup.cpp @@ -0,0 +1,192 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "lookups/RadioIdLookup.h" +#include "p25/P25Defines.h" +#include "Log.h" +#include "Timer.h" + +using namespace lookups; + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RadioIdLookup class. +/// +/// Full-path to the radio ID table file. +/// Interval of time to reload the radio ID table. +/// Flag indicating whether radio ID access control is enabled. +RadioIdLookup::RadioIdLookup(const std::string& filename, uint32_t reloadTime, bool ridAcl) : LookupTable(filename, reloadTime), + m_acl(ridAcl) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the RadioIdLookup class. +/// +RadioIdLookup::~RadioIdLookup() +{ + /* stub */ +} + +/// +/// Toggles the specified radio ID enabled or disabled. +/// +/// Unique ID to toggle. +/// Flag indicating if radio ID is enabled or not. +void RadioIdLookup::toggleEntry(uint32_t id, bool enabled) +{ + RadioId rid = find(id); + if (rid.radioEnabled() == false && rid.radioDefault() == true) { + if (enabled) { + LogMessage(LOG_HOST, "Added enabled RID %u to RID ACL table", id); + } + else { + LogMessage(LOG_HOST, "Added disabled RID %u to RID ACL table", id); + } + } + + if (rid.radioEnabled() == false && rid.radioDefault() == false) { + if (enabled) { + LogMessage(LOG_HOST, "Enabled RID %u in RID ACL table", id); + } + else { + LogMessage(LOG_HOST, "Disabled RID %u in RID ACL table", id); + } + } + + addEntry(id, enabled); +} + +/// +/// Adds a new entry to the lookup table by the specified unique ID. +/// +/// Unique ID to add. +/// Flag indicating if radio ID is enabled or not. +void RadioIdLookup::addEntry(uint32_t id, bool enabled) +{ + if ((id == p25::P25_WUID_ALL) || (id == p25::P25_WUID_SYS) || (id == p25::P25_WUID_FNE)) { + return; + } + + RadioId entry = RadioId(enabled, false); + + m_mutex.lock(); + { + try { + RadioId _entry = m_table.at(id); + + // if the enabled value doesn't match -- override with the intended + if (_entry.radioEnabled() != enabled) { + _entry = RadioId(enabled, false); + m_table[id] = _entry; + } + } catch (...) { + m_table[id] = entry; + } + } + m_mutex.unlock(); +} + +/// +/// Finds a table entry in this lookup table. +/// +/// Unique identifier for table entry. +/// Table entry. +RadioId RadioIdLookup::find(uint32_t id) +{ + RadioId entry; + + if (id == p25::P25_WUID_ALL || id == p25::P25_WUID_SYS || id == p25::P25_WUID_FNE) { + return RadioId(true, false); + } + + m_mutex.lock(); + { + try { + entry = m_table.at(id); + } catch (...) { + entry = RadioId(false, true); + } + } + m_mutex.unlock(); + + return entry; +} + +/// +/// Flag indicating whether radio ID access control is enabled or not. +/// +/// True, if radio ID access control is enabled, otherwise false. +bool RadioIdLookup::getACL() +{ + return m_acl; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Parses a table entry from the passed comma delimited string. +/// +/// Comma delimited string to process into table entry. +/// Table entry. +RadioId RadioIdLookup::parse(std::string tableEntry) +{ + std::string next; + std::vector parsed; + char delim = ','; + + for (auto it = tableEntry.begin(); it != tableEntry.end(); it++) { + if (*it == delim) { + if (!next.empty()) { + parsed.push_back(next); + next.clear(); + } + } + else + next += *it; + } + if (!next.empty()) + parsed.push_back(next); + + bool radioEnabled = ::atoi(parsed[1].c_str()) == 1; + bool radioDefault = false; + + return RadioId(radioEnabled, radioDefault); +} diff --git a/lookups/RadioIdLookup.h b/lookups/RadioIdLookup.h new file mode 100644 index 00000000..0fb54a2e --- /dev/null +++ b/lookups/RadioIdLookup.h @@ -0,0 +1,127 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__RADIO_ID_LOOKUP_H__) +#define __RADIO_ID_LOOKUP_H__ + +#include "Defines.h" +#include "lookups/LookupTable.h" +#include "Thread.h" +#include "Mutex.h" + +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an individual entry in the radio ID table. + // --------------------------------------------------------------------------- + + class HOST_SW_API RadioId { + public: + /// Initializes a new insatnce of the RadioId class. + RadioId() : + m_radioEnabled(false), + m_radioDefault(false) + { + /* stub */ + } + /// Initializes a new insatnce of the RadioId class. + /// + /// + RadioId(bool radioEnabled, bool radioDefault) : + m_radioEnabled(radioEnabled), + m_radioDefault(radioDefault) + { + /* stub */ + } + + /// Equals operator. Copies this RadioId to another RadioId. + RadioId& operator=(const RadioId& data) + { + if (this != &data) { + m_radioEnabled = data.m_radioEnabled; + m_radioDefault = data.m_radioDefault; + } + + return *this; + } + + /// + /// + /// + void set(bool radioEnabled, bool radioDefault) + { + m_radioEnabled = radioEnabled; + m_radioDefault = radioDefault; + } + + public: + /// + __READONLY_PROPERTY_PLAIN(bool, radioEnabled, radioEnabled); + /// + __READONLY_PROPERTY_PLAIN(bool, radioDefault, radioDefault); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a threading lookup table class that contains a radio ID + // lookup table. + // --------------------------------------------------------------------------- + + class HOST_SW_API RadioIdLookup : public LookupTable { + public: + /// Initializes a new instance of the RadioIdLookup class. + RadioIdLookup(const std::string& filename, uint32_t reloadTime, bool ridAcl); + /// Finalizes a instance of the RadioIdLookup class. + virtual ~RadioIdLookup(); + + /// Toggles the specified radio ID enabled or disabled. + void toggleEntry(uint32_t id, bool enabled); + + /// Adds a new entry to the lookup table by the specified unique ID. + void addEntry(uint32_t id, bool enabled); + /// Finds a table entry in this lookup table. + virtual RadioId find(uint32_t id); + + /// Flag indicating whether radio ID access control is enabled or not. + bool getACL(); + + private: + bool m_acl; + + /// Parses a table entry from the passed comma delimited string. + virtual RadioId parse(std::string tableEntry); + }; +} // namespace lookups + +#endif // __RADIO_ID_LOOKUP_H__ diff --git a/lookups/TalkgroupIdLookup.cpp b/lookups/TalkgroupIdLookup.cpp new file mode 100644 index 00000000..ad9a4c75 --- /dev/null +++ b/lookups/TalkgroupIdLookup.cpp @@ -0,0 +1,156 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "lookups/TalkgroupIdLookup.h" +#include "Log.h" +#include "Timer.h" + +using namespace lookups; + +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the TalkgroupIdLookup class. +/// +/// Full-path to the talkgroup ID table file. +/// Interval of time to reload the talkgroup ID table. +/// Flag indicating whether talkgroup ID access control is enabled. +TalkgroupIdLookup::TalkgroupIdLookup(const std::string& filename, uint32_t reloadTime, bool tidAcl) : LookupTable(filename, reloadTime), + m_acl(tidAcl) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the TalkgroupIdLookup class. +/// +TalkgroupIdLookup::~TalkgroupIdLookup() +{ + /* stub */ +} + +/// +/// Adds a new entry to the lookup table by the specified unique ID. +/// +/// Unique ID to add. +/// DMR slot this talkgroup is valid on. +/// Flag indicating if talkgroup ID is enabled or not. +void TalkgroupIdLookup::addEntry(uint32_t id, unsigned char slot, bool enabled) +{ + TalkgroupId entry = TalkgroupId(enabled, slot, false); + + m_mutex.lock(); + { + try { + TalkgroupId _entry = m_table.at(id); + + // if the enabled value doesn't match -- override with the intended + if (_entry.tgEnabled() != enabled) { + _entry = TalkgroupId(enabled, _entry.tgSlot(), false); + m_table[id] = _entry; + } + } catch (...) { + m_table[id] = entry; + } + } + m_mutex.unlock(); +} + +/// +/// Finds a table entry in this lookup table. +/// +/// Unique identifier for table entry. +/// Table entry. +TalkgroupId TalkgroupIdLookup::find(uint32_t id) +{ + TalkgroupId entry; + + m_mutex.lock(); + { + try { + entry = m_table.at(id); + } catch (...) { + entry = TalkgroupId(false, 0U, true); + } + } + m_mutex.unlock(); + + return entry; +} + +/// +/// Flag indicating whether talkgroup ID access control is enabled or not. +/// +/// True, if talkgroup ID access control is enabled, otherwise false. +bool TalkgroupIdLookup::getACL() +{ + return m_acl; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Parses a table entry from the passed comma delimited string. +/// +/// Comma delimited string to process into table entry. +/// Table entry. +TalkgroupId TalkgroupIdLookup::parse(std::string tableEntry) +{ + std::string next; + std::vector parsed; + char delim = ','; + + for (auto it = tableEntry.begin(); it != tableEntry.end(); it++) { + if (*it == delim) { + if (!next.empty()) { + parsed.push_back(next); + next.clear(); + } + } + else + next += *it; + } + if (!next.empty()) + parsed.push_back(next); + + bool tgEnabled = ::atoi(parsed[1].c_str()) == 1; + uint8_t tgSlot = (uint8_t)::atoi(parsed[2].c_str()); + bool tgDefault = false; + + return TalkgroupId(tgEnabled, tgSlot, tgDefault); +} diff --git a/lookups/TalkgroupIdLookup.h b/lookups/TalkgroupIdLookup.h new file mode 100644 index 00000000..3666ec25 --- /dev/null +++ b/lookups/TalkgroupIdLookup.h @@ -0,0 +1,132 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__TALKGROUP_ID_LOOKUP_H__) +#define __TALKGROUP_ID_LOOKUP_H__ + +#include "Defines.h" +#include "lookups/LookupTable.h" +#include "Thread.h" +#include "Mutex.h" + +#include +#include + +namespace lookups +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents an individual entry in the talkgroup ID table. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupId { + public: + /// Initializes a new insatnce of the TalkgroupId class. + TalkgroupId() : + m_tgEnabled(false), + m_tgSlot(0U), + m_tgDefault(false) + { + /* stub */ + } + /// Initializes a new insatnce of the TalkgroupId class. + /// + /// + /// + TalkgroupId(bool tgEnabled, uint8_t tgSlot, bool tgDefault) : + m_tgEnabled(tgEnabled), + m_tgSlot(tgSlot), + m_tgDefault(tgDefault) + { + /* stub */ + } + + /// Equals operator. Copies this TalkgroupId to another TalkgroupId. + TalkgroupId& operator=(const TalkgroupId& data) + { + if (this != &data) { + m_tgEnabled = data.m_tgEnabled; + m_tgSlot = data.m_tgSlot; + m_tgDefault = data.m_tgDefault; + } + + return *this; + } + + /// + /// + /// + /// + void set(bool tgEnabled, uint8_t tgSlot, bool tgDefault) + { + m_tgEnabled = tgEnabled; + m_tgSlot = tgSlot; + m_tgDefault = tgDefault; + } + + public: + /// + __READONLY_PROPERTY_PLAIN(bool, tgEnabled, tgEnabled); + /// + __READONLY_PROPERTY_PLAIN(uint8_t, tgSlot, tgSlot); + /// + __READONLY_PROPERTY_PLAIN(bool, tgDefault, tgDefault); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements a threading lookup table class that contains a talkgroup + // ID lookup table. + // --------------------------------------------------------------------------- + + class HOST_SW_API TalkgroupIdLookup : public LookupTable { + public: + /// Initializes a new instance of the TalkgroupIdLookup class. + TalkgroupIdLookup(const std::string& filename, uint32_t reloadTime, bool tidAcl); + /// Finalizes a instance of the TalkgroupIdLookup class. + virtual ~TalkgroupIdLookup(); + + /// Adds a new entry to the lookup table by the specified unique ID. + void addEntry(uint32_t id, unsigned char slot, bool enabled); + /// Finds a table entry in this lookup table. + virtual TalkgroupId find(uint32_t id); + + /// Flag indicating whether talkgroup ID access control is enabled or not. + bool getACL(); + + private: + bool m_acl; + + /// Parses a table entry from the passed comma delimited string. + virtual TalkgroupId parse(std::string tableEntry); + }; +} // namespace lookups + +#endif // __TALKGROUP_ID_LOOKUP_H__ diff --git a/modem/Modem.cpp b/modem/Modem.cpp new file mode 100644 index 00000000..7b99d71d --- /dev/null +++ b/modem/Modem.cpp @@ -0,0 +1,1323 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2011-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "p25/P25Defines.h" +#include "modem/Modem.h" +#include "modem/NullModem.h" +#include "Log.h" +#include "Thread.h" +#include "Utils.h" + +using namespace modem; + +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Modem class. +/// +/// Serial port the modem DSP is connected to. +/// Flag indicating the modem is operating in duplex mode. +/// Flag indicating the Rx polarity should be inverted. +/// Flag indicating the Tx polarity should be inverted. +/// Flag indicating the PTT polarity should be inverted. +/// Flag indicating whether the DSP DC-level blocking should be enabled. +/// Flag indicating whether the COS signal should be used to lockout the modem. +/// Compensation for transmitter to settle in ms. +/// Compensate for delay in transmitter audio chain in ms. Usually DSP based. +/// Flag indicating whether the ADC/DAC overflow reset logic is disabled. +/// Flag indicating whether modem DSP trace is enabled. +/// Flag indicating whether modem DSP debug is enabled. +Modem::Modem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug) : + m_port(port), + m_dmrColorCode(0U), + m_duplex(duplex), + m_rxInvert(rxInvert), + m_txInvert(txInvert), + m_pttInvert(pttInvert), + m_dcBlocker(dcBlocker), + m_cosLockout(cosLockout), + m_txDelay(txDelay), + m_dmrDelay(dmrDelay), + m_rxLevel(0U), + m_cwIdTXLevel(0U), + m_dmrTXLevel(0U), + m_p25TXLevel(0U), + m_disableOFlowReset(disableOFlowReset), + m_trace(trace), + m_debug(debug), + m_dmrEnabled(false), + m_p25Enabled(false), + m_rxDCOffset(0), + m_txDCOffset(0), + m_adcOverFlowCount(0U), + m_dacOverFlowCount(0U), + m_serial(port, SERIAL_115200, true), + m_buffer(NULL), + m_length(0U), + m_offset(0U), + m_rxDMRData1(1000U, "Modem RX DMR1"), + m_rxDMRData2(1000U, "Modem RX DMR2"), + m_txDMRData1(1000U, "Modem TX DMR1"), + m_txDMRData2(1000U, "Modem TX DMR2"), + m_rxP25Data(1000U, "Modem RX P25"), + m_txP25Data(1000U, "Modem TX P25"), + m_statusTimer(1000U, 0U, 250U), + m_inactivityTimer(1000U, 4U), + m_playoutTimer(1000U, 0U, 10U), + m_dmrSpace1(0U), + m_dmrSpace2(0U), + m_p25Space(0U), + m_tx(false), + m_cd(false), + m_lockout(false), + m_error(false) +{ + assert(!port.empty()); + + m_buffer = new uint8_t[BUFFER_LENGTH]; +} + +/// +/// Finalizes a instance of the Modem class. +/// +Modem::~Modem() +{ + delete[] m_buffer; +} + +/// +/// Sets the modem DSP RF DC offset parameters. +/// +/// +/// +void Modem::setDCOffsetParams(int txDCOffset, int rxDCOffset) +{ + m_txDCOffset = txDCOffset; + m_rxDCOffset = rxDCOffset; +} + +/// +/// Sets the modem DSP enabled modes. +/// +/// +/// +void Modem::setModeParams(bool dmrEnabled, bool p25Enabled) +{ + m_dmrEnabled = dmrEnabled; + m_p25Enabled = p25Enabled; +} + +/// +/// Sets the modem DSP RF deviation levels. +/// +/// +/// +/// +/// +void Modem::setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel) +{ + m_rxLevel = rxLevel; + m_cwIdTXLevel = cwIdTXLevel; + m_dmrTXLevel = dmrTXLevel; + m_p25TXLevel = p25TXLevel; +} + +/// +/// Sets the modem DSP DMR color code. +/// +/// +void Modem::setDMRParams(uint32_t colorCode) +{ + assert(colorCode < 16U); + + m_dmrColorCode = colorCode; +} + +/// +/// Sets the modem DSP RF receive deviation levels. +/// +/// +void Modem::setRXLevel(float rxLevel) +{ + m_rxLevel = rxLevel; + + uint8_t buffer[4U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 16U; + buffer[2U] = CMD_SET_RXLEVEL; + + buffer[3U] = (uint8_t)(m_rxLevel * 2.55F + 0.5F); + + // Utils::dump(1U, "Written", buffer, 16U); + + int ret = m_serial.write(buffer, 16U); + if (ret != 16) + return; + + uint32_t count = 0U; + RESP_TYPE_DVM resp; + do { + Thread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError(LOG_MODEM, "No response, SET_RXLEVEL command"); + return; + } + } + } while (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK); + + // Utils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { + LogError(LOG_MODEM, "NAK to the SET_RXLEVEL command from the modem"); + } +} + +/// +/// Opens connection to the modem DSP. +/// +/// True, if connection to modem is established, otherwise false. +bool Modem::open() +{ + LogMessage(LOG_MODEM, "Initializing HW modem"); + + bool ret = m_serial.open(); + if (!ret) + return false; + + ret = getFirmwareVersion(); + if (!ret) { + m_serial.close(); + return false; + } + else { + // Stopping the inactivity timer here when a firmware version has been + // successfuly read prevents the death spiral of "no reply from modem..." + m_inactivityTimer.stop(); + } + + ret = writeConfig(); + if (!ret) { + LogError(LOG_MODEM, "Modem is unresponsive"); + m_serial.close(); + return false; + } + + m_statusTimer.start(); + + m_error = false; + m_offset = 0U; + + LogMessage(LOG_MODEM, "Modem Ready"); + return true; +} + +/// +/// Updates the timer by the passed number of milliseconds. +/// +/// +void Modem::clock(uint32_t ms) +{ + // poll the modem status every 250ms + m_statusTimer.clock(ms); + if (m_statusTimer.hasExpired()) { + getStatus(); + m_statusTimer.start(); + } + + m_inactivityTimer.clock(ms); + if (m_inactivityTimer.hasExpired()) { + LogError(LOG_MODEM, "No reply from the modem for some time, resetting it"); + + m_error = true; + m_adcOverFlowCount = 0U; + m_dacOverFlowCount = 0U; + + close(); + + Thread::sleep(2000U); // 2s + while (!open()) + Thread::sleep(5000U); // 5s + } + + bool forceModemReset = false; + RESP_TYPE_DVM type = getResponse(); + + if (type == RTM_TIMEOUT) { + // Nothing to do + } + else if (type == RTM_ERROR) { + // Nothing to do + } + else { + // type == RTM_OK + switch (m_buffer[2U]) { + /** Digital Mobile Radio */ + case CMD_DMR_DATA1: + { + if (m_trace) + Utils::dump(1U, "RX DMR Data 1", m_buffer, m_length); + + uint8_t data = m_length - 2U; + m_rxDMRData1.addData(&data, 1U); + + if (m_buffer[3U] == (dmr::DMR_SYNC_DATA | dmr::DT_TERMINATOR_WITH_LC)) + data = TAG_EOT; + else + data = TAG_DATA; + m_rxDMRData1.addData(&data, 1U); + + m_rxDMRData1.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case CMD_DMR_DATA2: + { + if (m_trace) + Utils::dump(1U, "RX DMR Data 2", m_buffer, m_length); + + uint8_t data = m_length - 2U; + m_rxDMRData2.addData(&data, 1U); + + if (m_buffer[3U] == (dmr::DMR_SYNC_DATA | dmr::DT_TERMINATOR_WITH_LC)) + data = TAG_EOT; + else + data = TAG_DATA; + m_rxDMRData2.addData(&data, 1U); + + m_rxDMRData2.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case CMD_DMR_LOST1: + { + if (m_trace) + Utils::dump(1U, "RX DMR Lost 1", m_buffer, m_length); + + uint8_t data = 1U; + m_rxDMRData1.addData(&data, 1U); + + data = TAG_LOST; + m_rxDMRData1.addData(&data, 1U); + } + break; + + case CMD_DMR_LOST2: + { + if (m_trace) + Utils::dump(1U, "RX DMR Lost 2", m_buffer, m_length); + + uint8_t data = 1U; + m_rxDMRData2.addData(&data, 1U); + + data = TAG_LOST; + m_rxDMRData2.addData(&data, 1U); + } + break; + + /** Project 25 */ + case CMD_P25_DATA: + { + if (m_trace) + Utils::dump(1U, "RX P25 Data", m_buffer, m_length); + + uint8_t data = m_length - 2U; + m_rxP25Data.addData(&data, 1U); + + data = TAG_DATA; + m_rxP25Data.addData(&data, 1U); + + m_rxP25Data.addData(m_buffer + 3U, m_length - 3U); + } + break; + + case CMD_P25_LOST: + { + if (m_trace) + Utils::dump(1U, "RX P25 Lost", m_buffer, m_length); + + uint8_t data = 1U; + m_rxP25Data.addData(&data, 1U); + + data = TAG_LOST; + m_rxP25Data.addData(&data, 1U); + } + break; + + /** General */ + case CMD_GET_STATUS: + { + // if (m_trace) + // Utils::dump(1U, "Get Status", m_buffer, m_length); + + m_tx = (m_buffer[5U] & 0x01U) == 0x01U; + + bool adcOverflow = (m_buffer[5U] & 0x02U) == 0x02U; + if (adcOverflow) { + //LogError(LOG_MODEM, "ADC levels have overflowed"); + m_adcOverFlowCount++; + + if (m_adcOverFlowCount >= MAX_ADC_OVERFLOW / 2U) { + LogWarning(LOG_MODEM, "ADC overflow count > %u!", MAX_ADC_OVERFLOW / 2U); + } + + if (!m_disableOFlowReset) { + if (m_adcOverFlowCount > MAX_ADC_OVERFLOW) { + LogError(LOG_MODEM, "ADC overflow count > %u, resetting modem", MAX_ADC_OVERFLOW); + forceModemReset = true; + } + } else { + m_adcOverFlowCount = 0U; + } + } + else { + if (m_adcOverFlowCount != 0U) { + m_adcOverFlowCount--; + } + } + + bool rxOverflow = (m_buffer[5U] & 0x04U) == 0x04U; + if (rxOverflow) + LogError(LOG_MODEM, "RX buffer has overflowed"); + + bool txOverflow = (m_buffer[5U] & 0x08U) == 0x08U; + if (txOverflow) + LogError(LOG_MODEM, "TX buffer has overflowed"); + + m_lockout = (m_buffer[5U] & 0x10U) == 0x10U; + + bool dacOverflow = (m_buffer[5U] & 0x20U) == 0x20U; + if (dacOverflow) { + //LogError(LOG_MODEM, "DAC levels have overflowed"); + m_dacOverFlowCount++; + + if (m_dacOverFlowCount > MAX_DAC_OVERFLOW / 2U) { + LogWarning(LOG_MODEM, "DAC overflow count > %u!", MAX_DAC_OVERFLOW / 2U); + } + + if (!m_disableOFlowReset) { + if (m_dacOverFlowCount > MAX_DAC_OVERFLOW) { + LogError(LOG_MODEM, "DAC overflow count > %u, resetting modem", MAX_DAC_OVERFLOW); + forceModemReset = true; + } + } else { + m_dacOverFlowCount = 0U; + } + } + else { + if (m_dacOverFlowCount != 0U) { + m_dacOverFlowCount--; + } + } + + m_cd = (m_buffer[5U] & 0x40U) == 0x40U; + + m_dmrSpace1 = m_buffer[7U]; + m_dmrSpace2 = m_buffer[8U]; + m_p25Space = m_buffer[10U]; + + m_inactivityTimer.start(); + } + break; + + case CMD_GET_VERSION: + case CMD_ACK: + break; + + case CMD_NAK: + LogWarning(LOG_MODEM, "NAK, command = 0x%02X, reason = %u", m_buffer[3U], m_buffer[4U]); + break; + + case CMD_DEBUG1: + case CMD_DEBUG2: + case CMD_DEBUG3: + case CMD_DEBUG4: + case CMD_DEBUG5: + printDebug(); + break; + + default: + LogWarning(LOG_MODEM, "Unknown message, type = %02X", m_buffer[2U]); + Utils::dump("Buffer dump", m_buffer, m_length); + break; + } + } + + // force a modem reset because of a error condition + if (forceModemReset) { + m_error = true; + forceModemReset = false; + m_adcOverFlowCount = 0U; + m_dacOverFlowCount = 0U; + + close(); + + Thread::sleep(2000U); // 2s + while (!open()) + Thread::sleep(5000U); // 5s + } + + // Only feed data to the modem if the playout timer has expired + m_playoutTimer.clock(ms); + if (!m_playoutTimer.hasExpired()) + return; + + if (m_dmrSpace1 > 1U && !m_txDMRData1.isEmpty()) { + uint8_t len = 0U; + m_txDMRData1.getData(&len, 1U); + m_txDMRData1.getData(m_buffer, len); + + if (m_trace) + Utils::dump(1U, "TX DMR Data 1", m_buffer, len); + + int ret = m_serial.write(m_buffer, len); + if (ret != int(len)) + LogError(LOG_MODEM, "Error writing DMR slot 1 data"); + + m_playoutTimer.start(); + + m_dmrSpace1--; + } + + if (m_dmrSpace2 > 1U && !m_txDMRData2.isEmpty()) { + uint8_t len = 0U; + m_txDMRData2.getData(&len, 1U); + m_txDMRData2.getData(m_buffer, len); + + if (m_trace) + Utils::dump(1U, "TX DMR Data 2", m_buffer, len); + + int ret = m_serial.write(m_buffer, len); + if (ret != int(len)) + LogError(LOG_MODEM, "Error writing DMR slot 2 data"); + + m_playoutTimer.start(); + + m_dmrSpace2--; + } + + if (m_p25Space > 1U && !m_txP25Data.isEmpty()) { + uint8_t len = 0U; + m_txP25Data.getData(&len, 1U); + m_txP25Data.getData(m_buffer, len); + + if (m_trace) { + Utils::dump(1U, "TX P25 Data", m_buffer, len); + } + + int ret = m_serial.write(m_buffer, len); + if (ret != int(len)) + LogError(LOG_MODEM, "Error writing P25 data"); + + m_playoutTimer.start(); + + m_p25Space--; + } +} + +/// +/// Closes connection to the modem DSP. +/// +void Modem::close() +{ + LogDebug(LOG_MODEM, "Closing the modem"); + m_serial.close(); +} + +/// +/// Reads DMR Slot 1 frame data from the DMR Slot 1 ring buffer. +/// +/// Buffer to write frame data to. +/// Length of data read from ring buffer. +uint32_t Modem::readDMRData1(uint8_t* data) +{ + assert(data != NULL); + + if (m_rxDMRData1.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_rxDMRData1.getData(&len, 1U); + m_rxDMRData1.getData(data, len); + + return len; +} + +/// +/// Reads DMR Slot 2 frame data from the DMR Slot 2 ring buffer. +/// +/// Buffer to write frame data to. +/// Length of data read from ring buffer. +uint32_t Modem::readDMRData2(uint8_t* data) +{ + assert(data != NULL); + + if (m_rxDMRData2.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_rxDMRData2.getData(&len, 1U); + m_rxDMRData2.getData(data, len); + + return len; +} + +/// +/// Reads P25 frame data from the P25 ring buffer. +/// +/// Buffer to write frame data to. +/// Length of data read from ring buffer. +uint32_t Modem::readP25Data(uint8_t* data) +{ + assert(data != NULL); + + if (m_rxP25Data.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_rxP25Data.getData(&len, 1U); + m_rxP25Data.getData(data, len); + + return len; +} + +/// +/// Helper to test if the DMR Slot 1 ring buffer has free space. +/// +/// True, if the DMR Slot 1 ring buffer has free space, otherwise false. +bool Modem::hasDMRSpace1() const +{ + uint32_t space = m_txDMRData1.freeSpace() / (dmr::DMR_FRAME_LENGTH_BYTES + 4U); + return space > 1U; +} + +/// +/// Helper to test if the DMR Slot 2 ring buffer has free space. +/// +/// True, if the DMR Slot 2 ring buffer has free space, otherwise false. +bool Modem::hasDMRSpace2() const +{ + uint32_t space = m_txDMRData2.freeSpace() / (dmr::DMR_FRAME_LENGTH_BYTES + 4U); + return space > 1U; +} + +/// +/// Helper to test if the P25 ring buffer has free space. +/// +/// True, if the P25 ring buffer has free space, otherwise false. +bool Modem::hasP25Space() const +{ + uint32_t space = m_txP25Data.freeSpace() / (p25::P25_LDU_FRAME_LENGTH_BYTES + 4U); + return space > 1U; +} + +/// +/// Flag indicating whether or not the modem DSP is transmitting. +/// +/// True, if modem DSP is transmitting, otherwise false. +bool Modem::hasTX() const +{ + return m_tx; +} + +/// +/// Flag indicating whether or not the modem DSP has carrier detect. +/// +/// True, if modem DSP has carrier detect, otherwise false. +bool Modem::hasCD() const +{ + return m_cd; +} + +/// +/// Flag indicating whether or not the modem DSP is currently locked out. +/// +/// True, if modem DSP is currently locked out, otherwise false. +bool Modem::hasLockout() const +{ + return m_lockout; +} + +/// +/// Flag indicating whether or not the modem DSP is currently in an error condition. +/// +/// True, if the modem DSP is current in an error condition, otherwise false. +bool Modem::hasError() const +{ + return m_error; +} + +/// +/// Clears any buffered DMR Slot 1 frame data to be sent to the modem DSP. +/// +void Modem::clearDMRData1() +{ + if (!m_txDMRData1.isEmpty()) { + m_txDMRData1.clear(); + } +} + +/// +/// Clears any buffered DMR Slot 2 frame data to be sent to the modem DSP. +/// +void Modem::clearDMRData2() +{ + if (!m_txDMRData2.isEmpty()) { + m_txDMRData2.clear(); + } +} + +/// +/// Clears any buffered P25 frame data to be sent to the modem DSP. +/// +void Modem::clearP25Data() +{ + if (!m_txP25Data.isEmpty()) { + m_txP25Data.clear(); + + uint8_t buffer[3U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = CMD_P25_CLEAR; + + // Utils::dump(1U, "Written", buffer, 3U); + + m_serial.write(buffer, 3U); + } +} + +/// +/// Internal helper to inject DMR Slot 1 frame data as if it came from the modem DSP. +/// +/// Data to write to ring buffer. +/// Length of data to write. +void Modem::injectDMRData1(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "Injected DMR Slot 1 Data", data, length); + + uint8_t val = length; + m_rxDMRData1.addData(&val, 1U); + + val = TAG_DATA; + m_rxDMRData1.addData(&val, 1U); + val = dmr::DMR_SYNC_VOICE & dmr::DMR_SYNC_DATA; // valid sync + m_rxDMRData1.addData(&val, 1U); + + m_rxDMRData1.addData(data, length); +} + +/// +/// Internal helper to inject DMR Slot 2 frame data as if it came from the modem DSP. +/// +/// Data to write to ring buffer. +/// Length of data to write. +void Modem::injectDMRData2(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "Injected DMR Slot 2 Data", data, length); + + uint8_t val = length; + m_rxDMRData2.addData(&val, 1U); + + val = TAG_DATA; + m_rxDMRData2.addData(&val, 1U); + val = dmr::DMR_SYNC_VOICE & dmr::DMR_SYNC_DATA; // valid sync + m_rxDMRData2.addData(&val, 1U); + + m_rxDMRData2.addData(data, length); +} + +/// +/// Internal helper to inject P25 frame data as if it came from the modem DSP. +/// +/// Data to write to ring buffer. +/// Length of data to write. +void Modem::injectP25Data(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (m_trace) + Utils::dump(1U, "Injected P25 Data", data, length); + + uint8_t val = length; + m_rxP25Data.addData(&val, 1U); + + val = TAG_DATA; + m_rxP25Data.addData(&val, 1U); + val = 0x01U; // valid sync + m_rxP25Data.addData(&val, 1U); + + m_rxP25Data.addData(data, length); +} + +/// +/// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. +/// +/// Data to write to ring buffer. +/// Length of data to write. +/// True, if data is written, otherwise false. +bool Modem::writeDMRData1(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + + uint8_t buffer[40U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = CMD_DMR_DATA1; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + uint8_t len = length + 2U; + m_txDMRData1.addData(&len, 1U); + m_txDMRData1.addData(buffer, len); + + return true; +} + +/// +/// Writes DMR Slot 2 frame data to the DMR Slot 2 ring buffer. +/// +/// Data to write to ring buffer. +/// Length of data to write. +/// True, if data is written, otherwise false. +bool Modem::writeDMRData2(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + + uint8_t buffer[40U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = CMD_DMR_DATA2; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + uint8_t len = length + 2U; + m_txDMRData2.addData(&len, 1U); + m_txDMRData2.addData(buffer, len); + + return true; +} + +/// +/// Writes P25 frame data to the P25 ring buffer. +/// +/// Data to write to ring buffer. +/// Length of data to write. +/// True, if data is written, otherwise false. +bool Modem::writeP25Data(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + if (data[0U] != TAG_DATA && data[0U] != TAG_EOT) + return false; + + uint8_t buffer[250U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = length + 2U; + buffer[2U] = CMD_P25_DATA; + + ::memcpy(buffer + 3U, data + 1U, length - 1U); + + uint8_t len = length + 2U; + m_txP25Data.addData(&len, 1U); + m_txP25Data.addData(buffer, len); + + return true; +} + +/// +/// Triggers the start of DMR transmit. +/// +/// +/// True, if DMR transmit started, otherwise false. +bool Modem::writeDMRStart(bool tx) +{ + if (tx && m_tx) + return true; + if (!tx && !m_tx) + return true; + + uint8_t buffer[4U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_DMR_START; + buffer[3U] = tx ? 0x01U : 0x00U; + + // Utils::dump(1U, "Written", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +/// +/// Writes a DMR short LC to the modem DSP. +/// +/// +/// True, if DMR LC is written, otherwise false. +bool Modem::writeDMRShortLC(const uint8_t* lc) +{ + assert(lc != NULL); + + uint8_t buffer[12U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 12U; + buffer[2U] = CMD_DMR_SHORTLC; + buffer[3U] = lc[0U]; + buffer[4U] = lc[1U]; + buffer[5U] = lc[2U]; + buffer[6U] = lc[3U]; + buffer[7U] = lc[4U]; + buffer[8U] = lc[5U]; + buffer[9U] = lc[6U]; + buffer[10U] = lc[7U]; + buffer[11U] = lc[8U]; + + // Utils::dump(1U, "Written", buffer, 12U); + + return m_serial.write(buffer, 12U) == 12; +} + +/// +/// Writes a DMR abort message for the given slot to the modem DSP. +/// +/// DMR slot to write abort for. +/// True, if DMR abort is written, otherwise false. +bool Modem::writeDMRAbort(uint32_t slotNo) +{ + if (slotNo == 1U) + m_txDMRData1.clear(); + else + m_txDMRData2.clear(); + + uint8_t buffer[4U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_DMR_ABORT; + buffer[3U] = slotNo; + + // Utils::dump(1U, "Written", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +/// +/// Sets the current operating mode for the modem DSP. +/// +/// +/// +bool Modem::setMode(DVM_STATE state) +{ + uint8_t buffer[4U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 4U; + buffer[2U] = CMD_SET_MODE; + buffer[3U] = state; + + // Utils::dump(1U, "Written", buffer, 4U); + + return m_serial.write(buffer, 4U) == 4; +} + +/// +/// Transmits the given string as CW morse. +/// +/// +/// +bool Modem::sendCWId(const std::string& callsign) +{ + LogDebug(LOG_MODEM, "sending CW ID"); + + uint32_t length = (uint32_t)callsign.length(); + if (length > 200U) + length = 200U; + + uint8_t buffer[205U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = length + 3U; + buffer[2U] = CMD_SEND_CWID; + + for (uint32_t i = 0U; i < length; i++) + buffer[i + 3U] = callsign.at(i); + + if (m_trace) { + Utils::dump(1U, "CW ID Data", buffer, length + 3U); + } + + return m_serial.write(buffer, length + 3U) == int(length + 3U); +} + +/// +/// Helper to create an instance of the Modem class. +/// +/// Serial port the modem DSP is connected to. +/// Flag indicating the modem is operating in duplex mode. +/// Flag indicating the Rx polarity should be inverted. +/// Flag indicating the Tx polarity should be inverted. +/// Flag indicating the PTT polarity should be inverted. +/// Flag indicating whether the DSP DC-level blocking should be enabled. +/// Flag indicating whether the COS signal should be used to lockout the modem. +/// Amount of time to delay before transmitting frames. +/// +/// Flag indicating whether the ADC/DAC overflow reset logic is disabled. +/// Flag indicating whether modem DSP trace is enabled. +/// Flag indicating whether modem DSP debug is enabled. +Modem* Modem::createModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug) +{ + if (port == NULL_MODEM) { + return new NullModem(port, duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, txDelay, dmrDelay, disableOFlowReset, trace, debug); + } + else { + return new Modem(port, duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, txDelay, dmrDelay, disableOFlowReset, trace, debug); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Retrieve the modem DSP version. +/// +/// +bool Modem::getFirmwareVersion() +{ + Thread::sleep(2000U); // 2s + + for (uint32_t i = 0U; i < 6U; i++) { + uint8_t buffer[3U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = CMD_GET_VERSION; + + // Utils::dump(1U, "Written", buffer, 3U); + + int ret = m_serial.write(buffer, 3U); + if (ret != 3) + return false; + + for (uint32_t count = 0U; count < MAX_RESPONSES; count++) { + Thread::sleep(10U); + RESP_TYPE_DVM resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] == CMD_GET_VERSION) { + LogMessage(LOG_MODEM, MODEM_VERSION_STR, m_length - 4U, m_buffer + 4U, m_buffer[3U]); + return true; + } + } + + Thread::sleep(1500U); + } + + LogError(LOG_MODEM, "Unable to read the firmware version after 6 attempts"); + + return false; +} + +/// +/// Retrieve the current status from the modem DSP. +/// +/// +bool Modem::getStatus() +{ + uint8_t buffer[3U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = CMD_GET_STATUS; + + // Utils::dump(1U, "Written", buffer, 3U); + + return m_serial.write(buffer, 3U) == 3; +} + +/// +/// Write configuration to the modem DSP. +/// +/// +bool Modem::writeConfig() +{ + uint8_t buffer[20U]; + + buffer[0U] = DVM_FRAME_START; + buffer[1U] = 16U; + buffer[2U] = CMD_SET_CONFIG; + + buffer[3U] = 0x00U; + if (m_rxInvert) + buffer[3U] |= 0x01U; + if (m_txInvert) + buffer[3U] |= 0x02U; + if (m_pttInvert) + buffer[3U] |= 0x04U; + if (m_debug) + buffer[3U] |= 0x10U; + if (!m_duplex) + buffer[3U] |= 0x80U; + + buffer[4U] = 0x00U; + if (m_dcBlocker) + buffer[4U] |= 0x01U; + if (m_cosLockout) + buffer[4U] |= 0x04U; + + if (m_dmrEnabled) + buffer[4U] |= 0x02U; + if (m_p25Enabled) + buffer[4U] |= 0x08U; + + buffer[5U] = m_txDelay / 10U; // In 10ms units + + buffer[6U] = STATE_IDLE; + + buffer[7U] = (uint8_t)(m_rxLevel * 2.55F + 0.5F); + + buffer[8U] = (uint8_t)(m_cwIdTXLevel * 2.55F + 0.5F); + + buffer[9U] = m_dmrColorCode; + + buffer[10U] = m_dmrDelay; + + buffer[11U] = 128U; // Was OscOffset + + buffer[13U] = (uint8_t)(m_dmrTXLevel * 2.55F + 0.5F); + buffer[15U] = (uint8_t)(m_p25TXLevel * 2.55F + 0.5F); + + buffer[16U] = (uint8_t)(m_txDCOffset + 128); + buffer[17U] = (uint8_t)(m_rxDCOffset + 128); + + // Utils::dump(1U, "Written", buffer, 16U); + + int ret = m_serial.write(buffer, 16U); + if (ret != 16) + return false; + + uint32_t count = 0U; + RESP_TYPE_DVM resp; + do { + Thread::sleep(10U); + + resp = getResponse(); + if (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK) { + count++; + if (count >= MAX_RESPONSES) { + LogError(LOG_MODEM, "No response, SET_CONFIG command"); + return false; + } + } + } while (resp == RTM_OK && m_buffer[2U] != CMD_ACK && m_buffer[2U] != CMD_NAK); + + // Utils::dump(1U, "Response", m_buffer, m_length); + + if (resp == RTM_OK && m_buffer[2U] == CMD_NAK) { + LogError(LOG_MODEM, "NAK to the SET_CONFIG command from the modem"); + return false; + } + + m_playoutTimer.start(); + + return true; +} + +/// +/// +/// +void Modem::printDebug() +{ + if (m_buffer[2U] == CMD_DEBUG1) { + LogMessage(LOG_MODEM, "M: %.*s", m_length - 3U, m_buffer + 3U); + } + else if (m_buffer[2U] == CMD_DEBUG2) { + short val1 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d", m_length - 5U, m_buffer + 3U, val1); + } + else if (m_buffer[2U] == CMD_DEBUG3) { + short val1 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val2 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d", m_length - 7U, m_buffer + 3U, val1, val2); + } + else if (m_buffer[2U] == CMD_DEBUG4) { + short val1 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; + short val2 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val3 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d %d", m_length - 9U, m_buffer + 3U, val1, val2, val3); + } + else if (m_buffer[2U] == CMD_DEBUG5) { + short val1 = (m_buffer[m_length - 8U] << 8) | m_buffer[m_length - 7U]; + short val2 = (m_buffer[m_length - 6U] << 8) | m_buffer[m_length - 5U]; + short val3 = (m_buffer[m_length - 4U] << 8) | m_buffer[m_length - 3U]; + short val4 = (m_buffer[m_length - 2U] << 8) | m_buffer[m_length - 1U]; + LogMessage(LOG_MODEM, "M: %.*s %d %d %d %d", m_length - 11U, m_buffer + 3U, val1, val2, val3, val4); + } +} + +/// +/// +/// +/// +RESP_TYPE_DVM Modem::getResponse() +{ + if (m_offset == 0U) { + // Get the start of the frame or nothing at all + int ret = m_serial.read(m_buffer + 0U, 1U); + if (ret < 0) { + LogError(LOG_MODEM, "Error reading from the modem, ret = %d", ret); + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (m_buffer[0U] != DVM_FRAME_START) + return RTM_TIMEOUT; + + m_offset = 1U; + } + + if (m_offset == 1U) { + // Get the length of the frame + int ret = m_serial.read(m_buffer + 1U, 1U); + if (ret < 0) { + LogError(LOG_MODEM, "Error reading from the modem, ret = %d", ret); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (m_buffer[1U] >= 250U) { + LogError(LOG_MODEM, "Invalid length received from the modem, len = %u", m_buffer[1U]); + m_offset = 0U; + return RTM_ERROR; + } + + m_length = m_buffer[1U]; + m_offset = 2U; + } + + if (m_offset == 2U) { + // Get the frame type + int ret = m_serial.read(m_buffer + 2U, 1U); + if (ret < 0) { + LogError(LOG_MODEM, "Error reading from the modem, ret = %d", ret); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + m_offset = 3U; + } + + if (m_offset >= 3U) { + // Use later two byte length field + if (m_length == 0U) { + int ret = m_serial.read(m_buffer + 3U, 2U); + if (ret < 0) { + LogError(LOG_MODEM, "Error reading from the modem, ret = %d", ret); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + m_length = (m_buffer[3U] << 8) | m_buffer[4U]; + m_offset = 5U; + } + + while (m_offset < m_length) { + int ret = m_serial.read(m_buffer + m_offset, m_length - m_offset); + if (ret < 0) { + LogError(LOG_MODEM, "Error reading from the modem, ret = %d", ret); + m_offset = 0U; + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (ret > 0) + m_offset += ret; + } + } + + m_offset = 0U; + + // Utils::dump(1U, "Received", m_buffer, m_length); + + return RTM_OK; +} diff --git a/modem/Modem.h b/modem/Modem.h new file mode 100644 index 00000000..bfeecfc6 --- /dev/null +++ b/modem/Modem.h @@ -0,0 +1,323 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2011-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__MODEM_H__) +#define __MODEM_H__ + +#include "Defines.h" +#include "modem/SerialController.h" +#include "RingBuffer.h" +#include "Timer.h" + +#include + +#define MODEM_VERSION_STR "%.*s, Modem protocol: %u" +#define NULL_MODEM "null" + +namespace modem +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum RESP_TYPE_DVM { + RTM_OK, + RTM_TIMEOUT, + RTM_ERROR + }; + + enum DVM_STATE { + STATE_IDLE = 0U, + // DMR + STATE_DMR = 1U, + // Project 25 + STATE_P25 = 2U, + + // CW + STATE_CW = 10U, + + // Symbol Tests + STATE_DMR_LEVELA = 70U, + STATE_DMR_LEVELB = 71U, + STATE_DMR_LEVELC = 72U, + STATE_DMR_LEVELD = 73U, + STATE_P25_LEVELA = 74U, + STATE_P25_LEVELB = 75U, + STATE_P25_LEVELC = 77U, + STATE_P25_LEVELD = 78U, + + // Calibration States + STATE_P25_CAL_1K = 92U, + + STATE_DMR_DMO_CAL_1K = 93U, + STATE_DMR_CAL_1K = 94U, + + STATE_LF_CAL = 95U, + + STATE_RSSI_CAL = 96U, + + STATE_P25_CAL = 97U, + STATE_DMR_CAL = 98U + }; + + enum DVM_COMMANDS { + CMD_GET_VERSION = 0x00U, + CMD_GET_STATUS = 0x01U, + CMD_SET_CONFIG = 0x02U, + CMD_SET_MODE = 0x03U, + + CMD_SET_RXLEVEL = 0x05U, + + CMD_CAL_DATA = 0x08U, + CMD_RSSI_DATA = 0x09U, + + CMD_SEND_CWID = 0x0AU, + + CMD_DMR_DATA1 = 0x18U, + CMD_DMR_LOST1 = 0x19U, + CMD_DMR_DATA2 = 0x1AU, + CMD_DMR_LOST2 = 0x1BU, + CMD_DMR_SHORTLC = 0x1CU, + CMD_DMR_START = 0x1DU, + CMD_DMR_ABORT = 0x1EU, + + CMD_P25_DATA = 0x31U, + CMD_P25_LOST = 0x32U, + CMD_P25_CLEAR = 0x33U, + + CMD_ACK = 0x70U, + CMD_NAK = 0x7FU, + + CMD_SERIAL = 0x80U, + + CMD_DEBUG1 = 0xF1U, + CMD_DEBUG2 = 0xF2U, + CMD_DEBUG3 = 0xF3U, + CMD_DEBUG4 = 0xF4U, + CMD_DEBUG5 = 0xF5U, + }; + + enum CMD_REASON_CODE { + RSN_OK = 0U, + RSN_NAK = 1U, + + RSN_ILLEGAL_LENGTH = 2U, + RSN_INVALID_REQUEST = 4U, + RSN_RINGBUFF_FULL = 8U, + + RSN_INVALID_TXDELAY = 10U, + RSN_INVALID_MODE = 11U, + + RSN_INVALID_DMR_CC = 12U, + RSN_INVALID_DMR_SLOT = 13U, + RSN_INVALID_DMR_START = 14U, + + RSN_DMR_DISABLED = 63U, + RSN_P25_DISABLED = 64U, + }; + + const uint8_t DVM_FRAME_START = 0xFEU; + + const uint32_t MAX_RESPONSES = 30U; + const uint32_t BUFFER_LENGTH = 2000U; + + const uint32_t MAX_ADC_OVERFLOW = 128U; + const uint32_t MAX_DAC_OVERFLOW = 128U; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the core interface to the modem hardware. + // --------------------------------------------------------------------------- + + class HOST_SW_API Modem { + public: + /// Initializes a new instance of the Modem class. + Modem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug); + /// Finalizes a instance of the Modem class. + virtual ~Modem(); + + /// Sets the modem DSP RF DC offset parameters. + virtual void setDCOffsetParams(int txDCOffset, int rxDCOffset); + /// Sets the modem DSP enabled modes. + virtual void setModeParams(bool dmrEnabled, bool p25Enabled); + /// Sets the modem DSP RF deviation levels. + virtual void setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel); + /// Sets the modem DSP DMR color code. + virtual void setDMRParams(uint32_t colorCode); + /// Sets the modem DSP RF receive deviation levels. + virtual void setRXLevel(float rxLevel); + + /// Opens connection to the modem DSP. + virtual bool open(); + + /// Updates the modem by the passed number of milliseconds. + virtual void clock(uint32_t ms); + + /// Closes connection to the modem DSP. + virtual void close(); + + /// Reads DMR Slot 1 frame data from the DMR Slot 1 ring buffer. + virtual uint32_t readDMRData1(uint8_t* data); + /// Reads DMR Slot 2 frame data from the DMR Slot 1 ring buffer. + virtual uint32_t readDMRData2(uint8_t* data); + /// Reads P25 frame data from the P25 ring buffer. + virtual uint32_t readP25Data(uint8_t* data); + + /// Helper to test if the DMR Slot 1 ring buffer has free space. + virtual bool hasDMRSpace1() const; + /// Helper to test if the DMR Slot 2 ring buffer has free space. + virtual bool hasDMRSpace2() const; + /// Helper to test if the P25 ring buffer has free space. + virtual bool hasP25Space() const; + + /// Flag indicating whether or not the modem DSP is transmitting. + virtual bool hasTX() const; + /// Flag indicating whether or not the modem DSP has carrier detect. + virtual bool hasCD() const; + + /// Flag indicating whether or not the modem DSP is currently locked out. + virtual bool hasLockout() const; + /// Flag indicating whether or not the modem DSP is currently in an error condition. + virtual bool hasError() const; + + /// Clears any buffered DMR Slot 1 frame data to be sent to the modem DSP. + void clearDMRData1(); + /// Clears any buffered DMR Slot 2 frame data to be sent to the modem DSP. + void clearDMRData2(); + /// Clears any buffered P25 frame data to be sent to the modem DSP. + void clearP25Data(); + + /// Internal helper to inject DMR Slot 1 frame data as if it came from the modem DSP. + void injectDMRData1(const uint8_t* data, uint32_t length); + /// Internal helper to inject DMR Slot 2 frame data as if it came from the modem DSP. + void injectDMRData2(const uint8_t* data, uint32_t length); + /// Internal helper to inject P25 frame data as if it came from the modem DSP. + void injectP25Data(const uint8_t* data, uint32_t length); + + /// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. + virtual bool writeDMRData1(const uint8_t* data, uint32_t length); + /// Writes DMR Slot 2 frame data to the DMR Slot 2 ring buffer. + virtual bool writeDMRData2(const uint8_t* data, uint32_t length); + /// Writes P25 frame data to the P25 ring buffer. + virtual bool writeP25Data(const uint8_t* data, uint32_t length); + + /// Triggers the start of DMR transmit. + virtual bool writeDMRStart(bool tx); + /// Writes a DMR short LC to the modem DSP. + virtual bool writeDMRShortLC(const uint8_t* lc); + /// Writes a DMR abort message for the given slot to the modem DSP. + virtual bool writeDMRAbort(uint32_t slotNo); + + /// Sets the current operating mode for the modem DSP. + virtual bool setMode(DVM_STATE state); + + /// Transmits the given string as CW morse. + virtual bool sendCWId(const std::string& callsign); + + /// Helper to create an instance of the Modem class. + static Modem* createModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug); + + private: + std::string m_port; + + uint32_t m_dmrColorCode; + + bool m_duplex; + + bool m_rxInvert; + bool m_txInvert; + bool m_pttInvert; + + bool m_dcBlocker; + bool m_cosLockout; + + uint32_t m_txDelay; + uint32_t m_dmrDelay; + + float m_rxLevel; + float m_cwIdTXLevel; + float m_dmrTXLevel; + float m_p25TXLevel; + + bool m_disableOFlowReset; + bool m_trace; + bool m_debug; + + bool m_dmrEnabled; + bool m_p25Enabled; + int m_rxDCOffset; + int m_txDCOffset; + + uint32_t m_adcOverFlowCount; + uint32_t m_dacOverFlowCount; + + CSerialController m_serial; + uint8_t* m_buffer; + uint32_t m_length; + uint32_t m_offset; + + RingBuffer m_rxDMRData1; + RingBuffer m_rxDMRData2; + RingBuffer m_txDMRData1; + RingBuffer m_txDMRData2; + RingBuffer m_rxP25Data; + RingBuffer m_txP25Data; + + Timer m_statusTimer; + Timer m_inactivityTimer; + Timer m_playoutTimer; + + uint32_t m_dmrSpace1; + uint32_t m_dmrSpace2; + uint32_t m_p25Space; + + bool m_tx; + bool m_cd; + bool m_lockout; + bool m_error; + + /// Retrieve the modem DSP version. + bool getFirmwareVersion(); + /// Retrieve the current status from the modem DSP. + bool getStatus(); + /// Write configuration to the modem DSP. + bool writeConfig(); + + /// + void printDebug(); + + /// + RESP_TYPE_DVM getResponse(); + }; +} // namespace modem + +#endif // __MODEM_H__ diff --git a/modem/NullModem.cpp b/modem/NullModem.cpp new file mode 100644 index 00000000..5104d438 --- /dev/null +++ b/modem/NullModem.cpp @@ -0,0 +1,78 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2011-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "modem/NullModem.h" +#include "Log.h" + +using namespace modem; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the NullModem class. +/// +/// Serial port the modem DSP is connected to. +/// Flag indicating the modem is operating in duplex mode. +/// Flag indicating the Rx polarity should be inverted. +/// Flag indicating the Tx polarity should be inverted. +/// Flag indicating the PTT polarity should be inverted. +/// Flag indicating whether the DSP DC-level blocking should be enabled. +/// Flag indicating whether the COS signal should be used to lockout the modem. +/// Compensation for transmitter to settle in ms. +/// Compensate for delay in transmitter audio chain in ms. Usually DSP based. +/// Flag indicating whether the ADC/DAC overflow reset logic is disabled. +/// Flag indicating whether modem DSP trace is enabled. +/// Flag indicating whether modem DSP debug is enabled. +NullModem::NullModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug) : + Modem(port, duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, txDelay, dmrDelay, disableOFlowReset, trace, debug) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the NullModem class. +/// +NullModem::~NullModem() +{ + /* stub */ +} + +/// +/// Opens connection to the modem DSP. +/// +/// True, if connection to modem is established, otherwise false. +bool NullModem::open() +{ + LogMessage(LOG_MODEM, "Initializing NULL modem"); + return true; +} diff --git a/modem/NullModem.h b/modem/NullModem.h new file mode 100644 index 00000000..421560a5 --- /dev/null +++ b/modem/NullModem.h @@ -0,0 +1,104 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2011-2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__NULL_MODEM_H__) +#define __NULL_MODEM_H__ + +#include "Defines.h" +#include "modem/Modem.h" + +namespace modem +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the interface to a null modem. + // --------------------------------------------------------------------------- + + class HOST_SW_API NullModem : public Modem { + public: + /// Initializes a new instance of the NullModem class. + NullModem(const std::string& port, bool duplex, bool rxInvert, bool txInvert, bool pttInvert, bool dcBlocker, + bool cosLockout, uint32_t txDelay, uint32_t dmrDelay, bool disableOFlowReset, bool trace, bool debug); + /// Finalizes a instance of the NullModem class. + ~NullModem(); + + /// Sets the modem DSP RF DC offset parameters. + virtual void setDCOffsetParams(int txDCOffset, int rxDCOffset) { return; } + /// Sets the modem DSP enabled modes. + virtual void setModeParams(bool dmrEnabled, bool p25Enabled) { return; } + /// Sets the modem DSP RF deviation levels. + virtual void setLevels(float rxLevel, float cwIdTXLevel, float dmrTXLevel, float p25TXLevel) { return; } + /// Sets the modem DSP DMR color code. + virtual void setDMRParams(uint32_t colorCode) { return; } + /// Sets the modem DSP RF receive deviation levels. + virtual void setRXLevel(float rxLevel) { return; } + + /// Opens connection to the modem DSP. + virtual bool open(); + + /// Updates the timer by the passed number of milliseconds. + virtual void clock(uint32_t ms) { return; } + + /// Closes connection to the modem DSP. + virtual void close() { return; } + + /// Flag indicating whether or not the modem DSP is transmitting. + virtual bool hasTX() const { return false; } + /// Flag indicating whether or not the modem DSP has carrier detect. + virtual bool hasCD() const { return false; } + + /// Flag indicating whether or not the modem DSP is currently locked out. + virtual bool hasLockout() const { return false; } + /// Flag indicating whether or not the modem DSP is currently in an error condition. + virtual bool hasError() const { return false; } + + /// Writes DMR Slot 1 frame data to the DMR Slot 1 ring buffer. + virtual bool writeDMRData1(const uint8_t* data, uint32_t length) { return true; } + /// Writes DMR Slot 2 frame data to the DMR Slot 2 ring buffer. + virtual bool writeDMRData2(const uint8_t* data, uint32_t length) { return true; } + /// Writes P25 frame data to the P25 ring buffer. + virtual bool writeP25Data(const uint8_t* data, uint32_t length) { return true; } + + /// Triggers the start of DMR transmit. + virtual bool writeDMRStart(bool tx) { return true; } + /// Writes a DMR short LC to the modem DSP. + virtual bool writeDMRShortLC(const uint8_t* lc) { return true; } + /// Writes a DMR abort message for the given slot to the modem DSP. + virtual bool writeDMRAbort(uint32_t slotNo) { return true; } + + /// Sets the current operating mode for the modem DSP. + virtual bool setMode(DVM_STATE state) { return true; } + + /// Transmits the given string as CW morse. + virtual bool sendCWId(const std::string& callsign) { return true; } + }; +} // namespace modem + +#endif // __NULL_MODEM_H__ diff --git a/modem/SerialController.cpp b/modem/SerialController.cpp new file mode 100644 index 00000000..7a12768a --- /dev/null +++ b/modem/SerialController.cpp @@ -0,0 +1,530 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2002-2004,2007-2011,2013,2014-2017 by Jonathan Naylor G4KLX +* Copyright (C) 1999-2001 by Thomas Sailor HB9JNX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "modem/SerialController.h" +#include "Log.h" + +using namespace modem; + +#include +#include + +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// Initializes a new instance of the CSerialController class. +/// +/// Serial port device. +/// Serial port speed. +/// +CSerialController::CSerialController() : + m_handle(INVALID_HANDLE_VALUE) +{ + /* stub */ +} + +/// +/// Initializes a new instance of the CSerialController class. +/// +/// Serial port device. +/// Serial port speed. +/// +CSerialController::CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : + m_device(device), + m_speed(speed), + m_assertRTS(assertRTS), + m_handle(INVALID_HANDLE_VALUE) +{ + assert(!device.empty()); +} + +/// +/// Finalizes a instance of the CSerialController class. +/// +CSerialController::~CSerialController() +{ + /* stub */ +} + +/// +/// Opens a connection to the serial port. +/// +/// True, if connection is opened, otherwise false. +bool CSerialController::open() +{ + assert(!m_device.empty()); + assert(m_handle == INVALID_HANDLE_VALUE); + + DWORD errCode; + + // Convert "\\.\COM10" to "COM10" + std::string baseName = m_device.substr(4U); + + m_handle = ::CreateFileA(m_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + LogError(LOG_MODEM, "Cannot open device - %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return false; + } + + DCB dcb; + if (::GetCommState(m_handle, &dcb) == 0) { + LogError(LOG_MODEM, "Cannot get the attributes for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + dcb.BaudRate = DWORD(m_speed); + 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_handle, &dcb) == 0) { + LogError(LOG_MODEM, "Cannot set the attributes for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + COMMTIMEOUTS timeouts; + if (!::GetCommTimeouts(m_handle, &timeouts)) { + LogError(LOG_MODEM, "Cannot get the timeouts for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0UL; + timeouts.ReadTotalTimeoutConstant = 0UL; + + if (!::SetCommTimeouts(m_handle, &timeouts)) { + LogError(LOG_MODEM, "Cannot set the timeouts for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, CLRDTR) == 0) { + LogError(LOG_MODEM, "Cannot clear DTR for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, m_assertRTS ? SETRTS : CLRRTS) == 0) { + LogError(LOG_MODEM, "Cannot set/clear RTS for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + ::ClearCommError(m_handle, &errCode, NULL); + + return true; +} + +/// +/// Reads data from the serial port. +/// +/// Buffer to read data from the serial port to. +/// Length of data to read from the serial port. +/// Actual length of data read from serial port. +int CSerialController::read(uint8_t* buffer, uint32_t length) +{ + assert(m_handle != INVALID_HANDLE_VALUE); + assert(buffer != NULL); + + uint32_t ptr = 0U; + + while (ptr < length) { + int ret = readNonblock(buffer + ptr, length - ptr); + if (ret < 0) { + return ret; + } + else if (ret == 0) { + if (ptr == 0U) + return 0; + } + else { + ptr += ret; + } + } + + return int(length); +} + +/// +/// Writes data to the serial port. +/// +/// Buffer containing data to write to serial port. +/// Length of data to write to serial port. +/// Actual length of data written to the serial port. +int CSerialController::write(const uint8_t* buffer, uint32_t length) +{ + assert(m_handle != INVALID_HANDLE_VALUE); + assert(buffer != NULL); + + if (length == 0U) + return 0; + + uint32_t ptr = 0U; + + while (ptr < length) { + DWORD bytes = 0UL; + BOOL ret = ::WriteFile(m_handle, buffer + ptr, length - ptr, &bytes, NULL); + if (!ret) { + LogError(LOG_MODEM, "Error from WriteFile for %s: %04lx", m_device.c_str(), ::GetLastError()); + return -1; + } + + ptr += bytes; + } + + return int(length); +} + +/// +/// Closes the connection to the serial port. +/// +void CSerialController::close() +{ + assert(m_handle != INVALID_HANDLE_VALUE); + + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; +} +#else +/// +/// Initializes a new instance of the CSerialController class. +/// +/// Serial port device. +/// Serial port speed. +/// +CSerialController::CSerialController() : + m_fd(-1) +{ + /* stub */ +} +/// +/// Initializes a new instance of the CSerialController class. +/// +/// Serial port device. +/// Serial port speed. +/// +CSerialController::CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : + m_device(device), + m_speed(speed), + m_assertRTS(assertRTS), + m_fd(-1) +{ + assert(!device.empty()); +} + +/// +/// Finalizes a instance of the CSerialController class. +/// +CSerialController::~CSerialController() +{ + /* stub */ +} + +/// +/// Opens a connection to the serial port. +/// +/// True, if connection is opened, otherwise false. +bool CSerialController::open() +{ + assert(!m_device.empty()); + assert(m_fd == -1); + + m_fd = ::open(m_device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + LogError(LOG_MODEM, "Cannot open device - %s", m_device.c_str()); + return false; + } + + if (::isatty(m_fd) == 0) { + LogError(LOG_MODEM, "%s is not a TTY device", m_device.c_str()); + ::close(m_fd); + return false; + } + + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + LogError(LOG_MODEM, "Cannot get the attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + termios.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= CS8; + termios.c_oflag &= ~(OPOST); + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + switch (m_speed) { + case SERIAL_1200: + ::cfsetospeed(&termios, B1200); + ::cfsetispeed(&termios, B1200); + break; + case SERIAL_2400: + ::cfsetospeed(&termios, B2400); + ::cfsetispeed(&termios, B2400); + break; + case SERIAL_4800: + ::cfsetospeed(&termios, B4800); + ::cfsetispeed(&termios, B4800); + break; + case SERIAL_9600: + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + break; + case SERIAL_19200: + ::cfsetospeed(&termios, B19200); + ::cfsetispeed(&termios, B19200); + break; + case SERIAL_38400: + ::cfsetospeed(&termios, B38400); + ::cfsetispeed(&termios, B38400); + break; + case SERIAL_115200: + ::cfsetospeed(&termios, B115200); + ::cfsetispeed(&termios, B115200); + break; + case SERIAL_230400: + ::cfsetospeed(&termios, B230400); + ::cfsetispeed(&termios, B230400); + break; + default: + LogError(LOG_MODEM, "Unsupported serial port speed - %d", int(m_speed)); + ::close(m_fd); + return false; + } + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + LogError(LOG_MODEM, "Cannot set the attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + if (m_assertRTS) { + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + LogError(LOG_MODEM, "Cannot get the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + LogError(LOG_MODEM, "Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + } + + return true; +} + +/// +/// Reads data from the serial port. +/// +/// Buffer to read data from the serial port to. +/// Length of data to read from the serial port. +/// Actual length of data read from serial port. +int CSerialController::read(uint8_t* buffer, uint32_t length) +{ + assert(buffer != NULL); + assert(m_fd != -1); + + if (length == 0U) + return 0; + + uint32_t offset = 0U; + + while (offset < length) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + int n; + if (offset == 0U) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + n = ::select(m_fd + 1, &fds, NULL, NULL, &tv); + if (n == 0) + return 0; + } + else { + n = ::select(m_fd + 1, &fds, NULL, NULL, NULL); + } + + if (n < 0) { + LogError(LOG_MODEM, "Error from select(), errno=%d", errno); + return -1; + } + + if (n > 0) { + ssize_t len = ::read(m_fd, buffer + offset, length - offset); + if (len < 0) { + if (errno != EAGAIN) { + LogError(LOG_MODEM, "Error from read(), errno=%d", errno); + return -1; + } + } + + if (len > 0) + offset += len; + } + } + + return length; +} + +/// +/// Writes data to the serial port. +/// +/// Buffer containing data to write to serial port. +/// Length of data to write to serial port. +/// Actual length of data written to the serial port. +int CSerialController::write(const uint8_t* buffer, uint32_t length) +{ + assert(buffer != NULL); + assert(m_fd != -1); + + if (length == 0U) + return 0; + + uint32_t ptr = 0U; + + while (ptr < length) { + ssize_t n = ::write(m_fd, buffer + ptr, length - ptr); + if (n < 0) { + if (errno != EAGAIN) { + LogError(LOG_MODEM, "Error returned from write(), errno=%d", errno); + return -1; + } + } + + if (n > 0) + ptr += n; + } + + return length; +} + +/// +/// Closes the connection to the serial port. +/// +void CSerialController::close() +{ + assert(m_fd != -1); + + ::close(m_fd); + m_fd = -1; +} +#endif + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +#if defined(_WIN32) || defined(_WIN64) +/// +/// +/// +/// +/// +/// +int CSerialController::readNonblock(uint8_t* buffer, uint32_t length) +{ + assert(m_handle != INVALID_HANDLE_VALUE); + assert(buffer != NULL); + + if (length == 0U) + return 0; + + DWORD errors; + COMSTAT status; + if (::ClearCommError(m_handle, &errors, &status) == 0) { + LogError(LOG_MODEM, "Error from ClearCommError for %s, err=%04lx", m_device.c_str(), ::GetLastError()); + return -1; + } + + if (status.cbInQue == 0UL) + return 0; + + DWORD readLength = status.cbInQue; + if (length < readLength) + readLength = length; + + DWORD bytes = 0UL; + BOOL ret = ::ReadFile(m_handle, buffer, readLength, &bytes, NULL); + if (!ret) { + LogError(LOG_MODEM, "Error from ReadFile for %s: %04lx", m_device.c_str(), ::GetLastError()); + return -1; + } + + return int(bytes); +} +#endif diff --git a/modem/SerialController.h b/modem/SerialController.h new file mode 100644 index 00000000..d1981aea --- /dev/null +++ b/modem/SerialController.h @@ -0,0 +1,104 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2002-2004,2007-2009,2011-2013,2015-2017 by Jonathan Naylor G4KLX +* Copyright (C) 1999-2001 by Thomas Sailor HB9JNX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__SERIAL_CONTROLLER_H__) +#define __SERIAL_CONTROLLER_H__ + +#include "Defines.h" + +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#endif + +namespace modem +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum SERIAL_SPEED { + SERIAL_1200 = 1200, + SERIAL_2400 = 2400, + SERIAL_4800 = 4800, + SERIAL_9600 = 9600, + SERIAL_19200 = 19200, + SERIAL_38400 = 38400, + SERIAL_76800 = 76800, + SERIAL_115200 = 115200, + SERIAL_230400 = 230400 + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements low-level routines to communicate over a RS232 + // serial port. + // --------------------------------------------------------------------------- + + class HOST_SW_API CSerialController { + public: + /// Initializes a new instance of the CSerialController class. + CSerialController(); + /// Initializes a new instance of the CSerialController class. + CSerialController(const std::string& device, SERIAL_SPEED speed, bool assertRTS = false); + /// Finalizes a instance of the CSerialController class. + virtual ~CSerialController(); + + /// Opens a connection to the serial port. + virtual bool open(); + + /// Reads data from the serial port. + virtual int read(uint8_t* buffer, uint32_t length); + /// Writes data to the serial port. + virtual int write(const uint8_t* buffer, uint32_t length); + + /// Closes the connection to the serial port. + virtual void close(); + + private: + std::string m_device; + SERIAL_SPEED m_speed; + bool m_assertRTS; +#if defined(_WIN32) || defined(_WIN64) + HANDLE m_handle; +#else + int m_fd; +#endif + +#if defined(_WIN32) || defined(_WIN64) + /// + int readNonblock(uint8_t * buffer, uint32_t length); +#endif + }; +} // namespace Modem + +#endif // __SERIAL_CONTROLLER_H__ diff --git a/network/BaseNetwork.cpp b/network/BaseNetwork.cpp new file mode 100644 index 00000000..355e64d1 --- /dev/null +++ b/network/BaseNetwork.cpp @@ -0,0 +1,927 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/SHA256.h" +#include "network/BaseNetwork.h" +#include "Log.h" +#include "StopWatch.h" +#include "Utils.h" + +using namespace network; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the BaseNetwork class. +/// +/// Local port used to listen for incoming data. +/// Unique ID of this modem on the network. +/// Flag indicating full-duplex operation. +/// +/// Flag indicating whether DMR slot 1 is enabled for network traffic. +/// Flag indicating whether DMR slot 2 is enabled for network traffic. +/// Flag indicating that the system activity log will be sent to the network. +BaseNetwork::BaseNetwork(uint32_t localPort, uint32_t id, bool duplex, bool debug, bool slot1, bool slot2, bool transferActivityLog) : + m_id(id), + m_slot1(slot1), + m_slot2(slot2), + m_transferActivityLog(transferActivityLog), + m_duplex(duplex), + m_debug(debug), + m_socket(localPort), + m_status(NET_STAT_INVALID), + m_retryTimer(1000U, 10U), + m_timeoutTimer(1000U, 60U), + m_buffer(NULL), + m_salt(NULL), + m_streamId(NULL), + m_p25StreamId(0U), + m_rxDMRData(4000U, "DMR Net Buffer"), + m_rxP25Data(4000U, "P25 Net Buffer"), + m_audio() +{ + assert(id > 1000U); + + m_buffer = new uint8_t[DATA_PACKET_LENGTH]; + m_salt = new uint8_t[sizeof(uint32_t)]; + m_streamId = new uint32_t[2U]; + + m_p25StreamId = 0U; + m_streamId[0U] = 0x00U; + m_streamId[1U] = 0x00U; + + StopWatch stopWatch; + ::srand((uint32_t)stopWatch.start()); +} + +/// +/// Finalizes a instance of the BaseNetwork class. +/// +BaseNetwork::~BaseNetwork() +{ + delete[] m_buffer; + delete[] m_salt; + delete[] m_streamId; +} + +/// +/// Reads DMR frame data from the DMR ring buffer. +/// +/// +/// +bool BaseNetwork::readDMR(dmr::data::Data& data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_rxDMRData.isEmpty()) + return false; + + uint8_t length = 0U; + m_rxDMRData.getData(&length, 1U); + m_rxDMRData.getData(m_buffer, length); + + uint8_t seqNo = m_buffer[4U]; + + uint32_t srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + + uint32_t dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + + uint8_t flco = (m_buffer[15U] & 0x40U) == 0x40U ? dmr::FLCO_PRIVATE : dmr::FLCO_GROUP; + + uint32_t slotNo = (m_buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + // DMO mode slot disabling + if (slotNo == 1U && !m_duplex) + return false; + + // Individual slot disabling + if (slotNo == 1U && !m_slot1) + return false; + if (slotNo == 2U && !m_slot2) + return false; + + data.setSeqNo(seqNo); + data.setSlotNo(slotNo); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(flco); + + bool dataSync = (m_buffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (m_buffer[15U] & 0x10U) == 0x10U; + + if (m_debug) { + LogDebug(LOG_NET, "DMR, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u", seqNo, srcId, dstId, flco, slotNo, length); + } + + if (dataSync) { + uint8_t dataType = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(dataType); + data.setN(0U); + } + else if (voiceSync) { + data.setData(m_buffer + 20U); + data.setDataType(dmr::DT_VOICE_SYNC); + data.setN(0U); + } + else { + uint8_t n = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(dmr::DT_VOICE); + data.setN(n); + } + + return true; +} + +/// +/// Reads P25 frame data from the P25 ring buffer. +/// +/// +/// +/// +/// +/// +/// +uint8_t* BaseNetwork::readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint32_t& len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) { + ret = false; + return NULL; + } + + if (m_rxP25Data.isEmpty()) { + ret = false; + return NULL; + } + + uint8_t length = 0U; + m_rxP25Data.getData(&length, 1U); + m_rxP25Data.getData(m_buffer, length); + + uint8_t lco = m_buffer[4U]; + + uint32_t srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + + uint32_t dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + + uint8_t MFId = m_buffer[15U]; + + uint8_t lsd1 = m_buffer[20U]; + uint8_t lsd2 = m_buffer[21U]; + + duid = m_buffer[22U]; + + if (m_debug) { + LogDebug(LOG_NET, "P25, lco = $%02X, MFId = $%02X, srcId = %u, dstId = %u, len = %u", lco, MFId, srcId, dstId, length); + } + + control.setLCO(lco); + control.setSrcId(srcId); + control.setDstId(dstId); + control.setMFId(MFId); + + lsd.setLSD1(lsd1); + lsd.setLSD2(lsd2); + + uint8_t* data = NULL; + len = m_buffer[23U]; + if (len <= 24) { + data = new uint8_t[len]; + ::memset(data, 0x00U, len); + } + else { + data = new uint8_t[len]; + ::memset(data, 0x00U, len); + ::memcpy(data, m_buffer + 24U, len); + } + + ret = true; + return data; +} + +/// +/// Writes DMR frame data to the network. +/// +/// +/// +bool BaseNetwork::writeDMR(const dmr::data::Data& data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + uint32_t slotNo = data.getSlotNo(); + + // individual slot disabling + if (slotNo == 1U && !m_slot1) + return false; + if (slotNo == 2U && !m_slot2) + return false; + + uint8_t dataType = data.getDataType(); + + uint32_t slotIndex = slotNo - 1U; + + if (dataType == dmr::DT_VOICE_LC_HEADER) { + m_streamId[slotIndex] = ::rand() + 1U; + } + + if (dataType == dmr::DT_CSBK || dataType == dmr::DT_DATA_HEADER) { + m_streamId[slotIndex] = ::rand() + 1U; + } + + return writeDMR(m_id, m_streamId[slotIndex], data); +} + +/// +/// Writes P25 LDU1 frame data to the network. +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) + m_p25StreamId = ::rand() + 1U; + + m_streamId[0] = m_p25StreamId; + + return writeP25LDU1(m_id, m_p25StreamId, control, lsd, data); +} + +/// +/// Writes P25 LDU2 frame data to the network. +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) + m_p25StreamId = ::rand() + 1U; + + m_streamId[0] = m_p25StreamId; + + return writeP25LDU2(m_id, m_p25StreamId, control, lsd, data); +} + +/// +/// Writes P25 TDU frame data to the network. +/// +/// +/// +/// +bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) + m_p25StreamId = ::rand() + 1U; + + m_streamId[0] = m_p25StreamId; + + return writeP25TDU(m_id, m_p25StreamId, control, lsd); +} + +/// +/// Writes P25 TSDU frame data to the network. +/// +/// +/// +/// +bool BaseNetwork::writeP25TSDU(const p25::lc::TSBK& tsbk, const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) + m_p25StreamId = ::rand() + 1U; + + m_streamId[0] = m_p25StreamId; + + return writeP25TSDU(m_id, m_p25StreamId, tsbk, data); +} + +/// +/// Writes P25 PDU frame data to the network. +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25PDU(const uint32_t llId, const uint8_t dataType, const uint8_t* data, const uint32_t len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + if (m_p25StreamId == 0U) + m_p25StreamId = ::rand() + 1U; + + m_streamId[0] = m_p25StreamId; + + return writeP25PDU(m_id, m_p25StreamId, llId, dataType, data, len); +} + +/// +/// Writes the local activity log to the network. +/// +/// +/// +bool BaseNetwork::writeActLog(const char* message) +{ + if (!m_transferActivityLog) + return false; + if (m_status != NET_STAT_RUNNING) + return false; + + assert(message != NULL); + + char buffer[DATA_PACKET_LENGTH]; + uint32_t len = ::strlen(message); + + ::memcpy(buffer + 0U, TAG_REPEATER_LOG, 7U); + __SET_UINT32(m_id, buffer, 7U); + ::strcpy(buffer + 11U, message); + + return write((uint8_t*)buffer, (uint32_t)len + 12U); +} + +/// +/// Resets the DMR ring buffer for the given slot. +/// +/// DMR slot ring buffer to reset. +void BaseNetwork::resetDMR(uint32_t slotNo) +{ + assert(slotNo == 1U || slotNo == 2U); + + if (slotNo == 1U) { + m_streamId[0U] = ::rand() + 1U; + } + else { + m_streamId[1U] = ::rand() + 1U; + } + + m_rxDMRData.clear(); +} + +/// +/// Resets the P25 ring buffer. +/// +void BaseNetwork::resetP25() +{ + m_p25StreamId = ::rand() + 1U; + m_streamId[0] = m_p25StreamId; + + m_rxP25Data.clear(); +} + +// --------------------------------------------------------------------------- +// Protected Class Members +// --------------------------------------------------------------------------- +/// +/// Writes DMR frame data to the network. +/// +/// +/// +/// +/// +bool BaseNetwork::writeDMR(const uint32_t id, const uint32_t streamId, const dmr::data::Data& data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_DMR_DATA, 4U); + + uint32_t srcId = data.getSrcId(); // Source Address + __SET_UINT16(srcId, buffer, 5U); + + uint32_t dstId = data.getDstId(); // Target Address + __SET_UINT16(dstId, buffer, 8U); + + __SET_UINT32(id, buffer, 11U); + + uint32_t slotNo = data.getSlotNo(); + + // Individual slot disabling + if (slotNo == 1U && !m_slot1) + return false; + if (slotNo == 2U && !m_slot2) + return false; + + buffer[15U] = slotNo == 1U ? 0x00U : 0x80U; // Slot Number + + uint8_t flco = data.getFLCO(); + buffer[15U] |= flco == dmr::FLCO_GROUP ? 0x00U : 0x40U; // Group + + uint32_t count = 1U; + + uint8_t dataType = data.getDataType(); + if (dataType == dmr::DT_VOICE_SYNC) { + buffer[15U] |= 0x10U; + } + else if (dataType == dmr::DT_VOICE) { + buffer[15U] |= data.getN(); + } + else { + if (dataType == dmr::DT_VOICE_LC_HEADER) { + count = 2U; + } + + if (dataType == dmr::DT_CSBK || dataType == dmr::DT_DATA_HEADER) { + count = 1U; + } + + buffer[15U] |= (0x20U | dataType); + } + + buffer[4U] = data.getSeqNo(); // Sequence Number + + __SET_UINT32(streamId, buffer, 16U); + + data.getData(buffer + 20U); + + buffer[53U] = data.getBER(); // Bit Error Rate + buffer[54U] = data.getRSSI(); // RSSI + + if (m_debug) + Utils::dump(1U, "Network Transmitted, DMR", buffer, (DMR_PACKET_SIZE + PACKET_PAD)); + + for (uint32_t i = 0U; i < count; i++) + write(buffer, (DMR_PACKET_SIZE + PACKET_PAD)); + + return true; +} + +/// +/// Writes P25 LDU1 frame data to the network. +/// +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25LDU1(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, + const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + assert(data != NULL); + + uint8_t serviceOptions = + (control.getEmergency() ? 0x80U : 0x00U) + + (control.getEncrypted() ? 0x40U : 0x00U) + + (control.getPriority() & 0x07U); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); + + buffer[4U] = control.getLCO(); // LCO + + uint32_t srcId = control.getSrcId(); // Source Address + __SET_UINT16(srcId, buffer, 5U); + + uint32_t dstId = control.getDstId(); // Target Address + __SET_UINT16(dstId, buffer, 8U); + + __SET_UINT32(id, buffer, 11U); + + buffer[15U] = control.getMFId(); // MFId + + __SET_UINT32(streamId, buffer, 16U); + + buffer[20U] = lsd.getLSD1(); // LSD 1 + buffer[21U] = lsd.getLSD2(); // LSD 2 + + buffer[22U] = p25::P25_DUID_LDU1; // DUID + + uint32_t count = 24U; + uint8_t tempBuf[22U]; + + // The '62' record + ::memcpy(tempBuf, LDU1_REC62, 22U); + m_audio.decode(data, tempBuf + 10U, 0U); + ::memcpy(buffer + 24U, tempBuf, 22U); + count += 22U; + + // The '63' record + ::memcpy(tempBuf, LDU1_REC63, 14U); + m_audio.decode(data, tempBuf + 1U, 1U); + ::memcpy(buffer + 46U, tempBuf, 14U); + count += 14U; + + // The '64' record + ::memcpy(tempBuf, LDU1_REC64, 17U); + tempBuf[1U] = control.getLCO(); + tempBuf[2U] = control.getMFId(); + m_audio.decode(data, tempBuf + 5U, 2U); + ::memcpy(buffer + 60U, tempBuf, 17U); + count += 17U; + + // The '65' record + ::memcpy(tempBuf, LDU1_REC65, 17U); + tempBuf[1U] = (dstId >> 16) & 0xFFU; + tempBuf[2U] = (dstId >> 8) & 0xFFU; + tempBuf[3U] = (dstId >> 0) & 0xFFU; + m_audio.decode(data, tempBuf + 5U, 3U); + ::memcpy(buffer + 77U, tempBuf, 17U); + count += 17U; + + // The '66' record + ::memcpy(tempBuf, LDU1_REC66, 17U); + tempBuf[1U] = (srcId >> 16) & 0xFFU; + tempBuf[2U] = (srcId >> 8) & 0xFFU; + tempBuf[3U] = (srcId >> 0) & 0xFFU; + m_audio.decode(data, tempBuf + 5U, 4U); + ::memcpy(buffer + 94U, tempBuf, 17U); + count += 17U; + + // The '67' record + ::memcpy(tempBuf, LDU1_REC67, 17U); + m_audio.decode(data, tempBuf + 5U, 5U); + ::memcpy(buffer + 111U, tempBuf, 17U); + count += 17U; + + // The '68' record + ::memcpy(tempBuf, LDU1_REC68, 17U); + m_audio.decode(data, tempBuf + 5U, 6U); + ::memcpy(buffer + 128U, tempBuf, 17U); + count += 17U; + + // The '69' record + ::memcpy(tempBuf, LDU1_REC69, 17U); + m_audio.decode(data, tempBuf + 5U, 7U); + ::memcpy(buffer + 145U, tempBuf, 17U); + count += 17U; + + // The '6A' record + ::memcpy(tempBuf, LDU1_REC6A, 16U); + tempBuf[1U] = serviceOptions; + m_audio.decode(data, tempBuf + 4U, 8U); + ::memcpy(buffer + 162U, tempBuf, 16U); + count += 16U; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, P25 LDU1", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + +/// +/// Writes P25 LDU2 frame data to the network. +/// +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25LDU2(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, + const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + assert(data != NULL); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); + + buffer[4U] = control.getLCO(); // LCO + + uint32_t srcId = control.getSrcId(); // Source Address + __SET_UINT16(srcId, buffer, 5U); + + uint32_t dstId = control.getDstId(); // Target Address + __SET_UINT16(dstId, buffer, 8U); + + __SET_UINT32(id, buffer, 11U); + + buffer[15U] = control.getMFId(); // MFId + + __SET_UINT32(streamId, buffer, 16U); + + buffer[20U] = lsd.getLSD1(); // LSD 1 + buffer[21U] = lsd.getLSD2(); // LSD 2 + + buffer[22U] = p25::P25_DUID_LDU2; // DUID + + uint32_t count = 24U; + uint8_t tempBuf[22U]; + + // The '6B' record + ::memcpy(tempBuf, LDU2_REC6B, 22U); + m_audio.decode(data, tempBuf + 10U, 0U); + ::memcpy(buffer + 24U, tempBuf, 22U); + count += 22U; + + // The '6C' record + ::memcpy(tempBuf, LDU2_REC6C, 14U); + m_audio.decode(data, tempBuf + 1U, 1U); + ::memcpy(buffer + 46U, tempBuf, 14U); + count += 14U; + + // generate MI data + uint8_t mi[p25::P25_MI_LENGTH_BYTES]; + control.getMI(mi); + + // The '6D' record + ::memcpy(tempBuf, LDU2_REC6D, 17U); + tempBuf[1U] = mi[0U]; + tempBuf[2U] = mi[1U]; + tempBuf[3U] = mi[2U]; + m_audio.decode(data, tempBuf + 5U, 2U); + ::memcpy(buffer + 60U, tempBuf, 17U); + count += 17U; + + // The '6E' record + ::memcpy(tempBuf, LDU2_REC6E, 17U); + tempBuf[1U] = mi[3U]; + tempBuf[2U] = mi[4U]; + tempBuf[3U] = mi[5U]; + m_audio.decode(data, tempBuf + 5U, 3U); + ::memcpy(buffer + 77U, tempBuf, 17U); + count += 17U; + + // The '6F' record + ::memcpy(tempBuf, LDU2_REC6F, 17U); + tempBuf[1U] = mi[6U]; + tempBuf[2U] = mi[7U]; + tempBuf[3U] = mi[8U]; + m_audio.decode(data, tempBuf + 5U, 4U); + ::memcpy(buffer + 94U, tempBuf, 17U); + count += 17U; + + // The '70' record + ::memcpy(tempBuf, LDU2_REC70, 17U); + tempBuf[1U] = control.getAlgId(); + uint32_t kid = control.getKId(); + tempBuf[2U] = (kid >> 8) & 0xFFU; + tempBuf[3U] = (kid >> 0) & 0xFFU; + m_audio.decode(data, tempBuf + 5U, 5U); + ::memcpy(buffer + 111U, tempBuf, 17U); + count += 17U; + + // The '71' record + ::memcpy(tempBuf, LDU2_REC71, 17U); + m_audio.decode(data, tempBuf + 5U, 6U); + ::memcpy(buffer + 128U, tempBuf, 17U); + count += 17U; + + // The '72' record + ::memcpy(tempBuf, LDU2_REC72, 17U); + m_audio.decode(data, tempBuf + 5U, 7U); + ::memcpy(buffer + 145U, tempBuf, 17U); + count += 17U; + + // The '73' record + ::memcpy(tempBuf, LDU2_REC73, 16U); + m_audio.decode(data, tempBuf + 4U, 8U); + ::memcpy(buffer + 162U, tempBuf, 16U); + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, P25 LDU2", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + +/// +/// Writes P25 TDU frame data to the network. +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25TDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); + + buffer[4U] = control.getLCO(); // LCO + + uint32_t srcId = control.getSrcId(); // Source Address + __SET_UINT16(srcId, buffer, 5U); + + uint32_t dstId = control.getDstId(); // Target Address + __SET_UINT16(dstId, buffer, 8U); + + __SET_UINT32(id, buffer, 11U); + + buffer[15U] = control.getMFId(); // MFId + + __SET_UINT32(streamId, buffer, 16U); + + buffer[20U] = lsd.getLSD1(); // LSD 1 + buffer[21U] = lsd.getLSD2(); // LSD 2 + + buffer[22U] = p25::P25_DUID_TDU; // DUID + + uint32_t count = 24U; + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, P25 TDU", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + +/// +/// Writes P25 TSDU frame data to the network. +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25TSDU(const uint32_t id, const uint32_t streamId, const p25::lc::TSBK& tsbk, const uint8_t* data) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); + + buffer[4U] = tsbk.getLCO(); // LCO + + uint32_t srcId = tsbk.getSrcId(); // Source Address + __SET_UINT16(srcId, buffer, 5U); + + uint32_t dstId = tsbk.getDstId(); // Target Address + __SET_UINT16(dstId, buffer, 8U); + + __SET_UINT32(id, buffer, 11U); + + buffer[15U] = tsbk.getMFId(); // MFId + + __SET_UINT32(streamId, buffer, 16U); + + buffer[20U] = 0U; // Reserved (LSD 1) + buffer[21U] = 0U; // Reserved (LSD 2) + + buffer[22U] = p25::P25_DUID_TSDU; // DUID + + uint32_t count = 24U; + + ::memcpy(buffer + 24U, data, p25::P25_TSDU_FRAME_LENGTH_BYTES); + count += p25::P25_TSDU_FRAME_LENGTH_BYTES; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, P25 TDSU", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + +/// +/// Writes P25 PDU frame data to the network. +/// +/// +/// +/// +/// +/// +/// +/// +bool BaseNetwork::writeP25PDU(const uint32_t id, const uint32_t streamId, const uint32_t llId, const uint8_t dataType, const uint8_t* data, + const uint32_t len) +{ + if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) + return false; + + assert(data != NULL); + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + ::memcpy(buffer + 0U, TAG_P25_DATA, 4U); + + buffer[4U] = dataType; // Data Type (LCO) + + __SET_UINT16(llId, buffer, 5U); // Logical Link Address (Source Address) + + __SET_UINT16(len, buffer, 8U); // PDU Length [bytes] (Target Address) + + __SET_UINT32(id, buffer, 11U); + + buffer[15U] = p25::P25_MFG_STANDARD; // MFId + + __SET_UINT32(streamId, buffer, 16U); + + buffer[20U] = 0U; // Reserved (LSD 1) + buffer[21U] = 0U; // Reserved (LSD 2) + + buffer[22U] = p25::P25_DUID_PDU; // DUID + + uint32_t count = 24U; + + ::memcpy(buffer + 24U, data, len); + count += len; + + buffer[23U] = count; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, P25 PDU", buffer, (count + PACKET_PAD)); + + write(buffer, (count + PACKET_PAD)); + + return true; +} + +/// +/// Writes data to the network. +/// +/// Buffer to write to the network. +/// Length of buffer to write. +/// IP address to write data to. +/// Port number for remote UDP socket. +/// True, if buffer is written to the network, otherwise false. +bool BaseNetwork::write(const uint8_t* data, uint32_t length, const in_addr& address, uint32_t port) +{ + assert(data != NULL); + assert(length > 0U); + assert(port > 0U); + + // if (m_debug) + // Utils::dump(1U, "Network Transmitted", data, length); + + bool ret = m_socket.write(data, length, address, port); + if (!ret) { + LogError(LOG_NET, "Socket has failed when writing data to the peer, retrying connection"); + return false; + } + + return true; +} diff --git a/network/BaseNetwork.h b/network/BaseNetwork.h new file mode 100644 index 00000000..f4e78030 --- /dev/null +++ b/network/BaseNetwork.h @@ -0,0 +1,262 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__BASE_NETWORK_H__) +#define __BASE_NETWORK_H__ + +#include "Defines.h" +#include "dmr/DMRDefines.h" +#include "p25/P25Defines.h" +#include "dmr/data/Data.h" +#include "p25/data/LowSpeedData.h" +#include "p25/lc/LC.h" +#include "p25/lc/TSBK.h" +#include "p25/lc/TDULC.h" +#include "p25/Audio.h" +#include "network/UDPSocket.h" +#include "RingBuffer.h" +#include "Timer.h" + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +#define TAG_DMR_DATA "DMRD" +#define TAG_P25_DATA "P25D" + +#define TAG_MASTER_WL_RID "MSTWRID" +#define TAG_MASTER_BL_RID "MSTBRID" +#define TAG_MASTER_ACTIVE_TGS "MSTTID" +#define TAG_MASTER_DEACTIVE_TGS "MSTDTID" + +#define TAG_MASTER_NAK "MSTNAK" +#define TAG_MASTER_CLOSING "MSTCL" +#define TAG_MASTER_PONG "MSTPONG" + +#define TAG_REPEATER_LOGIN "RPTL" +#define TAG_REPEATER_AUTH "RPTK" +#define TAG_REPEATER_CONFIG "RPTC" + +#define TAG_REPEATER_ACK "RPTACK" +#define TAG_REPEATER_CLOSING "RPTCL" +#define TAG_REPEATER_PING "RPTPING" + +#define TAG_REPEATER_LOG "TRNSLOG"//"RPTALOG" + +namespace network +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + // P25 V.24 LDU1 Encapsulation Frames + const uint8_t LDU1_REC62[] = { + 0x62U, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; + + const uint8_t LDU1_REC63[] = { + 0x63U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC64[] = { + 0x64U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC65[] = { + 0x65U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC66[] = { + 0x66U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC67[] = { + 0x67U, 0xF0U, 0x9DU, 0x6AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC68[] = { + 0x68U, 0x19U, 0xD4U, 0x26U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC69[] = { + 0x69U, 0xE0U, 0xEBU, 0x7BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU1_REC6A[] = { + 0x6AU, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; + + // P25 V.24 LDU2 Encapsulation Frames + const uint8_t LDU2_REC6B[] = { + 0x6BU, 0x02U, 0x02U, 0x0CU, 0x0BU, 0x12U, 0x64U, 0x00U, 0x00U, 0x80U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; + + const uint8_t LDU2_REC6C[] = { + 0x6CU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC6D[] = { + 0x6DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC6E[] = { + 0x6EU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC6F[] = { + 0x6FU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC70[] = { + 0x70U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC71[] = { + 0x71U, 0xACU, 0xB8U, 0xA4U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC72[] = { + 0x72U, 0x9BU, 0xDCU, 0x75U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x02U }; + + const uint8_t LDU2_REC73[] = { + 0x73U, 0x00U, 0x00U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; + + const uint32_t DATA_PACKET_LENGTH = 8192U; + const uint32_t DMR_PACKET_SIZE = 55U; + const uint32_t PACKET_PAD = 8U; + + // --------------------------------------------------------------------------- + // Network Peer Connection Status + // --------------------------------------------------------------------------- + + enum NET_CONN_STATUS { + // Common States + NET_STAT_WAITING_CONNECT, + NET_STAT_WAITING_LOGIN, + NET_STAT_WAITING_AUTHORISATION, + NET_STAT_WAITING_CONFIG, + NET_STAT_RUNNING, + + // Master States + NET_STAT_RPTL_RECEIVED, + NET_STAT_CHALLENGE_SENT, + + NET_STAT_MST_RUNNING, + + NET_STAT_INVALID = 0x7FFFFFF + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the base networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API BaseNetwork { + public: + /// Initializes a new instance of the BaseNetwork class. + BaseNetwork(uint32_t localPort, uint32_t id, bool duplex, bool debug, bool slot1, bool slot2, bool transferActivityLog); + /// Finalizes a instance of the BaseNetwork class. + virtual ~BaseNetwork(); + + /// Gets the current status of the network. + NET_CONN_STATUS getStatus() { return m_status; } + + /// Reads DMR frame data from the DMR ring buffer. + virtual bool readDMR(dmr::data::Data& data); + /// Reads P25 frame data from the P25 ring buffer. + virtual uint8_t* readP25(bool& ret, p25::lc::LC& control, p25::data::LowSpeedData& lsd, uint8_t& duid, uint32_t& len); + + /// Writes DMR frame data to the network. + virtual bool writeDMR(const dmr::data::Data& data); + /// Writes P25 LDU1 frame data to the network. + virtual bool writeP25LDU1(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + /// Writes P25 LDU2 frame data to the network. + virtual bool writeP25LDU2(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + /// Writes P25 TDU frame data to the network. + virtual bool writeP25TDU(const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); + /// Writes P25 TSDU frame data to the network. + virtual bool writeP25TSDU(const p25::lc::TSBK& control, const uint8_t* data); + /// Writes P25 PDU frame data to the network. + virtual bool writeP25PDU(const uint32_t llId, const uint8_t dataType, const uint8_t* data, const uint32_t len); + + /// Writes the local activity log to the network. + virtual bool writeActLog(const char* message); + + /// Updates the timer by the passed number of milliseconds. + virtual void clock(uint32_t ms) = 0; + + /// Opens connection to the network. + virtual bool open() = 0; + + /// Closes connection to the network. + virtual void close() = 0; + + /// Resets the DMR ring buffer for the given slot. + virtual void resetDMR(uint32_t slotNo); + /// Resets the P25 ring buffer. + virtual void resetP25(); + + protected: + uint32_t m_id; + + bool m_slot1; + bool m_slot2; + + bool m_transferActivityLog; + + bool m_duplex; + bool m_debug; + + UDPSocket m_socket; + NET_CONN_STATUS m_status; + + Timer m_retryTimer; + Timer m_timeoutTimer; + + uint8_t* m_buffer; + uint8_t* m_salt; + + uint32_t* m_streamId; + uint32_t m_p25StreamId; + + RingBuffer m_rxDMRData; + RingBuffer m_rxP25Data; + + p25::Audio m_audio; + + /// Writes DMR frame data to the network. + bool writeDMR(const uint32_t id, const uint32_t streamId, const dmr::data::Data& data); + /// Writes P25 LDU1 frame data to the network. + bool writeP25LDU1(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + /// Writes P25 LDU2 frame data to the network. + bool writeP25LDU2(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd, const uint8_t* data); + /// Writes P25 TDU frame data to the network. + bool writeP25TDU(const uint32_t id, const uint32_t streamId, const p25::lc::LC& control, const p25::data::LowSpeedData& lsd); + /// Writes P25 TSDU frame data to the network. + bool writeP25TSDU(const uint32_t id, const uint32_t streamId, const p25::lc::TSBK& control, const uint8_t* data); + /// Writes P25 PDU frame data to the network. + bool writeP25PDU(const uint32_t id, const uint32_t streamId, const uint32_t llId, const uint8_t dataType, const uint8_t* data, const uint32_t len); + + /// Writes data to the network. + virtual bool write(const uint8_t* data, uint32_t length) = 0; + /// Writes data to the network. + virtual bool write(const uint8_t* data, uint32_t length, const in_addr& address, uint32_t port); + }; +} // namespace network + +#endif // __BASE_NETWORK_H__ diff --git a/network/Network.cpp b/network/Network.cpp new file mode 100644 index 00000000..1316af1c --- /dev/null +++ b/network/Network.cpp @@ -0,0 +1,547 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "edac/SHA256.h" +#include "network/Network.h" +#include "Log.h" +#include "StopWatch.h" +#include "Utils.h" + +using namespace network; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Network class. +/// +/// Network Hostname/IP address to connect to. +/// Network port number. +/// +/// Unique ID of this modem on the network. +/// Network authentication password. +/// Flag indicating full-duplex operation. +/// Flag indicating whether DMR slot 1 is enabled for network traffic. +/// Flag indicating whether DMR slot 2 is enabled for network traffic. +/// Flag indicating that the system activity log will be sent to the network. +/// Flag indicating that the system will accept radio ID and talkgroup ID lookups from the network. +Network::Network(const std::string& address, uint32_t port, uint32_t local, uint32_t id, const std::string& password, + bool duplex, bool debug, bool slot1, bool slot2, bool transferActivityLog, bool updateLookup) : + BaseNetwork(local, id, duplex, debug, slot1, slot2, transferActivityLog), + m_addressStr(address), + m_address(), + m_port(port), + m_password(password), + m_enabled(false), + m_updateLookup(updateLookup), + m_callsign(), + m_rxFrequency(0U), + m_txFrequency(0U), + m_txOffsetMhz(0.0F), + m_chBandwidthKhz(0.0F), + m_power(0U), + m_latitude(0.0F), + m_longitude(0.0F), + m_height(0), + m_location() +{ + assert(!address.empty()); + assert(port > 0U); + assert(!password.empty()); + + m_address = UDPSocket::lookup(address); +} + +/// +/// Finalizes a instance of the Network class. +/// +Network::~Network() +{ + /* stub */ +} + +/// +/// Sets the instances of the Radio ID and Talkgroup ID lookup tables. +/// +/// Radio ID Lookup Table Instance +/// Talkgroup ID Lookup Table Instance +void Network::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Sets various configuration settings from the modem. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +void Network::setConfig(const std::string& callsign, uint32_t rxFrequency, uint32_t txFrequency, float txOffsetMhz, + float chBandwidthKhz, uint32_t power, float latitude, float longitude, int height, const std::string& location) +{ + m_callsign = callsign; + m_rxFrequency = rxFrequency; + m_txFrequency = txFrequency; + m_txOffsetMhz = txOffsetMhz; + m_chBandwidthKhz = chBandwidthKhz; + m_power = power; + m_latitude = latitude; + m_longitude = longitude; + m_height = height; + m_location = location; +} + +/// +/// Gets the current status of the network. +/// +/// +uint8_t Network::getStatus() +{ + return m_status; +} + +/// +/// Updates the timer by the passed number of milliseconds. +/// +/// +void Network::clock(uint32_t ms) +{ + if (m_status == NET_STAT_WAITING_CONNECT) { + m_retryTimer.clock(ms); + if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { + bool ret = m_socket.open(); + if (ret) { + ret = writeLogin(); + if (!ret) + return; + + m_status = NET_STAT_WAITING_LOGIN; + m_timeoutTimer.start(); + } + + m_retryTimer.start(); + } + + return; + } + + in_addr address; + uint32_t port; + int length = m_socket.read(m_buffer, DATA_PACKET_LENGTH, address, port); + if (length < 0) { + LogError(LOG_NET, "Socket has failed, retrying connection to the master"); + close(); + open(); + return; + } + + if (m_debug && length > 0) + Utils::dump(1U, "Network Received", m_buffer, length); + + if (length > 0 && m_address.s_addr == address.s_addr && m_port == port) { + if (::memcmp(m_buffer, TAG_DMR_DATA, 4U) == 0) { + if (m_enabled) { + if (m_debug) + Utils::dump(1U, "Network Received, DMR", m_buffer, length); + + uint8_t len = length; + m_rxDMRData.addData(&len, 1U); + m_rxDMRData.addData(m_buffer, len); + } + } + else if (::memcmp(m_buffer, TAG_P25_DATA, 4U) == 0) { + if (m_enabled) { + if (m_debug) + Utils::dump(1U, "Network Received, P25", m_buffer, length); + + uint8_t len = length; + m_rxP25Data.addData(&len, 1U); + m_rxP25Data.addData(m_buffer, len); + } + } + else if (::memcmp(m_buffer, TAG_MASTER_WL_RID, 7U) == 0) { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, WL RID", m_buffer, length); + + // update RID lists + uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); + m_ridLookup->toggleEntry(id, true); + j += 4U; + } + } + } + else if (::memcmp(m_buffer, TAG_MASTER_BL_RID, 7U) == 0) { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, BL RID", m_buffer, length); + + // update RID lists + uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); + m_ridLookup->toggleEntry(id, false); + j += 4U; + } + } + } + else if (::memcmp(m_buffer, TAG_MASTER_ACTIVE_TGS, 6U) == 0) { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, ACTIVE TGS", m_buffer, length); + + // update TGID lists + uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); + uint8_t slot = (m_buffer[14U + j]); + + lookups::TalkgroupId tid = m_tidLookup->find(id); + if (tid.tgEnabled() == false && tid.tgDefault() == true) { + LogMessage(LOG_NET, "Activated TG %u TS %u in TGID table", id, slot); + } + + m_tidLookup->addEntry(id, slot, true); + j += 5U; + } + } + } + else if (::memcmp(m_buffer, TAG_MASTER_DEACTIVE_TGS, 7U) == 0) { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, DEACTIVE TGS", m_buffer, length); + + // update TGID lists + uint32_t len = (m_buffer[7U] << 16) | (m_buffer[8U] << 8) | (m_buffer[9U] << 0); + uint32_t j = 0U; + for (uint8_t i = 0; i < len; i++) { + uint32_t id = (m_buffer[11U + j] << 16) | (m_buffer[12U + j] << 8) | (m_buffer[13U + j] << 0); + uint8_t slot = (m_buffer[14U + j]); + + lookups::TalkgroupId tid = m_tidLookup->find(id); + if (tid.tgEnabled() == true && tid.tgDefault() == false) { + LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); + m_tidLookup->addEntry(id, slot, false); + } + + j += 5U; + } + } + } + else if (::memcmp(m_buffer, TAG_MASTER_NAK, 6U) == 0) { + if (m_status == NET_STAT_RUNNING) { + LogWarning(LOG_NET, "Login to the master has failed, retrying login ..."); + m_status = NET_STAT_WAITING_LOGIN; + m_timeoutTimer.start(); + m_retryTimer.start(); + } + else { + // Once the modem death spiral has been prevented in Modem.cpp + // the Network sometimes times out and reaches here. + // We want it to reconnect so... + LogError(LOG_NET, "Login to the master has failed, retrying network ..."); + close(); + open(); + return; + } + } + else if (::memcmp(m_buffer, TAG_REPEATER_ACK, 6U) == 0) { + switch (m_status) { + case NET_STAT_WAITING_LOGIN: + LogDebug(LOG_NET, "Sending authorisation"); + ::memcpy(m_salt, m_buffer + 6U, sizeof(uint32_t)); + writeAuthorisation(); + m_status = NET_STAT_WAITING_AUTHORISATION; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case NET_STAT_WAITING_AUTHORISATION: + LogDebug(LOG_NET, "Sending configuration"); + writeConfig(); + m_status = NET_STAT_WAITING_CONFIG; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case NET_STAT_WAITING_CONFIG: + LogMessage(LOG_NET, "Logged into the master successfully"); + m_status = NET_STAT_RUNNING; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + default: + break; + } + } + else if (::memcmp(m_buffer, TAG_MASTER_CLOSING, 5U) == 0) { + LogError(LOG_NET, "Master is closing down"); + close(); + open(); + } + else if (::memcmp(m_buffer, TAG_MASTER_PONG, 7U) == 0) { + m_timeoutTimer.start(); + } + else { + Utils::dump("Unknown packet from the master", m_buffer, length); + } + } + + m_retryTimer.clock(ms); + if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { + switch (m_status) { + case NET_STAT_WAITING_LOGIN: + writeLogin(); + break; + case NET_STAT_WAITING_AUTHORISATION: + writeAuthorisation(); + break; + case NET_STAT_WAITING_CONFIG: + writeConfig(); + break; + case NET_STAT_RUNNING: + writePing(); + break; + default: + break; + } + + m_retryTimer.start(); + } + + m_timeoutTimer.clock(ms); + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + LogError(LOG_NET, "Connection to the master has timed out, retrying connection"); + close(); + open(); + } +} + +/// +/// Opens connection to the network. +/// +/// +bool Network::open() +{ + if (m_debug) + LogMessage(LOG_NET, "Opening Network"); + + if (m_address.s_addr == INADDR_NONE) { + m_address = UDPSocket::lookup(m_addressStr); + } + + m_status = NET_STAT_WAITING_CONNECT; + m_timeoutTimer.stop(); + m_retryTimer.start(); + + return true; +} + +/// +/// Sets flag enabling network communication. +/// +/// +void Network::enable(bool enabled) +{ + m_enabled = enabled; +} + +/// +/// Closes connection to the network. +/// +void Network::close() +{ + if (m_debug) + LogMessage(LOG_NET, "Closing Network"); + + if (m_status == NET_STAT_RUNNING) { + uint8_t buffer[9U]; + ::memcpy(buffer + 0U, TAG_REPEATER_CLOSING, 5U); + __SET_UINT32(m_id, buffer, 5U); + write(buffer, 9U); + } + + m_socket.close(); + + m_retryTimer.stop(); + m_timeoutTimer.stop(); + + m_status = NET_STAT_WAITING_CONNECT; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Writes login request to the network. +/// +/// +bool Network::writeLogin() +{ + uint8_t buffer[8U]; + + ::memcpy(buffer + 0U, TAG_REPEATER_LOGIN, 4U); + __SET_UINT32(m_id, buffer, 4U); + + if (m_debug) + Utils::dump(1U, "Network Transmitted, Login", buffer, 8U); + + return write(buffer, 8U); +} + +/// +/// Writes network authentication challenge. +/// +/// +bool Network::writeAuthorisation() +{ + size_t size = m_password.size(); + + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, m_salt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = m_password.at(i); + + uint8_t out[40U]; + ::memcpy(out + 0U, TAG_REPEATER_AUTH, 4U); + __SET_UINT32(m_id, out, 4U); + + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out + 8U); + + delete[] in; + + if (m_debug) + Utils::dump(1U, "Network Transmitted, Authorisation", out, 40U); + + return write(out, 40U); +} + +/// +/// Writes modem configuration to the network. +/// +/// +bool Network::writeConfig() +{ + const char* software = "DVM_DMR_P25"; + char slots = '0'; + if (m_duplex) { + if (m_slot1 && m_slot2) + slots = '3'; + else if (m_slot1 && !m_slot2) + slots = '1'; + else if (!m_slot1 && m_slot2) + slots = '2'; + } + else { + slots = '4'; + } + + char buffer[400U]; + + ::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U); + __SET_UINT32(m_id, buffer, 4U); + + char latitude[20U]; + ::sprintf(latitude, "%08f", m_latitude); + + char longitude[20U]; + ::sprintf(longitude, "%09f", m_longitude); + + char chBandwidthKhz[20U]; + ::sprintf(chBandwidthKhz, "%03f", m_chBandwidthKhz); + + char txOffsetMhz[20U]; + ::sprintf(txOffsetMhz, "%03f", m_txOffsetMhz); + + uint32_t power = m_power; + if (power > 99U) + power = 99U; + + int height = m_height; + if (height > 999) + height = 999; + + // CallsgRX TX TxOfChBndLatitLongiHghtLocationPowrReservedSlReserved SoftwarePackage + ::sprintf(buffer + 8U, "%-8.8s%09u%09u%2.1s%2.1s%8.8s%9.9s%03d%-20.20s%02u%-17.17s%c%-124.124s%-40.40s%-40.40s", m_callsign.c_str(), + m_rxFrequency, m_txFrequency, txOffsetMhz, chBandwidthKhz, latitude, longitude, height, m_location.c_str(), + power, "", slots, "", software, software); + + if (m_debug) + Utils::dump(1U, "Network Transmitted, Configuration", (uint8_t*)buffer, 11U); + + return write((uint8_t*)buffer, 302U); +} + +/// +/// Writes a network stay-alive ping. +/// +bool Network::writePing() +{ + uint8_t buffer[11U]; + + ::memcpy(buffer + 0U, TAG_REPEATER_PING, 7U); + __SET_UINT32(m_id, buffer, 7U); + + if (m_debug) + Utils::dump(1U, "Network Transmitted, Ping", buffer, 11U); + + return write(buffer, 11U); +} + +/// +/// Writes data to the network. +/// +/// Buffer to write to the network. +/// Length of buffer to write. +/// True, if buffer is written to the network, otherwise false. +bool Network::write(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + assert(length > 0U); + + return BaseNetwork::write(data, length, m_address, m_port); +} diff --git a/network/Network.h b/network/Network.h new file mode 100644 index 00000000..1f69e49e --- /dev/null +++ b/network/Network.h @@ -0,0 +1,117 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__NETWORK_H__) +#define __NETWORK_H__ + +#include "Defines.h" +#include "network/BaseNetwork.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +#include +#include + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements the core networking logic. + // --------------------------------------------------------------------------- + + class HOST_SW_API Network : public BaseNetwork { + public: + /// Initializes a new instance of the Network class. + Network(const std::string& address, uint32_t port, uint32_t local, uint32_t id, const std::string& password, + bool duplex, bool debug, bool slot1, bool slot2, bool transferActivityLog, bool updateLookup); + /// Finalizes a instance of the Network class. + ~Network(); + + /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. + void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup); + /// Sets various configuration settings from the modem. + void setConfig(const std::string& callsign, uint32_t rxFrequency, uint32_t txFrequency, float txOffsetMhz, + float chBandwidthKhz, uint32_t power, float latitude, float longitude, int height, const std::string& location); + /// Gets the current status of the network. + uint8_t getStatus(); + + /// Updates the timer by the passed number of milliseconds. + void clock(uint32_t ms); + + /// Opens connection to the network. + bool open(); + + /// Sets flag enabling network communication. + void enable(bool enabled); + + /// Closes connection to the network. + void close(); + + private: + std::string m_addressStr; + in_addr m_address; + uint32_t m_port; + + std::string m_password; + + bool m_enabled; + + bool m_updateLookup; + + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + /** station metadata */ + std::string m_callsign; + uint32_t m_rxFrequency; + uint32_t m_txFrequency; + float m_txOffsetMhz; + float m_chBandwidthKhz; + uint32_t m_power; + float m_latitude; + float m_longitude; + int m_height; + std::string m_location; + + /// Writes login request to the network. + bool writeLogin(); + /// Writes network authentication challenge. + bool writeAuthorisation(); + /// Writes modem configuration to the network. + bool writeConfig(); + /// Writes a network stay-alive ping. + bool writePing(); + + /// Writes data to the network. + bool write(const uint8_t* data, uint32_t length); + }; +} // namespace network + +#endif // __NETWORK_H__ diff --git a/network/RemoteControl.cpp b/network/RemoteControl.cpp new file mode 100644 index 00000000..44463417 --- /dev/null +++ b/network/RemoteControl.cpp @@ -0,0 +1,693 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2019 by Jonathan Naylor G4KLX +* Copyright (C) 2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "RemoteControl.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace network; +using namespace modem; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define BAD_CMD_STR "Bad or invalid remote command." +#define INVALID_OPT_STR "Invalid command arguments: " +#define CMD_FAILED_STR "Remote command failed: " + +#define RCD_MODE_CMD "mdm-mode" +#define RCD_MODE_OPT_IDLE "idle" +#define RCD_MODE_OPT_LCKOUT "lockout" +#define RCD_MODE_OPT_FDMR "dmr" +#define RCD_MODE_OPT_FP25 "p25" + +#define RCD_KILL_CMD "mdm-kill" + +#define RCD_RID_WLIST_CMD "rid-whitelist" +#define RCD_RID_BLIST_CMD "rid-blacklist" + +#define RCD_DMR_BEACON_CMD "dmr-beacon" +#define RCD_P25_CC_CMD "p25-cc" + +#define RCD_DMRD_MDM_INJ_CMD "dmrd-mdm-inj" +#define RCD_P25D_MDM_INJ_CMD "p25d-mdm-inj" + +#define RCD_DMR_RID_PAGE_CMD "dmr-rid-page" +#define RCD_DMR_RID_CHECK_CMD "dmr-rid-check" +#define RCD_DMR_RID_INHIBIT_CMD "dmr-rid-inhibit" +#define RCD_DMR_RID_UNINHIBIT_CMD "dmr-rid-uninhibit" + +#define RCD_P25_SET_MFID_CMD "p25-set-mfid" +#define RCD_P25_RID_PAGE_CMD "p25-rid-page" +#define RCD_P25_RID_CHECK_CMD "p25-rid-check" +#define RCD_P25_RID_INHIBIT_CMD "p25-rid-inhibit" +#define RCD_P25_RID_UNINHIBIT_CMD "p25-rid-uninhibit" +#define RCD_P25_RID_GAQ_CMD "p25-rid-gaq" +#define RCD_P25_RID_UREG_CMD "p25-rid-ureg" + +#define RCD_P25_PATCH_CMD "p25-patch" + +#define RCD_P25_RELEASE_GRANTS "p25-rel-grnts" + +const uint32_t RC_BUFFER_LENGTH = 100U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the RemoteControl class. +/// +/// Network Hostname/IP address to connect to. +/// Network port number. +RemoteControl::RemoteControl(const std::string& address, uint32_t port) : + m_socket(address, port), + m_p25MFId(p25::P25_MFG_STANDARD) +{ + assert(!address.empty()); + assert(port > 0U); +} + +/// +/// Finalizes a instance of the RemoteControl class. +/// +RemoteControl::~RemoteControl() +{ + /* stub */ +} + +/// +/// Sets the instances of the Radio ID and Talkgroup ID lookup tables. +/// +/// Radio ID Lookup Table Instance +/// Talkgroup ID Lookup Table Instance +void RemoteControl::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Process remote network command data. +/// +/// Instance of the Host class. +/// Instance of the Control class. +/// Instance of the Control class. +void RemoteControl::process(Host* host, dmr::Control* dmr, p25::Control* p25) +{ + std::vector args = std::vector(); + args.clear(); + + char buffer[RC_BUFFER_LENGTH]; + in_addr address; + uint32_t port; + + int ret = m_socket.read((uint8_t*)buffer, RC_BUFFER_LENGTH, address, port); + if (ret > 0) { + buffer[ret] = '\0'; + + // parse the original command into a vector of strings. + char* b = buffer; + char* p = NULL; + while ((p = ::strtok(b, " ")) != NULL) { + b = NULL; + args.push_back(std::string(p)); + } + + if (args.size() < 1 || args.at(0U) == "") { + args.clear(); + LogWarning(LOG_RCON, BAD_CMD_STR); + } + else { + std::string rcom = args.at(0); + + // process command + if (rcom == RCD_MODE_CMD && args.size() >= 1U) { + // Command is in the form of: "mode " + if (args.at(1U) == RCD_MODE_OPT_IDLE) { + host->m_fixedMode = false; + host->setMode(STATE_IDLE); + } + else if (args.at(1U) == RCD_MODE_OPT_LCKOUT) { + host->m_fixedMode = false; + host->setMode(HOST_STATE_LOCKOUT); + } + else if (args.at(1U) == RCD_MODE_OPT_FDMR) { + if (dmr != NULL) { + host->m_fixedMode = true; + host->setMode(STATE_DMR); + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (args.at(1U) == RCD_MODE_OPT_FP25) { + if (p25 != NULL) { + host->m_fixedMode = true; + host->setMode(STATE_P25); + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + } + else if (rcom == RCD_KILL_CMD) { + // Command is in the form of: "kill" + g_killed = true; + host->setMode(HOST_STATE_QUIT); + } + else if (rcom == RCD_RID_WLIST_CMD && args.size() >= 1U) { + // Command is in the form of: "rid-whitelist " + uint32_t srcId = getArgUInt32(args, 0U); + if (srcId != 0U) { + m_ridLookup->toggleEntry(srcId, true); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to whitelist RID 0!"); + } + } + else if (rcom == RCD_RID_BLIST_CMD && args.size() >= 1U) { + // Command is in the form of: "rid-blacklist " + uint32_t srcId = getArgUInt32(args, 0U); + if (srcId != 0U) { + m_ridLookup->toggleEntry(srcId, false); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to blacklist RID 0!"); + } + } + else if (rcom == RCD_DMR_BEACON_CMD) { + // Command is in the form of: "dmr-beacon" + if (dmr != NULL) { + g_fireDMRBeacon = true; + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_P25_CC_CMD) { + // Command is in the form of: "p25-cc" + if (p25 != NULL) { + g_fireP25Control = true; + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_DMRD_MDM_INJ_CMD && args.size() >= 1U) { + // Command is in the form of: "dmrd-mdm-inj + if (dmr != NULL) { + uint8_t slot = getArgUInt32(args, 0U); + const char* fileName = getArgString(args, 1U).c_str(); + if (fileName != NULL) { + FILE* file = ::fopen(fileName, "r"); + if (file != NULL) { + uint8_t* buffer = NULL; + int32_t fileSize = 0; + + // obtain file size + ::fseek(file, 0, SEEK_END); + fileSize = ::ftell(file); + ::rewind(file); + + // allocate a buffer and read file + buffer = new uint8_t[fileSize]; + if (buffer != NULL) { + int32_t bytes = ::fread(buffer, 1U, fileSize, file); + if (bytes == fileSize) { + uint8_t sync[dmr::DMR_SYNC_LENGTH_BYTES]; + ::memcpy(sync, buffer, dmr::DMR_SYNC_LENGTH_BYTES); + + // count data sync errors + uint8_t dataErrs = 0U; + for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) + dataErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_DATA_SYNC_BYTES[i]); + + // count voice sync errors + uint8_t voiceErrs = 0U; + for (uint8_t i = 0U; i < dmr::DMR_SYNC_LENGTH_BYTES; i++) + voiceErrs += Utils::countBits8(sync[i] ^ dmr::DMR_MS_VOICE_SYNC_BYTES[i]); + + if ((dataErrs <= 4U) || (voiceErrs <= 4U)) { + if (slot == 0U) { + host->m_modem->injectDMRData1(buffer, fileSize); + } + else if (slot == 1U) { + host->m_modem->injectDMRData2(buffer, fileSize); + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "invalid DMR slot!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR data has too many errors!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR failed to open DMR data!"); + } + + delete buffer; + } + + ::fclose(file); + } + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_P25D_MDM_INJ_CMD && args.size() >= 1U) { + // Command is in the form of: "p25d-mdm-inj + if (p25 != NULL) { + const char* fileName = getArgString(args, 0U).c_str(); + if (fileName != NULL) { + FILE* file = ::fopen(fileName, "r"); + if (file != NULL) { + uint8_t* buffer = NULL; + int32_t fileSize = 0; + + // obtain file size + ::fseek(file, 0, SEEK_END); + fileSize = ::ftell(file); + ::rewind(file); + + // allocate a buffer and read file + buffer = new uint8_t[fileSize]; + if (buffer != NULL) { + int32_t bytes = ::fread(buffer, 1U, fileSize, file); + if (bytes == fileSize) { + uint8_t sync[p25::P25_SYNC_LENGTH_BYTES]; + ::memcpy(sync, buffer, p25::P25_SYNC_LENGTH_BYTES); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < p25::P25_SYNC_LENGTH_BYTES; i++) + errs += Utils::countBits8(sync[i] ^ p25::P25_SYNC_BYTES[i]); + + if (errs <= 4U) { + bool valid = p25->nid().decode(buffer); + if (valid) { + host->m_modem->injectP25Data(buffer, fileSize); + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 data did not contain a valid NID!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 data has too many errors!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 failed to open P25 data!"); + } + + delete buffer; + } + + ::fclose(file); + } + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_DMR_RID_PAGE_CMD && args.size() >= 2U) { + // Command is in the form of: "dmr-rid-page " + if (dmr != NULL) { + uint32_t slotNo = getArgUInt32(args, 0U); + uint32_t dstId = getArgUInt32(args, 1U); + if (slotNo > 0U && slotNo < 3U) { + if (dstId != 0U) { + dmr->writeRF_Call_Alrt(slotNo, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR call alert RID 0!"); + } + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for call alert!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_DMR_RID_CHECK_CMD && args.size() >= 2U) { + // Command is in the form of: "dmr-rid-check " + if (dmr != NULL) { + uint32_t slotNo = getArgUInt32(args, 0U); + uint32_t dstId = getArgUInt32(args, 1U); + if (slotNo > 0U && slotNo < 3U) { + if (dstId != 0U) { + dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_CHECK, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio check RID 0!"); + } + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio check!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_DMR_RID_INHIBIT_CMD && args.size() >= 2U) { + // Command is in the form of: "dmr-rid-inhibit " + if (dmr != NULL) { + uint32_t slotNo = getArgUInt32(args, 0U); + uint32_t dstId = getArgUInt32(args, 1U); + if (slotNo > 0U && slotNo < 3U) { + if (dstId != 0U) { + dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_INHIBIT, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio inhibit RID 0!"); + } + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio inhibit!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_DMR_RID_UNINHIBIT_CMD && args.size() >= 2U) { + // Command is in the form of: "dmr-rid-uninhibit " + if (dmr != NULL) { + uint32_t slotNo = getArgUInt32(args, 0U); + uint32_t dstId = getArgUInt32(args, 1U); + if (slotNo > 0U && slotNo < 3U) { + if (dstId != 0U) { + dmr->writeRF_Ext_Func(slotNo, dmr::DMR_EXT_FNCT_UNINHIBIT, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to DMR radio uninhibit RID 0!"); + } + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "invalid DMR slot number for radio uninhibit!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "DMR mode is not enabled!"); + } + } + else if (rcom == RCD_P25_SET_MFID_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-set-mfid + if (p25 != NULL) { + uint8_t mfId = getArgUInt8(args, 0U); + if (mfId != 0U) { + LogMessage(LOG_RCON, "Remote P25, mfgId = $%02X", mfId); + m_p25MFId = mfId; + } + else { + LogMessage(LOG_RCON, "Remote P25, mfgId reset, mfgId = $%02X", mfId); + m_p25MFId = p25::P25_MFG_STANDARD; + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_PAGE_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-page " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Call_Alrt(p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 call alert RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_CHECK_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-check " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_CHECK, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 radio check RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_INHIBIT_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-inhibit " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_INHIBIT, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 inhibit RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_UNINHIBIT_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-uninhibit " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Ext_Func(p25::P25_EXT_FNCT_UNINHIBIT, p25::P25_WUID_SYS, dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 uninhibit RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_GAQ_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-gaq " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Grp_Aff_Q(dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 grp aff. query RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RID_UREG_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-rid-ureg " + if (p25 != NULL) { + uint32_t dstId = getArgUInt32(args, 0U); + if (dstId != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_U_Reg_Cmd(dstId); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to P25 unit reg. command RID 0!"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_PATCH_CMD && args.size() >= 1U) { + // Command is in the form of: "p25-patch " + if (p25 != NULL) { + uint32_t group1 = getArgUInt32(args, 0U); + uint32_t group2 = getArgUInt32(args, 1U); + uint32_t group3 = getArgUInt32(args, 2U); + + if (group1 != 0U) { + p25->trunk()->setMFId(m_p25MFId); + p25->trunk()->writeRF_TSDU_Mot_Patch(group1, group2, group3); + } + else { + LogError(LOG_RCON, INVALID_OPT_STR "tried to add P25 group patch with no TGID?"); + } + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else if (rcom == RCD_P25_RELEASE_GRANTS) { + // Command is in the form of: "p25-rel-grnts" + if (p25 != NULL) { + p25->trunk()->releaseDstIdGrant(0, true); + } + else { + LogError(LOG_RCON, CMD_FAILED_STR "P25 mode is not enabled!"); + } + } + else { + args.clear(); + LogError(LOG_RCON, BAD_CMD_STR " (\"%s\")", rcom.c_str()); + } + } + } +} + +/// +/// Opens connection to the network. +/// +/// +bool RemoteControl::open() +{ + return m_socket.open(); +} + +/// +/// Closes connection to the network. +/// +void RemoteControl::close() +{ + m_socket.close(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +/// +std::string RemoteControl::getArgString(std::vector args, uint32_t n) const +{ + n += 1; + if (n >= args.size()) + return ""; + + return args.at(n); +} + +/// +/// +/// +/// +/// +/// +uint64_t RemoteControl::getArgUInt64(std::vector args, uint32_t n) const +{ + return (uint64_t)::atol(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +uint32_t RemoteControl::getArgUInt32(std::vector args, uint32_t n) const +{ + return (uint32_t)::atoi(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +int32_t RemoteControl::getArgInt32(std::vector args, uint32_t n) const +{ + return ::atoi(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +uint16_t RemoteControl::getArgUInt16(std::vector args, uint32_t n) const +{ + return (uint16_t)::atoi(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +int16_t RemoteControl::getArgInt16(std::vector args, uint32_t n) const +{ + return (int16_t)::atoi(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +uint8_t RemoteControl::getArgUInt8(std::vector args, uint32_t n) const +{ + return (uint8_t)::atoi(getArgString(args, n).c_str()); +} + +/// +/// +/// +/// +/// +/// +int8_t RemoteControl::getArgInt8(std::vector args, uint32_t n) const +{ + return (int8_t)::atoi(getArgString(args, n).c_str()); +} diff --git a/network/RemoteControl.h b/network/RemoteControl.h new file mode 100644 index 00000000..e72984bd --- /dev/null +++ b/network/RemoteControl.h @@ -0,0 +1,102 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2019 by Jonathan Naylor G4KLX +* Copyright (C) 2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__REMOTE_CONTROL_H__) +#define __REMOTE_CONTROL_H__ + +#include "Defines.h" +#include "network/UDPSocket.h" +#include "dmr/Control.h" +#include "p25/Control.h" +#include "host/Host.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +#include +#include + +// --------------------------------------------------------------------------- +// Class Prototypes +// --------------------------------------------------------------------------- +class HOST_SW_API Host; +namespace dmr { class HOST_SW_API Control; } +namespace p25 { class HOST_SW_API Control; } + +// --------------------------------------------------------------------------- +// Class Declaration +// Implements the remote control networking logic. +// --------------------------------------------------------------------------- + +class HOST_SW_API RemoteControl { +public: + /// Initializes a new instance of the RemoteControl class. + RemoteControl(const std::string& address, uint32_t port); + /// Finalizes a instance of the RemoteControl class. + ~RemoteControl(); + + /// Sets the instances of the Radio ID and Talkgroup ID lookup tables. + void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup); + + /// Process remote network command data. + void process(Host* host, dmr::Control* dmr, p25::Control* p25); + + /// Opens connection to the network. + bool open(); + + /// Closes connection to the network. + void close(); + +private: + network::UDPSocket m_socket; + uint8_t m_p25MFId; + + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + /// + std::string getArgString(std::vector args, uint32_t n) const; + + /// + uint64_t getArgUInt64(std::vector args, uint32_t n) const; + /// + uint32_t getArgUInt32(std::vector args, uint32_t n) const; + /// + int32_t getArgInt32(std::vector args, uint32_t n) const; + /// + uint16_t getArgUInt16(std::vector args, uint32_t n) const; + /// + int16_t getArgInt16(std::vector args, uint32_t n) const; + /// + uint8_t getArgUInt8(std::vector args, uint32_t n) const; + /// + int8_t getArgInt8(std::vector args, uint32_t n) const; +}; + +#endif // __REMOTE_CONTROL_H__ diff --git a/network/UDPSocket.cpp b/network/UDPSocket.cpp new file mode 100644 index 00000000..96d830cd --- /dev/null +++ b/network/UDPSocket.cpp @@ -0,0 +1,318 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2006-2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "network/UDPSocket.h" +#include "Log.h" + +using namespace network; + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the UDPSocket class. +/// +/// Hostname/IP address to connect to. +/// Port number. +UDPSocket::UDPSocket(const std::string& address, uint32_t port) : + m_address(address), + m_port(port), + m_fd(-1) +{ + assert(!address.empty()); +#if defined(_WIN32) || defined(_WIN64) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + LogError(LOG_NET, "Error from WSAStartup"); +#endif +} + +/// +/// Initializes a new instance of the UDPSocket class. +/// +/// Port number. +UDPSocket::UDPSocket(uint32_t port) : + m_address(), + m_port(port), + m_fd(-1) +{ +#if defined(_WIN32) || defined(_WIN64) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + LogError(LOG_NET, "Error from WSAStartup"); +#endif +} + +/// +/// Finalizes a instance of the UDPSocket class. +/// +UDPSocket::~UDPSocket() +{ +#if defined(_WIN32) || defined(_WIN64) + ::WSACleanup(); +#endif +} + +/// +/// Opens UDP socket connection. +/// +/// True, if UDP socket is opened, otherwise false. +bool UDPSocket::open() +{ + m_fd = ::socket(PF_INET, SOCK_DGRAM, 0); + if (m_fd < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Cannot create the UDP socket, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot create the UDP socket, err: %d", errno); +#endif + return false; + } + + if (m_port > 0U) { + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (!m_address.empty()) { +#if defined(_WIN32) || defined(_WIN64) + addr.sin_addr.s_addr = ::inet_addr(m_address.c_str()); +#else + addr.sin_addr.s_addr = ::inet_addr(m_address.c_str()); +#endif + if (addr.sin_addr.s_addr == INADDR_NONE) { + LogError(LOG_NET, "The local address is invalid - %s", m_address.c_str()); + return false; + } + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Cannot set the UDP socket option, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot set the UDP socket option, err: %d", errno); +#endif + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Cannot bind the UDP address, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Cannot bind the UDP address, err: %d", errno); +#endif + return false; + } + } + + return true; +} + +/// +/// Read data from the UDP socket. +/// +/// Buffer to read data into. +/// Length of data to read. +/// IP address to read data from. +/// Port number for remote UDP socket. +/// Actual length of data read from remote UDP socket. +int UDPSocket::read(uint8_t* buffer, uint32_t length, in_addr& address, uint32_t& port) +{ + assert(buffer != NULL); + assert(length > 0U); + + // Check that the readfrom() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(_WIN32) || defined(_WIN64) + FD_SET((uint32_t)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return immediately + timeval tv; + tv.tv_sec = 0L; + tv.tv_usec = 0L; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Error returned from UDP select, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from UDP select, err: %d", errno); +#endif + return -1; + } + + if (ret == 0) + return 0; + + sockaddr_in addr; +#if defined(_WIN32) || defined(_WIN64) + int size = sizeof(sockaddr_in); +#else + socklen_t size = sizeof(sockaddr_in); +#endif + +#if defined(_WIN32) || defined(_WIN64) + int len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); +#else + ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); +#endif + if (len <= 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Error returned from recvfrom, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from recvfrom, err: %d", errno); +#endif + return -1; + } + + address = addr.sin_addr; + port = ntohs(addr.sin_port); + + return len; +} + +/// +/// Write data to the UDP socket. +/// +/// Buffer containing data to write to socket. +/// Length of data to write. +/// IP address to write data to. +/// Port number for remote UDP socket. +/// Actual length of data written to remote UDP socket. +bool UDPSocket::write(const uint8_t* buffer, uint32_t length, const in_addr& address, uint32_t port) +{ + assert(buffer != NULL); + assert(length > 0U); + + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_addr = address; + addr.sin_port = htons(port); + +#if defined(_WIN32) || defined(_WIN64) + int ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); +#else + ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); +#endif + if (ret < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError(LOG_NET, "Error returned from sendto, err: %lu", ::GetLastError()); +#else + LogError(LOG_NET, "Error returned from sendto, err: %d", errno); +#endif + return false; + } + +#if defined(_WIN32) || defined(_WIN64) + if (ret != int(length)) + return false; +#else + if (ret != ssize_t(length)) + return false; +#endif + + return true; +} + +/// +/// Closes the UDP socket connection. +/// +void UDPSocket::close() +{ +#if defined(_WIN32) || defined(_WIN64) + ::closesocket(m_fd); +#else + ::close(m_fd); +#endif +} + +/// +/// Helper to lookup a hostname and resolve it to an IP address. +/// +/// String containing hostname to resolve. +/// IP address structure for containing resolved hostname. +in_addr UDPSocket::lookup(const std::string& hostname) +{ + in_addr addr; +#if defined(_WIN32) || defined(_WIN64) + unsigned long address = ::inet_addr(hostname.c_str()); + if (address != INADDR_NONE && address != INADDR_ANY) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.c_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + LogError(LOG_NET, "Cannot find address for host %s", hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#else + in_addr_t address = ::inet_addr(hostname.c_str()); + if (address != in_addr_t(-1)) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.c_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + LogError(LOG_NET, "Cannot find address for host %s", hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#endif +} diff --git a/network/UDPSocket.h b/network/UDPSocket.h new file mode 100644 index 00000000..6aa9a2f7 --- /dev/null +++ b/network/UDPSocket.h @@ -0,0 +1,88 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__UDP_SOCKET_H__) +#define __UDP_SOCKET_H__ + +#include "Defines.h" + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +namespace network +{ + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements low-level routines to communicate over a UDP + // network socket. + // --------------------------------------------------------------------------- + + class HOST_SW_API UDPSocket { + public: + /// Initializes a new instance of the UDPSocket class. + UDPSocket(const std::string& address, uint32_t port = 0U); + /// Initializes a new instance of the UDPSocket class. + UDPSocket(uint32_t port = 0U); + /// Finalizes a instance of the UDPSocket class. + ~UDPSocket(); + + /// Opens UDP socket connection. + bool open(); + + /// Read data from the UDP socket. + int read(uint8_t* buffer, uint32_t length, in_addr& address, uint32_t& port); + /// Write data to the UDP socket. + bool write(const uint8_t* buffer, uint32_t length, const in_addr& address, uint32_t port); + + /// Closes the UDP socket connection. + void close(); + + /// Helper to lookup a hostname and resolve it to an IP address. + static in_addr lookup(const std::string& hostName); + + private: + std::string m_address; + uint16_t m_port; + int m_fd; + }; +} // namespace Net + +#endif // __UDP_SOCKET_H__ diff --git a/p25/Audio.cpp b/p25/Audio.cpp new file mode 100644 index 00000000..4e426ca7 --- /dev/null +++ b/p25/Audio.cpp @@ -0,0 +1,368 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/Audio.h" +#include "p25/P25Utils.h" +#include "edac/Golay24128.h" +#include "edac/Hamming.h" + +using namespace p25; + +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Audio class. +/// +Audio::Audio() : + m_fec() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Audio class. +/// +Audio::~Audio() +{ + /* stub */ +} + +/// +/// Process P25 IMBE audio data. +/// +/// +/// Number of errors corrected. +uint32_t Audio::process(uint8_t* data) +{ + assert(data != NULL); + + uint32_t errs = 0U; + + uint8_t imbe[18U]; + + P25Utils::decode(data, imbe, 114U, 262U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 114U, 262U); + + P25Utils::decode(data, imbe, 262U, 410U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 262U, 410U); + + P25Utils::decode(data, imbe, 452U, 600U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 452U, 600U); + + P25Utils::decode(data, imbe, 640U, 788U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 640U, 788U); + + P25Utils::decode(data, imbe, 830U, 978U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 830U, 978U); + + P25Utils::decode(data, imbe, 1020U, 1168U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 1020U, 1168U); + + P25Utils::decode(data, imbe, 1208U, 1356U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 1208U, 1356U); + + P25Utils::decode(data, imbe, 1398U, 1546U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 1398U, 1546U); + + P25Utils::decode(data, imbe, 1578U, 1726U); + errs += m_fec.regenerateIMBE(imbe); + P25Utils::encode(imbe, data, 1578U, 1726U); + + return errs; +} + +/// +/// Decode a P25 IMBE audio frame. +/// +/// +/// +/// +void Audio::decode(const uint8_t* data, uint8_t* imbe, uint32_t n) +{ + assert(data != NULL); + assert(imbe != NULL); + + uint8_t temp[18U]; + + switch (n) { + case 0U: + P25Utils::decode(data, temp, 114U, 262U); + break; + case 1U: + P25Utils::decode(data, temp, 262U, 410U); + break; + case 2U: + P25Utils::decode(data, temp, 452U, 600U); + break; + case 3U: + P25Utils::decode(data, temp, 640U, 788U); + break; + case 4U: + P25Utils::decode(data, temp, 830U, 978U); + break; + case 5U: + P25Utils::decode(data, temp, 1020U, 1168U); + break; + case 6U: + P25Utils::decode(data, temp, 1208U, 1356U); + break; + case 7U: + P25Utils::decode(data, temp, 1398U, 1546U); + break; + case 8U: + P25Utils::decode(data, temp, 1578U, 1726U); + break; + default: + return; + } + + bool bit[144U]; + + // De-interleave + for (uint32_t i = 0U; i < 144U; i++) { + uint32_t n = edac::IMBE_INTERLEAVE[i]; + bit[i] = READ_BIT(temp, n); + } + + // now .. + + // 12 voice bits 0 + // 11 golay bits 12 + // + // 12 voice bits 23 + // 11 golay bits 35 + // + // 12 voice bits 46 + // 11 golay bits 58 + // + // 12 voice bits 69 + // 11 golay bits 81 + // + // 11 voice bits 92 + // 4 hamming bits 103 + // + // 11 voice bits 107 + // 4 hamming bits 118 + // + // 11 voice bits 122 + // 4 hamming bits 133 + // + // 7 voice bits 137 + + // c0 + uint32_t c0data = 0U; + for (uint32_t i = 0U; i < 12U; i++) + c0data = (c0data << 1) | (bit[i] ? 0x01U : 0x00U); + + bool prn[114U]; + + // Create the whitening vector and save it for future use + uint32_t p = 16U * c0data; + for (uint32_t i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // De-whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + bit[i + 23U] ^= prn[i]; + + uint32_t offset = 0U; + for (uint32_t i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 0U]); + for (uint32_t i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 23U]); + for (uint32_t i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 46U]); + for (uint32_t i = 0U; i < 12U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 69U]); + for (uint32_t i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 92U]); + for (uint32_t i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 107U]); + for (uint32_t i = 0U; i < 11U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 122U]); + for (uint32_t i = 0U; i < 7U; i++, offset++) + WRITE_BIT(imbe, offset, bit[i + 137U]); +} + +/// +/// Encode a P25 IMBE audio frame. +/// +/// +/// +/// +void Audio::encode(uint8_t* data, const uint8_t* imbe, uint32_t n) +{ + assert(data != NULL); + assert(imbe != NULL); + + bool bTemp[144U]; + bool* bit = bTemp; + + // c0 + uint32_t c0 = 0U; + for (uint32_t i = 0U; i < 12U; i++) { + bool b = READ_BIT(imbe, i); + c0 = (c0 << 1) | (b ? 0x01U : 0x00U); + } + uint32_t g2 = edac::Golay24128::encode23127(c0); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c1 + uint32_t c1 = 0U; + for (uint32_t i = 12U; i < 24U; i++) { + bool b = READ_BIT(imbe, i); + c1 = (c1 << 1) | (b ? 0x01U : 0x00U); + } + g2 = edac::Golay24128::encode23127(c1); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c2 + uint32_t c2 = 0; + for (uint32_t i = 24U; i < 36U; i++) { + bool b = READ_BIT(imbe, i); + c2 = (c2 << 1) | (b ? 0x01U : 0x00U); + } + g2 = edac::Golay24128::encode23127(c2); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c3 + uint32_t c3 = 0U; + for (uint32_t i = 36U; i < 48U; i++) { + bool b = READ_BIT(imbe, i); + c3 = (c3 << 1) | (b ? 0x01U : 0x00U); + } + g2 = edac::Golay24128::encode23127(c3); + for (int i = 23; i >= 0; i--) { + bit[i] = (g2 & 0x01U) == 0x01U; + g2 >>= 1; + } + bit += 23U; + + // c4 + for (uint32_t i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 48U); + edac::Hamming::encode15113_1(bit); + bit += 15U; + + // c5 + for (uint32_t i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 59U); + edac::Hamming::encode15113_1(bit); + bit += 15U; + + // c6 + for (uint32_t i = 0U; i < 11U; i++) + bit[i] = READ_BIT(imbe, i + 70U); + edac::Hamming::encode15113_1(bit); + bit += 15U; + + // c7 + for (uint32_t i = 0U; i < 7U; i++) + bit[i] = READ_BIT(imbe, i + 81U); + + bool prn[114U]; + + // Create the whitening vector and save it for future use + uint32_t p = 16U * c0; + for (uint32_t i = 0U; i < 114U; i++) { + p = (173U * p + 13849U) % 65536U; + prn[i] = p >= 32768U; + } + + // Whiten some bits + for (uint32_t i = 0U; i < 114U; i++) + bTemp[i + 23U] ^= prn[i]; + + uint8_t temp[18U]; + + // Interleave + for (uint32_t i = 0U; i < 144U; i++) { + uint32_t n = edac::IMBE_INTERLEAVE[i]; + WRITE_BIT(temp, n, bTemp[i]); + } + + switch (n) { + case 0U: + P25Utils::encode(temp, data, 114U, 262U); + break; + case 1U: + P25Utils::encode(temp, data, 262U, 410U); + break; + case 2U: + P25Utils::encode(temp, data, 452U, 600U); + break; + case 3U: + P25Utils::encode(temp, data, 640U, 788U); + break; + case 4U: + P25Utils::encode(temp, data, 830U, 978U); + break; + case 5U: + P25Utils::encode(temp, data, 1020U, 1168U); + break; + case 6U: + P25Utils::encode(temp, data, 1208U, 1356U); + break; + case 7U: + P25Utils::encode(temp, data, 1398U, 1546U); + break; + case 8U: + P25Utils::encode(temp, data, 1578U, 1726U); + break; + default: + return; + } +} diff --git a/p25/Audio.h b/p25/Audio.h new file mode 100644 index 00000000..45ae363e --- /dev/null +++ b/p25/Audio.h @@ -0,0 +1,63 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_AUDIO_H__) +#define __P25_AUDIO_H__ + +#include "Defines.h" +#include "edac/AMBEFEC.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements P25 audio processing and interleaving. + // --------------------------------------------------------------------------- + + class HOST_SW_API Audio { + public: + /// Initializes a new instance of the Audio class. + Audio(); + /// Finalizes a instance of the Audio class. + ~Audio(); + + /// Process P25 IMBE audio data. + uint32_t process(uint8_t* data); + + /// Decode a P25 IMBE audio frame. + void decode(const uint8_t* data, uint8_t* imbe, uint32_t n); + /// Encode a P25 IMBE audio frame. + void encode(uint8_t* data, const uint8_t* imbe, uint32_t n); + + private: + edac::AMBEFEC m_fec; + }; +} // namespace p25 + +#endif // __P25_AUDIO_H__ diff --git a/p25/Control.cpp b/p25/Control.cpp new file mode 100644 index 00000000..d795eddc --- /dev/null +++ b/p25/Control.cpp @@ -0,0 +1,821 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/Control.h" +#include "p25/acl/AccessControl.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "edac/CRC.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t TSBK_PCH_CCH_CNT = 6U; +const uint32_t MAX_PREAMBLE_CNT = 85U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Control class. +/// +/// P25 Network Access Code. +/// Amount of hangtime for a P25 call. +/// Modem frame buffer queue size (bytes). +/// Instance of the Modem class. +/// Instance of the BaseNetwork class. +/// Transmit timeout. +/// Amount of time to hang on the last talkgroup mode from RF. +/// Control Channel Broadcast Interval. +/// Flag indicating full-duplex operation. +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +/// Instance of the IdenTableLookup class. +/// Instance of the CRSSIInterpolator class. +/// +/// +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +Control::Control(uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::BaseNetwork* network, + uint32_t timeout, uint32_t tgHang, uint32_t ccBcstInterval, bool duplex, lookups::RadioIdLookup* ridLookup, + lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, + bool dumpPDUData, bool repeatPDU, bool debug, bool verbose) : + m_voice(NULL), + m_data(NULL), + m_trunk(NULL), + m_nac(nac), + m_timeout(timeout), + m_modem(modem), + m_network(network), + m_inhibitIllegal(false), + m_legacyGroupGrnt(true), + m_duplex(duplex), + m_control(false), + m_continuousControl(false), + m_voiceOnControl(false), + m_idenTable(idenTable), + m_ridLookup(ridLookup), + m_tidLookup(tidLookup), + m_queue(queueSize, "P25 Control"), + m_rfState(RS_RF_LISTENING), + m_rfLastDstId(0U), + m_netState(RS_NET_IDLE), + m_netLastDstId(0U), + m_tailOnIdle(false), + m_ccOnIdle(false), + m_ccRunning(false), + m_ccBcstInterval(ccBcstInterval), + m_rfTimeout(1000U, timeout), + m_netTimeout(1000U, timeout), + m_networkWatchdog(1000U, 0U, 1500U), + m_networkTGHang(1000U, tgHang), + m_hangCount(3U * 8U), + m_preambleCount(0U), + m_ccFrameCnt(0U), + m_nid(nac), + m_rssiMapper(rssiMapper), + m_rssi(0U), + m_maxRSSI(0U), + m_minRSSI(0U), + m_aveRSSI(0U), + m_rssiCount(0U), + m_verbose(verbose), + m_debug(debug) +{ + assert(ridLookup != NULL); + assert(tidLookup != NULL); + assert(idenTable != NULL); + assert(rssiMapper != NULL); + + acl::AccessControl::init(m_ridLookup, m_tidLookup); + + m_hangCount = callHang * 4U; + + m_voice = new VoicePacket(this, network, debug, verbose); + m_data = new DataPacket(this, network, dumpPDUData, repeatPDU, debug, verbose); + m_trunk = new TrunkPacket(this, network, debug, verbose); +} + +/// +/// Finalizes a instance of the Control class. +/// +Control::~Control() +{ + delete m_voice; + delete m_data; + delete m_trunk; +} + +/// +/// Resets the data states for the RF interface. +/// +void Control::reset() +{ + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + + m_voice->resetRF(); + m_trunk->resetRF(); + m_data->resetRF(); + m_queue.clear(); +} + +/// +/// Helper to set P25 configuration options. +/// +/// Instance of the ConfigINI class. +/// Flag indicating this instance should operate in network only mode. +void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, + uint32_t channelNo, bool printOptions) +{ + yaml::Node systemConf = conf["system"]; + yaml::Node p25Protocol = conf["protocols"]["p25"]; + + m_trunk->setCallsign(cwCallsign); + + m_preambleCount = p25Protocol["preambleCount"].as(4U); + + m_trunk->m_patchSuperGroup = pSuperGroup; + m_trunk->setSiteData(netId, sysId, rfssId, siteId, 0U, channelId, channelNo); + + m_inhibitIllegal = p25Protocol["inhibitIllegal"].as(false); + m_legacyGroupGrnt = p25Protocol["legacyGroupGrnt"].as(true); + + m_trunk->m_verifyAff = p25Protocol["verifyAff"].as(false); + m_trunk->m_verifyReg = p25Protocol["verifyReg"].as(false); + + m_trunk->m_noStatusAck = p25Protocol["noStatusAck"].as(false); + m_trunk->m_noMessageAck = p25Protocol["noMessageAck"].as(true); + + yaml::Node statusCmd = p25Protocol["statusCmd"]; + m_trunk->m_statusCmdEnable = statusCmd["enable"].as(false); + m_trunk->m_statusRadioCheck = (uint8_t)statusCmd["radioCheck"].as(0U); + m_trunk->m_statusRadioInhibit = (uint8_t)statusCmd["radioInhibit"].as(0U); + m_trunk->m_statusRadioUninhibit = (uint8_t)statusCmd["radioUninhibit"].as(0U); + m_trunk->m_statusRadioForceReg = (uint8_t)statusCmd["radioForceReg"].as(0U); + m_trunk->m_statusRadioForceDereg = (uint8_t)statusCmd["radioForceDereg"].as(0U); + + yaml::Node control = p25Protocol["control"]; + m_control = control["enable"].as(false); + if (m_control) { + m_continuousControl = control["continuous"].as(false); + } + else { + m_continuousControl = false; + } + + m_voiceOnControl = p25Protocol["voiceOnControl"].as(false); + + m_voice->m_silenceThreshold = p25Protocol["silenceThreshold"].as(p25::DEFAULT_SILENCE_THRESHOLD); + + std::vector availCh = voiceChNo; + m_trunk->m_voiceChCnt = (uint8_t)availCh.size(); + m_trunk->setSiteChCnt((uint8_t)availCh.size()); + + for (auto it = availCh.begin(); it != availCh.end(); ++it) { + m_trunk->m_voiceChTable.push_back(*it); + } + + if (printOptions) { + LogInfo(" Silence Threshold: %u (%.1f%%)", m_voice->m_silenceThreshold, float(m_voice->m_silenceThreshold) / 12.33F); + + if (m_control) { + LogInfo(" Voice on Control: %s", m_voiceOnControl ? "yes" : "no"); + } + + LogInfo(" Inhibit Illegal: %s", m_inhibitIllegal ? "yes" : "no"); + LogInfo(" Legacy Group Grant: %s", m_legacyGroupGrnt ? "yes" : "no"); + LogInfo(" Verify Affiliation: %s", m_trunk->m_verifyAff ? "yes" : "no"); + LogInfo(" Verify Registration: %s", m_trunk->m_verifyReg ? "yes" : "no"); + + LogInfo(" No Status ACK: %s", m_trunk->m_noStatusAck ? "yes" : "no"); + LogInfo(" No Message ACK: %s", m_trunk->m_noMessageAck ? "yes" : "no"); + LogInfo(" Status Command Support: %s", m_trunk->m_statusCmdEnable ? "yes" : "no"); + if (m_trunk->m_statusCmdEnable) { + LogInfo(" Status Radio Check: $%02X", m_trunk->m_statusRadioCheck); + LogInfo(" Status Radio Inhibit: $%02X", m_trunk->m_statusRadioInhibit); + LogInfo(" Status Radio Uninhibit: $%02X", m_trunk->m_statusRadioUninhibit); + LogInfo(" Status Radio Force Register: $%02X", m_trunk->m_statusRadioForceReg); + LogInfo(" Status Radio Force Deregister: $%02X", m_trunk->m_statusRadioForceDereg); + } + } +} + +/// +/// Sets a flag indicating whether the P25 control channel is running. +/// +/// +void Control::setCCRunning(bool ccRunning) +{ + m_ccRunning = ccRunning; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool Control::processFrame(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + // Utils::dump(2U, "!!! *RX P25 Raw", data, len); + + bool sync = data[1U] == 0x01U; + + if (data[0U] == TAG_LOST && m_rfState == RS_RF_AUDIO) { + if (m_rssi != 0U) { + ::ActivityLog("P25", true, "transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", + float(m_voice->m_rfFrames) / 5.56F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); + } + else { + ::ActivityLog("P25", true, "transmission lost, %.1f seconds, BER: %.1f%%", + float(m_voice->m_rfFrames) / 5.56F, float(m_voice->m_rfErrs * 100U) / float(m_voice->m_rfBits)); + } + + LogMessage(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)); + + if (m_control) { + m_trunk->releaseDstIdGrant(m_voice->m_rfLC.getDstId(), false); + } + + writeRF_TDU(false); + m_voice->m_lastDUID = P25_DUID_TDU; + m_voice->writeNetworkRF(data + 2U, P25_DUID_TDU); + + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + + m_tailOnIdle = true; + + m_rfTimeout.stop(); + m_queue.clear(); + + if (m_network != NULL) + m_network->resetP25(); + + return false; + } + + if (data[0U] == TAG_LOST && m_rfState == RS_RF_DATA) { + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + + m_tailOnIdle = true; + + m_data->resetRF(); + + m_rfTimeout.stop(); + m_queue.clear(); + + return false; + } + + if (data[0U] == TAG_LOST) { + m_rfState = RS_RF_LISTENING; + m_rfLastDstId = 0U; + + m_voice->resetRF(); + m_trunk->resetRF(); + m_data->resetRF(); + + return false; + } + + if (!sync && m_rfState == RS_RF_LISTENING) { + if (m_verbose) { + uint8_t sync[P25_SYNC_LENGTH_BYTES]; + ::memcpy(sync, data + 2U, P25_SYNC_LENGTH_BYTES); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < P25_SYNC_LENGTH_BYTES; i++) + errs += Utils::countBits8(sync[i] ^ P25_SYNC_BYTES[i]); + + LogDebug(LOG_RF, "P25, possible sync word rejected, errs = %u", errs); + } + return false; + } + + if (sync && m_debug) { + Utils::dump(2U, "!!! *RX P25 Frame", data, len); + } + + // Decode the NID + bool valid = m_nid.decode(data + 2U); + + if (!valid && m_rfState == RS_RF_LISTENING) + return false; + + uint8_t duid = m_nid.getDUID(); + + // Have we got RSSI bytes on the end of a P25 LDU? + if (len == (P25_LDU_FRAME_LENGTH_BYTES + 4U)) { + uint16_t raw = 0U; + raw |= (data[218U] << 8) & 0xFF00U; + raw |= (data[219U] << 0) & 0x00FFU; + + // 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); + } + + // RSSI is always reported as positive + m_rssi = (rssi >= 0) ? rssi : -rssi; + + if (m_rssi > m_minRSSI) + m_minRSSI = m_rssi; + if (m_rssi < m_maxRSSI) + m_maxRSSI = m_rssi; + + m_aveRSSI += m_rssi; + m_rssiCount++; + } + + if (m_debug) { + LogDebug(LOG_RF, "P25, rfState = %u, netState = %u, DUID = %u, lastDUID = %u", m_rfState, m_netState, duid, m_voice->m_lastDUID); + } + + // are we interrupting a running CC? + if (m_ccRunning) { + if (duid != P25_DUID_TSDU) { + g_interruptP25Control = true; + } + } + + bool ret = false; + + // handle individual DUIDs + switch (duid) { + case P25_DUID_HDU: + case P25_DUID_LDU1: + case P25_DUID_LDU2: + ret = m_voice->process(data, len); + break; + + case P25_DUID_TDU: + case P25_DUID_TDULC: + ret = m_voice->process(data, len); + break; + + case P25_DUID_PDU: + ret = m_data->process(data, len); + break; + + case P25_DUID_TSDU: + ret = m_trunk->process(data, len); + break; + + default: + LogError(LOG_RF, "P25 unhandled DUID, duid = $%02X", duid); + return false; + } + + return ret; +} + +/// +/// Get frame data from data ring buffer. +/// +/// Buffer to store frame data. +/// Length of frame data retreived. +uint32_t Control::getFrame(uint8_t* data) +{ + assert(data != NULL); + + if (m_queue.isEmpty()) + return 0U; + + uint8_t len = 0U; + m_queue.getData(&len, 1U); + + m_queue.getData(data, len); + + return len; +} + +/// +/// Helper to write P25 adjacent site information to the network. +/// +void Control::writeAdjSSNetwork() +{ + m_trunk->writeAdjSSNetwork(); +} + +/// +/// Helper to write control channel frame data. +/// +/// +bool Control::writeControlRF() +{ + if (!m_control) { + return false; + } + + if (m_ccFrameCnt == 254U) { + m_ccFrameCnt = 0U; + } + + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { + m_trunk->writeRF_ControlData(m_ccFrameCnt, true); + m_ccFrameCnt++; + return true; + } + + return false; +} + +/// +/// Helper to write end of control channel frame data. +/// +/// +bool Control::writeControlEndRF() +{ + if (!m_control) { + return false; + } + + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { + for (uint32_t i = 0; i < TSBK_PCH_CCH_CNT; i++) { + m_trunk->queueRF_TSBK_Ctrl_MBF(TSBK_OSP_MOT_PSH_CCH); + } + + writeRF_Nulls(); + return true; + } + + return false; +} + +/// +/// Helper to write end of frame data. +/// +/// +bool Control::writeEndRF() +{ + if (m_netState == RS_NET_IDLE && m_rfState == RS_RF_LISTENING) { + if (m_tailOnIdle) { + bool ret = m_voice->writeEndRF(); + if (!m_control) { + writeRF_Nulls(); + } + + return ret; + } + + if (m_ccOnIdle) { + g_fireP25Control = true; + m_ccOnIdle = false; + } + } + + return false; +} + +/// +/// Updates the processor by the passed number of milliseconds. +/// +/// +void Control::clock(uint32_t ms) +{ + if (m_network != NULL) { + processNetwork(); + + if (m_network->getStatus() == network::NET_STAT_RUNNING) { + m_trunk->setNetActive(true); + } + else { + m_trunk->setNetActive(false); + } + } + + m_rfTimeout.clock(ms); + m_netTimeout.clock(ms); + + if (m_networkTGHang.isRunning()) { + m_networkTGHang.clock(ms); + + if (m_networkTGHang.hasExpired()) { + m_networkTGHang.stop(); + m_rfLastDstId = 0U; + } + } + + if (m_netState == RS_NET_AUDIO || m_netState == RS_NET_DATA) { + m_networkWatchdog.clock(ms); + + if (m_networkWatchdog.hasExpired()) { + if (m_netState == RS_NET_AUDIO) { + ::ActivityLog("P25", false, "network watchdog has expired, %.1f seconds, %u%% packet loss", + float(m_voice->m_netFrames) / 50.0F, (m_voice->m_netLost * 100U) / m_voice->m_netFrames); + } + else { + ::ActivityLog("P25", false, "network watchdog has expired"); + } + + m_networkWatchdog.stop(); + + if (m_control) { + m_trunk->releaseDstIdGrant(m_voice->m_netLC.getDstId(), false); + } + + if (m_continuousControl) { + if (m_network != NULL) + m_network->resetP25(); + } + + m_netState = RS_NET_IDLE; + m_tailOnIdle = true; + + m_voice->resetNet(); + m_trunk->resetNet(); + + m_netTimeout.stop(); + } + } + + if (m_rfState == RS_RF_REJECTED) { + m_queue.clear(); + + m_voice->resetRF(); + m_voice->m_rfLastHDU.reset(); + m_voice->resetNet(); + m_trunk->resetRF(); + m_trunk->resetNet(); + m_data->resetRF(); + + if (m_network != NULL) + m_network->resetP25(); + + m_rfState = RS_RF_LISTENING; + } + + m_trunk->clock(ms); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Write data processed from RF to the data ring buffer. +/// +/// +/// +void Control::writeQueueRF(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + + if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired()) + return; + + uint32_t space = m_queue.freeSpace(); + if (space < (length + 1U)) { + uint32_t queueLen = m_queue.length(); + m_queue.resize(queueLen + 2500); + + LogError(LOG_P25, "overflow in the P25 RF queue; queue resized was %u is %u", queueLen, m_queue.length()); + return; + } + + uint8_t len = length; + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +/// +/// Write data processed from the network to the data ring buffer. +/// +/// +/// +void Control::writeQueueNet(const uint8_t* data, uint32_t length) +{ + assert(data != NULL); + + if (m_netTimeout.isRunning() && m_netTimeout.hasExpired()) + return; + + uint32_t space = m_queue.freeSpace(); + if (space < (length + 1U)) { + LogError(LOG_P25, "network overflow in the P25 RF queue"); + return; + } + + uint8_t len = length; + m_queue.addData(&len, 1U); + + m_queue.addData(data, len); +} + +/// +/// Process a data frames from the network. +/// +void Control::processNetwork() +{ + if (m_rfState != RS_RF_LISTENING && m_netState == RS_NET_IDLE) + return; + + lc::LC control; + data::LowSpeedData lsd; + uint8_t duid; + + uint32_t length = 100U; + bool ret = false; + uint8_t* data = m_network->readP25(ret, control, lsd, duid, length); + if (!ret) + return; + if (length == 0U) + return; + if (data == NULL) { + m_network->resetP25(); + return; + } + + m_networkWatchdog.start(); + + if (m_debug) { + Utils::dump(2U, "!!! *RX P25 Network Frame - Data Bytes", data, length); + } + + switch (duid) { + case P25_DUID_HDU: + case P25_DUID_LDU1: + case P25_DUID_LDU2: + case P25_DUID_TDU: + case P25_DUID_TDULC: + m_voice->processNetwork(data, length, control, lsd, duid); + break; + + case P25_DUID_PDU: + m_data->processNetwork(data, length, control, lsd, duid); + break; + + case P25_DUID_TSDU: + m_trunk->processNetwork(data, length, control, lsd, duid); + break; + } + + delete data; +} + +/// +/// Helper to write data nulls. +/// +void Control::writeRF_Nulls() +{ + const uint8_t NULLS_LENGTH_BYTES = 25U; + uint8_t data[NULLS_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NULLS_LENGTH_BYTES); + + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + // fill nulls + for (uint8_t i = 0; i < NULLS_LENGTH_BYTES; i++) { + data[i + 2U] = 0x00U; + } + + writeQueueRF(data, NULLS_LENGTH_BYTES + 2U); +} + +/// +/// Helper to write preamble packet burst. +/// +void Control::writeRF_Preamble() +{ + if (m_modem->hasTX() || m_preambleCount == 0U) { + return; + } + + if (m_ccRunning) { + return; + } + + if (m_preambleCount > MAX_PREAMBLE_CNT) { + m_preambleCount = MAX_PREAMBLE_CNT; + } + + writeRF_Nulls(); + for (uint8_t i = 0U; i < m_preambleCount; i++) { + writeRF_TDU(true); + } +} + +/// +/// Helper to write a P25 TDU packet. +/// +/// +void Control::writeRF_TDU(bool noNetwork) +{ + uint8_t data[P25_TDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TDU_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_nid.encode(data + 2U, P25_DUID_TDU); + + // Add busy bits + addBusyBits(data + 2U, P25_TDU_FRAME_LENGTH_BITS, true, true); + + if (!noNetwork) + m_voice->writeNetworkRF(data + 2U, P25_DUID_TDU); + + if (m_duplex) { + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + writeQueueRF(data, P25_TDU_FRAME_LENGTH_BYTES + 2U); + } +} + +/// +/// +/// +void Control::checkAndReject() +{ + if (m_rfState != RS_RF_LISTENING && m_rfState != RS_RF_DATA) { + LogWarning(LOG_RF, "rejected operation (not setting RF rejection)"); + } + else { + m_rfState = RS_RF_REJECTED; + LogWarning(LOG_RF, "rejected operation"); + } +} + +/// +/// Helper to set the busy status bits on P25 frame data. +/// +/// +/// +/// +/// +void Control::setBusyBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2) +{ + assert(data != NULL); + + WRITE_BIT(data, ssOffset, b1); + WRITE_BIT(data, ssOffset + 1U, b2); +} + +/// +/// Helper to add the busy status bits on P25 frame data. +/// +/// +/// +/// +/// +void Control::addBusyBits(uint8_t* data, uint32_t length, bool b1, bool b2) +{ + assert(data != NULL); + + for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_SS_INCREMENT) { + uint32_t ss1Pos = ss0Pos + 1U; + WRITE_BIT(data, ss0Pos, b1); + WRITE_BIT(data, ss1Pos, b2); + } +} diff --git a/p25/Control.h b/p25/Control.h new file mode 100644 index 00000000..c4004185 --- /dev/null +++ b/p25/Control.h @@ -0,0 +1,193 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_CONTROL_H__) +#define __P25_CONTROL_H__ + +#include "Defines.h" +#include "p25/TrunkPacket.h" +#include "p25/DataPacket.h" +#include "p25/VoicePacket.h" +#include "p25/NID.h" +#include "network/BaseNetwork.h" +#include "network/RemoteControl.h" +#include "lookups/RSSIInterpolator.h" +#include "lookups/IdenTableLookup.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" +#include "modem/Modem.h" +#include "RingBuffer.h" +#include "Timer.h" +#include "yaml/Yaml.h" + +#include + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API VoicePacket; + class HOST_SW_API DataPacket; + class HOST_SW_API TrunkPacket; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements core logic for handling P25. + // --------------------------------------------------------------------------- + + class HOST_SW_API Control { + public: + /// Initializes a new instance of the Control class. + Control(uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::BaseNetwork* network, + uint32_t timeout, uint32_t tgHang, uint32_t ccBcstInterval, bool duplex, lookups::RadioIdLookup* ridLookup, + lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, + bool dumpPDUData, bool repeatPDU, bool debug, bool verbose); + /// Finalizes a instance of the Control class. + ~Control(); + + /// Resets the data states for the RF interface. + void reset(); + + /// Helper to set P25 configuration options. + void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, + uint32_t pSuperGroup, uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, + uint8_t channelId, uint32_t channelNo, bool printOptions); + /// Sets a flag indicating whether the P25 control channel is running. + void setCCRunning(bool ccRunning); + + /// Process a data frame from the RF interface. + bool processFrame(uint8_t* data, uint32_t len); + /// Get frame data from data ring buffer. + uint32_t getFrame(uint8_t* data); + + /// Helper to write P25 adjacent site information to the network. + void writeAdjSSNetwork(); + + /// Helper to write control channel frame data. + bool writeControlRF(); + /// Helper to write end of control channel frame data. + bool writeControlEndRF(); + /// Helper to write end of frame data. + bool writeEndRF(); + + /// Updates the processor by the passed number of milliseconds. + void clock(uint32_t ms); + + /// + NID nid() { return m_nid; } + /// + TrunkPacket* trunk() { return m_trunk; } + + private: + friend class VoicePacket; + VoicePacket* m_voice; + friend class DataPacket; + DataPacket* m_data; + friend class TrunkPacket; + TrunkPacket* m_trunk; + + uint32_t m_nac; + uint32_t m_timeout; + + modem::Modem* m_modem; + network::BaseNetwork* m_network; + + bool m_inhibitIllegal; + bool m_legacyGroupGrnt; + + bool m_duplex; + bool m_control; + bool m_continuousControl; + bool m_voiceOnControl; + + lookups::IdenTableLookup* m_idenTable; + lookups::RadioIdLookup* m_ridLookup; + lookups::TalkgroupIdLookup* m_tidLookup; + + RingBuffer m_queue; + + RPT_RF_STATE m_rfState; + uint32_t m_rfLastDstId; + RPT_NET_STATE m_netState; + uint32_t m_netLastDstId; + + bool m_tailOnIdle; + bool m_ccOnIdle; + bool m_ccRunning; + uint32_t m_ccBcstInterval; + + Timer m_rfTimeout; + Timer m_netTimeout; + Timer m_networkWatchdog; + Timer m_networkTGHang; + + uint32_t m_hangCount; + uint32_t m_preambleCount; + uint8_t m_ccFrameCnt; + + NID m_nid; + + lookups::RSSIInterpolator* m_rssiMapper; + uint8_t m_rssi; + uint8_t m_maxRSSI; + uint8_t m_minRSSI; + uint32_t m_aveRSSI; + uint32_t m_rssiCount; + + bool m_verbose; + bool m_debug; + + /// Write data processed from RF to the data ring buffer. + void writeQueueRF(const uint8_t* data, uint32_t length); + /// Write data processed from the network to the data ring buffer. + void writeQueueNet(const uint8_t* data, uint32_t length); + + /// Process a data frames from the network. + void processNetwork(); + + /// Helper to write data nulls. + void writeRF_Nulls(); + /// Helper to write preamble packet burst. + void writeRF_Preamble(); + /// Helper to write a P25 TDU packet. + void writeRF_TDU(bool noNetwork); + + /// + void checkAndReject(); + + /// Helper to set the busy status bits on P25 frame data. + void setBusyBits(uint8_t* data, uint32_t ssOffset, bool b1, bool b2); + /// Helper to add the busy status bits on P25 frame data. + void addBusyBits(uint8_t* data, uint32_t length, bool b1, bool b2); + }; +} // namespace p25 + +#endif // __P25_CONTROL_H__ diff --git a/p25/DataPacket.cpp b/p25/DataPacket.cpp new file mode 100644 index 00000000..cf27190d --- /dev/null +++ b/p25/DataPacket.cpp @@ -0,0 +1,942 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/DataPacket.h" +#include "p25/acl/AccessControl.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "edac/CRC.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; + +#include +#include +#include +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Resets the data states for the RF interface. +/// +void DataPacket::resetRF() +{ + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + + m_rfDataHeader.reset(); +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool DataPacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + // decode the NID + bool valid = m_p25->m_nid.decode(data + 2U); + + if (m_p25->m_rfState == RS_RF_LISTENING && !valid) + return false; + + if (m_prevRfState != RS_RF_DATA) { + m_prevRfState = m_p25->m_rfState; + } + + uint8_t duid = m_p25->m_nid.getDUID(); + + // are we interrupting a running CC? + if (m_p25->m_ccRunning) { + g_interruptP25Control = true; + } + + // handle individual DUIDs + if (duid == P25_DUID_PDU) { + m_p25->m_trunk->resetStatusCommand(); + + if (m_p25->m_rfState != RS_RF_DATA) { + m_rfDataHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + + m_p25->m_rfState = RS_RF_DATA; + + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_COUNT * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + } + + uint32_t start = m_rfPDUCount * P25_LDU_FRAME_LENGTH_BITS; + + uint8_t buffer[P25_MAX_PDU_LENGTH]; + ::memset(buffer, 0x00U, P25_MAX_PDU_LENGTH); + + uint32_t bits = P25Utils::decode(data + 2U, buffer, start, start + P25_LDU_FRAME_LENGTH_BITS); + m_rfPDUBits = Utils::getBits(buffer, m_rfPDU, 0U, bits); + + // Utils::dump(2U, "* !!! P25_DUID_PDU - m_rfPDU", m_rfPDU, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + 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, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + + m_rfDataHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u, hdrOffset = %u", + m_rfDataHeader.getAckNeeded(), m_rfDataHeader.getOutbound(), m_rfDataHeader.getFormat(), m_rfDataHeader.getSAP(), m_rfDataHeader.getFullMessage(), + m_rfDataHeader.getBlocksToFollow(), m_rfDataHeader.getPadCount(), m_rfDataHeader.getN(), m_rfDataHeader.getSeqNo(), + m_rfDataHeader.getHeaderOffset()); + } + + // make sure we don't get a PDU with more blocks then we support + if (m_rfDataHeader.getBlocksToFollow() >= P25_MAX_PDU_COUNT) { + LogError(LOG_RF, P25_PDU_STR ", too many PDU blocks to process, %u > %u", m_rfDataHeader.getBlocksToFollow(), P25_MAX_PDU_COUNT); + + m_rfDataHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; + } + + writeNetworkRF(P25_DT_DATA_HEADER, buffer, P25_PDU_FEC_LENGTH_BYTES); + + ::ActivityLog("P25", true, "received RF data transmission from %u to %u, %u blocks", m_rfDataHeader.getLLId(), m_rfDataHeader.getLLId(), m_rfDataHeader.getBlocksToFollow()); + } + + if (m_p25->m_rfState == RS_RF_DATA) { + uint32_t blocksToFollow = m_rfDataHeader.getBlocksToFollow(); + + // confirmed extended addressing is unsupported + if (m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR && + m_rfDataHeader.getFormat() == PDU_FMT_CONFIRMED) { + LogWarning(LOG_RF, P25_PDU_STR ", unsupported confirmed enhanced addressing"); + + m_rfDataHeader.reset(); + m_rfSecondHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; + } + + // process second header if we're using enhanced addressing + if (m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR) { + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + Utils::getBitRange(m_rfPDU, buffer, offset, P25_PDU_FEC_LENGTH_BITS); + bool ret = m_rfSecondHeader.decode(buffer); + if (!ret) { + LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); + Utils::dump(1U, "Unfixable PDU Data", m_rfPDU + offset, P25_PDU_HEADER_LENGTH_BYTES); + + m_rfDataHeader.reset(); + m_rfSecondHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + m_p25->m_rfState = m_prevRfState; + return false; + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", fmt = $%02X, sap = $%02X, srcId = %u", + m_rfSecondHeader.getFormat(), m_rfSecondHeader.getSAP(), m_rfSecondHeader.getLLId()); + } + + writeNetworkRF(P25_DT_DATA_SEC_HEADER, buffer, P25_PDU_FEC_LENGTH_BYTES); + + offset += P25_PDU_FEC_LENGTH_BITS; + m_rfPDUCount++; + blocksToFollow--; + } + + m_rfPDUCount++; + uint32_t bitLength = ((blocksToFollow + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + + if (m_rfPDUBits >= bitLength) { + uint32_t dataOffset = 0U; + for (uint32_t i = 0U; i < blocksToFollow; i++) { + ::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.getSAP() == PDU_SAP_EXT_ADDR) ? m_rfSecondHeader : m_rfDataHeader); + if (ret) { + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", block %u, fmt = $%02X", + (m_rfDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? m_rfData[i].getSerialNo() : m_rfDataBlockCnt, m_rfData[i].getFormat()); + } + + m_rfData[i].getData(m_pduUserData + dataOffset); + m_rfDataBlockCnt++; + + writeNetworkRF(P25_DT_DATA, buffer, P25_PDU_FEC_LENGTH_BYTES); + } + else { + if (m_rfData[i].getHalfRateTrellis()) + LogWarning(LOG_RF, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC)"); + else + LogWarning(LOG_RF, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC)"); + + if (m_dumpPDUData) { + Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + } + } + + offset += P25_PDU_FEC_LENGTH_BITS; + dataOffset += (m_rfDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } + + if (m_dumpPDUData) { + Utils::dump(1U, "PDU Packet", m_pduUserData, dataOffset); + } + + if (m_rfDataBlockCnt < blocksToFollow) { + LogWarning(LOG_RF, P25_PDU_STR ", incomplete PDU (%d / %d blocks)", m_rfDataBlockCnt, blocksToFollow); + } + + switch (m_rfDataHeader.getSAP()) { + case PDU_SAP_REG: + { + uint8_t regType = (m_pduUserData[0] >> 4) & 0x0F; + switch (regType) { + case PDU_REG_TYPE_REQ_CNCT: + { + uint32_t llId = (m_pduUserData[1U] << 16) + (m_pduUserData[2U] << 8) + m_pduUserData[3U]; + ulong64_t ipAddr = (m_pduUserData[8U] << 24) + (m_pduUserData[9U] << 16) + + (m_pduUserData[10U] << 8) + m_pduUserData[11U]; + + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", PDU_REG_TYPE_REQ_CNCT (Registration Request Connect), llId = %u, ipAddr = %u", llId, ipAddr); + } + + if (!hasLLIdFNEReg(llId)) { + // update dynamic FNE registration table entry + m_fneRegTable[llId] = ipAddr; + + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", PDU_REG_TYPE_RSP_ACCPT (Registration Response Accept), llId = %u, ipAddr = %u", llId, ipAddr); + } + writeRF_PDU_Reg_Response(PDU_REG_TYPE_RSP_ACCPT, llId, ipAddr); + } + else { + LogWarning(LOG_RF, P25_PDU_STR ", PDU_REG_TYPE_RSP_DENY (Registration Response Deny), llId = %u, ipAddr = %u", llId, ipAddr); + writeRF_PDU_Reg_Response(PDU_REG_TYPE_RSP_DENY, llId, ipAddr); + } + } + break; + case PDU_REG_TYPE_REQ_DISCNCT: + { + uint32_t llId = (m_pduUserData[1U] << 16) + (m_pduUserData[2U] << 8) + m_pduUserData[3U]; + + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", PDU_REG_TYPE_REQ_DISCNCT (Registration Request Disconnect), llId = %u", llId); + } + + if (hasLLIdFNEReg(llId)) { + // remove dynamic FNE registration table entry + try { + m_fneRegTable.at(llId); + m_fneRegTable.erase(llId); + } + catch (...) { + // stub + } + } + } + break; + default: + LogError(LOG_RF, "P25 unhandled PDU registration type, regType = $%02X", regType); + break; + } + } + break; + default: + if (m_repeatPDU) { + if (m_verbose) { + LogMessage(LOG_RF, P25_PDU_STR ", repeating PDU, llId = %u", (m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR) ? m_rfSecondHeader.getLLId() : m_rfDataHeader.getLLId()); + } + + writeRF_PDU(); // re-generate PDU and send it on + } + break; + } + + ::ActivityLog("P25", true, "end RF data transmission"); + + m_rfDataHeader.reset(); + m_rfDataBlockCnt = 0U; + m_rfPDUCount = 0U; + m_rfPDUBits = 0U; + + m_p25->m_rfState = m_prevRfState; + } + } + + return true; + } + else { + LogError(LOG_RF, "P25 unhandled data DUID, duid = $%02X", duid); + } + + return false; +} + +/// +/// Process a data frame from the network. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +/// +/// +/// +bool DataPacket::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid) +{ + if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_netState == RS_NET_IDLE) + return false; + + switch (duid) { + case P25_DUID_PDU: + { + uint32_t pduLen = control.getDstId(); // PDU's use dstId as the PDU len + ::memset(m_netPDU, 0x00U, pduLen + 2U); + ::memcpy(m_netPDU, data, pduLen); + + uint8_t dataType = control.getLCO(); + if (dataType == P25_DT_DATA_HEADER) { + writeNet_PDU_Header(); + } + else if (dataType == P25_DT_DATA_SEC_HEADER) { + writeNet_PDU_Sec_Header(); + } + else if (dataType == P25_DT_DATA) { + writeNet_PDU(); + } + + if (m_p25->m_netState == RS_NET_DATA) { + if (m_netDataBlockCnt >= m_netBlocksToFollow) { + if (m_dumpPDUData) { + Utils::dump(1U, "PDU Packet", m_pduUserData, m_netDataOffset); + } + } + + // write data to RF interface? + if (m_repeatPDU) { + if (m_netDataBlockCnt >= m_netBlocksToFollow) { + m_p25->writeRF_Preamble(); + + uint32_t bitLength = ((m_netDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; + + ::memset(m_netPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = m_netDataHeader.getBlocksToFollow(); + + // Generate the PDU header and 1/2 rate Trellis + m_netDataHeader.encode(buffer); + Utils::setBitRange(buffer, m_netPDU, offset, P25_PDU_FEC_LENGTH_BITS); + offset += P25_PDU_FEC_LENGTH_BITS; + + // Generate the second PDU header + if (m_netDataHeader.getSAP() == PDU_SAP_EXT_ADDR) { + m_netSecondHeader.encode(buffer); + Utils::setBitRange(buffer, m_netPDU, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + blocksToFollow--; + } + + // Generate the PDU data + bool halfRate = m_netData[0].getHalfRateTrellis(); + uint32_t dataOffset = 0U; + for (uint32_t i = 0U; i < blocksToFollow; i++) { + m_netData[i].setFormat((m_netDataHeader.getSAP() == PDU_SAP_EXT_ADDR) ? m_netSecondHeader : m_netDataHeader); + m_netData[i].setSerialNo(i); + m_netData[i].setHalfRateTrellis(halfRate); + m_netData[i].setData(m_pduUserData + dataOffset); + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + m_netData[i].encode(buffer); + Utils::setBitRange(buffer, m_netPDU, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + dataOffset += (m_netDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } + + if (m_debug) { + Utils::dump(2U, "!!! *Raw PDU Frame Data - P25_DUID_PDU", m_netPDU, bitLength / 8U); + } + + uint8_t pdu[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + + // Add the data + uint32_t newBitLength = P25Utils::encode(m_netPDU, pdu + 2U, bitLength); + uint32_t newByteLength = newBitLength / 8U; + if ((newBitLength % 8U) > 0U) + newByteLength++; + + // Regenerate Sync + Sync::addP25Sync(pdu + 2U); + + // Regenerate NID + m_p25->m_nid.encode(pdu + 2U, P25_DUID_PDU); + + // Add busy bits + m_p25->addBusyBits(pdu + 2U, newBitLength, false, true); + + ::memset(m_netPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + if (m_p25->m_duplex) { + pdu[0U] = TAG_DATA; + pdu[1U] = 0x00U; + m_p25->writeQueueNet(pdu, newByteLength + 2U); + } + + // add trailing null pad; only if control data isn't being transmitted + if (!m_p25->m_ccRunning) { + m_p25->writeRF_Nulls(); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_PDU", pdu + 2U, newByteLength); + } + + ::ActivityLog("P25", true, "end RF data transmission"); + + m_netDataHeader.reset(); + m_netSecondHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + m_p25->m_netState = RS_NET_IDLE; + } + } + } // if (m_netState == RS_NET_DATA) + } + break; + default: + return false; + } + + return true; +} + +/// +/// Helper to check if a logical link ID has registered with data services. +/// +/// Logical Link ID. +/// True, if ID has registered, otherwise false. +bool DataPacket::hasLLIdFNEReg(uint32_t llId) const +{ + // lookup dynamic FNE registration table entry + try { + ulong64_t tblIpAddr = m_fneRegTable.at(llId); + if (tblIpAddr != 0U) { + return true; + } + else { + return false; + } + } catch (...) { + return false; + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DataPacket class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// +/// +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +DataPacket::DataPacket(Control* p25, network::BaseNetwork* network, bool dumpPDUData, bool repeatPDU, bool debug, bool verbose) : + m_p25(p25), + m_network(network), + m_prevRfState(RS_RF_LISTENING), + m_rfData(NULL), + m_rfDataHeader(), + m_rfSecondHeader(), + m_rfDataBlockCnt(0U), + m_rfPDU(NULL), + m_rfPDUCount(0U), + m_rfPDUBits(0U), + m_netData(NULL), + m_netDataHeader(), + m_netSecondHeader(), + m_netBlocksToFollow(0U), + m_netDataBlockCnt(0U), + m_netBitOffset(0U), + m_netDataOffset(0U), + m_netPDU(NULL), + m_pduUserData(NULL), + m_fneRegTable(), + m_dumpPDUData(dumpPDUData), + m_repeatPDU(repeatPDU), + m_verbose(verbose), + m_debug(debug) +{ + m_rfData = new data::DataBlock[P25_MAX_PDU_COUNT]; + + m_rfPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(m_rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + m_netData = new data::DataBlock[P25_MAX_PDU_COUNT]; + + m_netPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(m_netPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + m_pduUserData = new uint8_t[P25_MAX_PDU_COUNT * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_COUNT * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + + m_fneRegTable.clear(); +} + +/// +/// Finalizes a instance of the DataPacket class. +/// +DataPacket::~DataPacket() +{ + delete[] m_rfPDU; + delete[] m_netPDU; + delete[] m_pduUserData; +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +/// +void DataPacket::writeNetworkRF(const uint8_t dataType, const uint8_t *data, uint32_t len) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) + return; + + m_network->writeP25PDU((m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR) ? m_rfSecondHeader.getLLId() : m_rfDataHeader.getLLId(), dataType, data, len); +} + +/// +/// Helper to write a P25 PDU packet. +/// +void DataPacket::writeRF_PDU() +{ + m_p25->writeRF_Preamble(); + + uint32_t bitLength = ((m_rfDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; + + ::memset(m_rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + uint32_t blocksToFollow = m_rfDataHeader.getBlocksToFollow(); + + // Generate the PDU header and 1/2 rate Trellis + m_rfDataHeader.encode(buffer); + Utils::setBitRange(buffer, m_rfPDU, offset, P25_PDU_FEC_LENGTH_BITS); + offset += P25_PDU_FEC_LENGTH_BITS; + + // Generate the second PDU header + if (m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR) { + m_rfSecondHeader.encode(buffer); + Utils::setBitRange(buffer, m_rfPDU, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + blocksToFollow--; + } + + // Generate the PDU data + bool halfRate = m_rfData[0].getHalfRateTrellis(); + uint32_t dataOffset = 0U; + for (uint32_t i = 0U; i < blocksToFollow; i++) { + m_rfData[i].setFormat((m_rfDataHeader.getSAP() == PDU_SAP_EXT_ADDR) ? m_rfSecondHeader : m_rfDataHeader); + m_rfData[i].setSerialNo(i); + m_rfData[i].setHalfRateTrellis(halfRate); + m_rfData[i].setData(m_pduUserData + dataOffset); + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + m_rfData[i].encode(buffer); + Utils::setBitRange(buffer, m_rfPDU, offset, P25_PDU_FEC_LENGTH_BITS); + + offset += P25_PDU_FEC_LENGTH_BITS; + dataOffset += (m_rfDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } + + if (m_debug) { + Utils::dump(2U, "!!! *Raw PDU Frame Data - P25_DUID_PDU", m_rfPDU, bitLength / 8U); + } + + uint8_t pdu[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + + // Add the data + uint32_t newBitLength = P25Utils::encode(m_rfPDU, pdu + 2U, bitLength); + uint32_t newByteLength = newBitLength / 8U; + if ((newBitLength % 8U) > 0U) + newByteLength++; + + // Regenerate Sync + Sync::addP25Sync(pdu + 2U); + + // Regenerate NID + m_p25->m_nid.encode(pdu + 2U, P25_DUID_PDU); + + // Add busy bits + m_p25->addBusyBits(pdu + 2U, newBitLength, false, true); + + ::memset(m_rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + if (m_p25->m_duplex) { + pdu[0U] = TAG_DATA; + pdu[1U] = 0x00U; + m_p25->writeQueueRF(pdu, newByteLength + 2U); + } + + // add trailing null pad; only if control data isn't being transmitted + if (!m_p25->m_ccRunning) { + m_p25->writeRF_Nulls(); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_PDU", pdu + 2U, newByteLength); + } +} + +/// +/// Helper to write a PDU registration response. +/// +/// +/// +/// +void DataPacket::writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, ulong64_t ipAddr) +{ + if ((regType != PDU_REG_TYPE_RSP_ACCPT) && (regType != PDU_REG_TYPE_RSP_DENY)) + return; + + uint32_t bitLength = (2U * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + uint32_t offset = P25_PREAMBLE_LENGTH_BITS; + + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + m_rfDataHeader.reset(); + + // set header fields + m_rfDataHeader.setAckNeeded(true); + m_rfDataHeader.setOutbound(true); + m_rfDataHeader.setSAP(PDU_SAP_REG); + m_rfDataHeader.setLLId(llId); + m_rfDataHeader.setFullMessage(true); + m_rfDataHeader.setBlocksToFollow(1U); + m_rfDataHeader.setPadCount(0U); + m_rfDataHeader.setSync(false); + m_rfDataHeader.setN(0U); + m_rfDataHeader.setSeqNo(8U); + m_rfDataHeader.setHeaderOffset(0U); + + // Generate the PDU header and 1/2 rate Trellis + m_rfDataHeader.encode(buffer); + Utils::setBitRange(buffer, m_rfPDU, offset, P25_PDU_FEC_LENGTH_BITS); + offset += P25_PDU_FEC_LENGTH_BITS; + + // build registration response data + ::memset(buffer, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + + buffer[0U] = (regType << 4); // Registration Type & Options + buffer[1U] = (llId >> 16) & 0xFFU; // Logical Link ID + buffer[2U] = (llId >> 8) & 0xFFU; + buffer[3U] = (llId >> 0) & 0xFFU; + if (regType == PDU_REG_TYPE_RSP_ACCPT) { + buffer[8U] = (ipAddr >> 24) & 0xFFU; // IP Address + buffer[9U] = (ipAddr >> 16) & 0xFFU; + buffer[10U] = (ipAddr >> 8) & 0xFFU; + buffer[11U] = (ipAddr >> 0) & 0xFFU; + } + + edac::CRC::addCRC32(buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); + + // Generate the PDU data + m_rfData[0].setFormat(m_rfDataHeader); + m_rfData[0].setSerialNo(0U); + m_rfData[0].setHalfRateTrellis(true); + m_rfData[0].setData(buffer); + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + m_rfData[0].encode(buffer); + Utils::setBitRange(buffer, m_rfPDU, offset, P25_PDU_FEC_LENGTH_BITS); + + if (m_debug) { + Utils::dump(2U, "!!! *Raw PDU Frame Data - P25_DUID_PDU", m_rfPDU, bitLength / 8U); + } + + uint8_t pdu[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + + // Add the data + uint32_t newBitLength = P25Utils::encode(m_rfPDU, pdu + 2U, bitLength); + uint32_t newByteLength = newBitLength / 8U; + if ((newBitLength % 8U) > 0U) + newByteLength++; + + // Regenerate Sync + Sync::addP25Sync(pdu + 2U); + + // Regenerate NID + m_p25->m_nid.encode(pdu + 2U, P25_DUID_PDU); + + // Add busy bits + m_p25->addBusyBits(pdu + 2U, newBitLength, false, true); + + ::memset(m_rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + if (m_p25->m_duplex) { + pdu[0U] = TAG_DATA; + pdu[1U] = 0x00U; + m_p25->writeQueueRF(pdu, newByteLength + 2U); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_PDU", pdu + 2U, newByteLength); + } +} + +/// +/// Helper to write a network P25 PDU header packet. +/// +void DataPacket::writeNet_PDU_Header() +{ + if (m_p25->m_netState != RS_NET_DATA) { + m_netDataHeader.reset(); + m_netSecondHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + + m_p25->m_netState = RS_NET_DATA; + + ::memset(m_pduUserData, 0x00U, P25_MAX_PDU_COUNT * P25_PDU_CONFIRMED_LENGTH_BYTES + 2U); + + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + ::memcpy(buffer, m_netPDU, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = m_netDataHeader.decode(buffer); + if (!ret) { + LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate header data"); + Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + + m_netDataHeader.reset(); + m_netSecondHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + m_p25->m_netState = RS_NET_IDLE; + return; + } + + ::ActivityLog("P25", true, "received network data transmission from %u to %u, %u blocks", m_netDataHeader.getLLId(), m_netDataHeader.getLLId(), m_netDataHeader.getBlocksToFollow()); + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u, hdrOffset = %u", + m_netDataHeader.getAckNeeded(), m_netDataHeader.getOutbound(), m_netDataHeader.getFormat(), m_netDataHeader.getSAP(), m_netDataHeader.getFullMessage(), + m_netDataHeader.getBlocksToFollow(), m_netDataHeader.getPadCount(), m_netDataHeader.getN(), m_netDataHeader.getSeqNo(), + m_netDataHeader.getHeaderOffset()); + } + + // make sure we don't get a PDU with more blocks then we support + if (m_netDataHeader.getBlocksToFollow() >= P25_MAX_PDU_COUNT) { + LogError(LOG_NET, P25_PDU_STR ", too many PDU blocks to process, %u > %u", m_netDataHeader.getBlocksToFollow(), P25_MAX_PDU_COUNT); + + m_netDataHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + return; + } + + if (m_netDataHeader.getSAP() == PDU_SAP_EXT_ADDR && + m_netDataHeader.getFormat() == PDU_FMT_CONFIRMED) { + LogWarning(LOG_NET, P25_PDU_STR ", unsupported confirmed enhanced addressing"); + + m_netDataHeader.reset(); + m_netSecondHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + m_p25->m_netState = RS_NET_IDLE; + return; + } + + m_netBlocksToFollow = m_netDataHeader.getBlocksToFollow(); + + uint32_t bitLength = ((m_netDataHeader.getBlocksToFollow() + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_PREAMBLE_LENGTH_BITS; + m_netBitOffset = P25_PREAMBLE_LENGTH_BITS; + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + // Generate the PDU header and 1/2 rate Trellis + m_netDataHeader.encode(buffer); + Utils::setBitRange(buffer, m_netPDU, m_netBitOffset, P25_PDU_FEC_LENGTH_BITS); + m_netBitOffset += P25_PDU_FEC_LENGTH_BITS; + } +} + +/// +/// Helper to write a network P25 PDU secondary header packet. +/// +void DataPacket::writeNet_PDU_Sec_Header() +{ + if (m_p25->m_netState == RS_NET_DATA) { + // process second header if we're using enhanced addressing + if (m_netDataHeader.getSAP() == PDU_SAP_EXT_ADDR) { + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + ::memcpy(buffer, m_netPDU, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = m_netSecondHeader.decode(buffer); + if (!ret) { + LogWarning(LOG_NET, P25_PDU_STR ", unfixable RF 1/2 rate second header data"); + Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_HEADER_LENGTH_BYTES); + + m_netDataHeader.reset(); + m_netSecondHeader.reset(); + m_netBlocksToFollow = 0U; + m_netDataBlockCnt = 0U; + m_netBitOffset = 0U; + m_netDataOffset = 0U; + m_p25->m_netState = RS_NET_IDLE; + return; + } + + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", fmt = $%02X, sap = $%02X, srcId = %u", + m_netSecondHeader.getFormat(), m_netSecondHeader.getSAP(), m_netSecondHeader.getLLId()); + } + + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + + // Generate the PDU header and 1/2 rate Trellis + m_netDataHeader.encode(buffer); + Utils::setBitRange(buffer, m_netPDU, m_netBitOffset, P25_PDU_FEC_LENGTH_BITS); + m_netBitOffset += P25_PDU_FEC_LENGTH_BITS; + + m_netBlocksToFollow--; + } + } +} + +/// +/// Helper to write a network P25 PDU data packet. +/// +void DataPacket::writeNet_PDU() +{ + if (m_p25->m_netState == RS_NET_DATA) { + uint8_t buffer[P25_PDU_FEC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_FEC_LENGTH_BYTES); + ::memcpy(buffer, m_netPDU, P25_PDU_FEC_LENGTH_BYTES); + + bool ret = m_netData[m_netDataBlockCnt].decode(buffer, (m_netDataHeader.getSAP() == PDU_SAP_EXT_ADDR) ? m_netSecondHeader : m_netDataHeader); + if (ret) { + if (m_verbose) { + LogMessage(LOG_NET, P25_PDU_STR ", block %u, fmt = $%02X", + (m_netDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? m_netData[m_netDataBlockCnt].getSerialNo() : m_netDataBlockCnt, m_netData[m_netDataBlockCnt].getFormat()); + } + + m_netData[m_netDataBlockCnt].getData(m_pduUserData + m_netDataOffset); + m_netDataBlockCnt++; + } + else { + if (m_netData[m_netDataBlockCnt].getHalfRateTrellis()) + LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (1/2 rate or CRC)"); + else + LogWarning(LOG_NET, P25_PDU_STR ", unfixable PDU data (3/4 rate or CRC)"); + + if (m_dumpPDUData) { + Utils::dump(1U, "Unfixable PDU Data", buffer, P25_PDU_FEC_LENGTH_BYTES); + } + } + + m_netBitOffset += P25_PDU_FEC_LENGTH_BITS; + m_netDataOffset += (m_netDataHeader.getFormat() == PDU_FMT_CONFIRMED) ? P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } +} diff --git a/p25/DataPacket.h b/p25/DataPacket.h new file mode 100644 index 00000000..174cfd82 --- /dev/null +++ b/p25/DataPacket.h @@ -0,0 +1,128 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_DATA_PACKET_H__) +#define __P25_DATA_PACKET_H__ + +#include "Defines.h" +#include "p25/data/DataBlock.h" +#include "p25/data/DataHeader.h" +#include "p25/data/LowSpeedData.h" +#include "p25/lc/LC.h" +#include "p25/Control.h" +#include "network/BaseNetwork.h" + +#include +#include +#include + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API Control; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for P25 data packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API DataPacket { + public: + /// Resets the data states for the RF interface. + void resetRF(); + + /// Process a data frame from the RF interface. + bool process(uint8_t* data, uint32_t len); + /// Process a data frame from the network. + bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + + /// Helper to check if a logical link ID has registered with data services. + bool hasLLIdFNEReg(uint32_t llId) const; + + private: + friend class Control; + Control* m_p25; + + network::BaseNetwork* m_network; + + RPT_RF_STATE m_prevRfState; + + data::DataBlock* m_rfData; + data::DataHeader m_rfDataHeader; + data::DataHeader m_rfSecondHeader; + uint8_t m_rfDataBlockCnt; + uint8_t* m_rfPDU; + uint32_t m_rfPDUCount; + uint32_t m_rfPDUBits; + + data::DataBlock* m_netData; + data::DataHeader m_netDataHeader; + data::DataHeader m_netSecondHeader; + uint8_t m_netBlocksToFollow; + uint8_t m_netDataBlockCnt; + uint32_t m_netBitOffset; + uint32_t m_netDataOffset; + uint8_t* m_netPDU; + + uint8_t* m_pduUserData; + + std::unordered_map m_fneRegTable; + + bool m_dumpPDUData; + bool m_repeatPDU; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the DataPacket class. + DataPacket(Control* p25, network::BaseNetwork* network, bool dumpPDUData, bool repeatPDU, bool debug, bool verbose); + /// Finalizes a instance of the DataPacket class. + ~DataPacket(); + + /// Write data processed from RF to the network. + void writeNetworkRF(const uint8_t dataType, const uint8_t* data, uint32_t len); + + /// Helper to write a P25 PDU packet. + void writeRF_PDU(); + /// Helper to write a PDU registration response. + void writeRF_PDU_Reg_Response(uint8_t regType, uint32_t llId, ulong64_t ipAddr); + + /// Helper to write a network P25 PDU header packet. + void writeNet_PDU_Header(); + /// Helper to write a network P25 PDU secondary header packet. + void writeNet_PDU_Sec_Header(); + /// Helper to write a network P25 PDU data packet. + void writeNet_PDU(); + }; +} // namespace p25 + +#endif // __P25_DATA_PACKET_H__ diff --git a/p25/NID.cpp b/p25/NID.cpp new file mode 100644 index 00000000..080a5ce9 --- /dev/null +++ b/p25/NID.cpp @@ -0,0 +1,222 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/NID.h" +#include "p25/P25Utils.h" +#include "edac/BCH.h" + +using namespace p25; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t MAX_NID_ERRS = 7U;//5U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the NID class. +/// +/// P25 Network Access Code. +NID::NID(uint32_t nac) : + m_duid(0U), + m_hdu(NULL), + m_tdu(NULL), + m_ldu1(NULL), + m_pdu(NULL), + m_tsdu(NULL), + m_ldu2(NULL), + m_tdulc(NULL) +{ + edac::BCH bch; + + m_hdu = new uint8_t[P25_NID_LENGTH_BYTES]; + m_hdu[0U] = (nac >> 4) & 0xFFU; + m_hdu[1U] = (nac << 4) & 0xF0U; + m_hdu[1U] |= P25_DUID_HDU; + bch.encode(m_hdu); + m_hdu[7U] &= 0xFEU; // Clear the parity bit + + m_tdu = new uint8_t[P25_NID_LENGTH_BYTES]; + m_tdu[0U] = (nac >> 4) & 0xFFU; + m_tdu[1U] = (nac << 4) & 0xF0U; + m_tdu[1U] |= P25_DUID_TDU; + bch.encode(m_tdu); + m_tdu[7U] &= 0xFEU; // Clear the parity bit + + m_ldu1 = new uint8_t[P25_NID_LENGTH_BYTES]; + m_ldu1[0U] = (nac >> 4) & 0xFFU; + m_ldu1[1U] = (nac << 4) & 0xF0U; + m_ldu1[1U] |= P25_DUID_LDU1; + bch.encode(m_ldu1); + m_ldu1[7U] |= 0x01U; // Set the parity bit + + m_pdu = new uint8_t[P25_NID_LENGTH_BYTES]; + m_pdu[0U] = (nac >> 4) & 0xFFU; + m_pdu[1U] = (nac << 4) & 0xF0U; + m_pdu[1U] |= P25_DUID_PDU; + bch.encode(m_pdu); + m_pdu[7U] &= 0xFEU; // Clear the parity bit + + m_tsdu = new uint8_t[P25_NID_LENGTH_BYTES]; + m_tsdu[0U] = (nac >> 4) & 0xFFU; + m_tsdu[1U] = (nac << 4) & 0xF0U; + m_tsdu[1U] |= P25_DUID_TSDU; + bch.encode(m_tsdu); + m_tsdu[7U] &= 0xFEU; // Clear the parity bit + + m_ldu2 = new uint8_t[P25_NID_LENGTH_BYTES]; + m_ldu2[0U] = (nac >> 4) & 0xFFU; + m_ldu2[1U] = (nac << 4) & 0xF0U; + m_ldu2[1U] |= P25_DUID_LDU2; + bch.encode(m_ldu2); + m_ldu2[7U] |= 0x01U; // Set the parity bit + + m_tdulc = new uint8_t[P25_NID_LENGTH_BYTES]; + m_tdulc[0U] = (nac >> 4) & 0xFFU; + m_tdulc[1U] = (nac << 4) & 0xF0U; + m_tdulc[1U] |= P25_DUID_TDULC; + bch.encode(m_tdulc); + m_tdulc[7U] &= 0xFEU; // Clear the parity bit +} + +/// +/// Finalizes a instance of the NID class. +/// +NID::~NID() +{ + delete[] m_hdu; + delete[] m_tdu; + delete[] m_ldu1; + delete[] m_pdu; + delete[] m_tsdu; + delete[] m_ldu2; + delete[] m_tdulc; +} + +/// +/// Decodes P25 network identifier data. +/// +/// +/// +bool NID::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t nid[P25_NID_LENGTH_BYTES]; + P25Utils::decode(data, nid, 48U, 114U); + + uint32_t errs = P25Utils::compare(nid, m_ldu1, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_LDU1; + return true; + } + + errs = P25Utils::compare(nid, m_ldu2, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_LDU2; + return true; + } + + errs = P25Utils::compare(nid, m_tdu, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_TDU; + return true; + } + + errs = P25Utils::compare(nid, m_tdulc, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_TDULC; + return true; + } + + errs = P25Utils::compare(nid, m_pdu, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_PDU; + return true; + } + + errs = P25Utils::compare(nid, m_tsdu, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_TSDU; + return true; + } + + errs = P25Utils::compare(nid, m_hdu, P25_NID_LENGTH_BYTES); + if (errs < MAX_NID_ERRS) { + m_duid = P25_DUID_HDU; + return true; + } + + return false; +} + +/// +/// Encodes P25 network identifier data. +/// +/// +/// +void NID::encode(uint8_t* data, uint8_t duid) const +{ + assert(data != NULL); + + switch (duid) { + case P25_DUID_HDU: + P25Utils::encode(m_hdu, data, 48U, 114U); + break; + case P25_DUID_TDU: + P25Utils::encode(m_tdu, data, 48U, 114U); + break; + case P25_DUID_LDU1: + P25Utils::encode(m_ldu1, data, 48U, 114U); + break; + case P25_DUID_PDU: + P25Utils::encode(m_pdu, data, 48U, 114U); + break; + case P25_DUID_TSDU: + P25Utils::encode(m_tsdu, data, 48U, 114U); + break; + case P25_DUID_LDU2: + P25Utils::encode(m_ldu2, data, 48U, 114U); + break; + case P25_DUID_TDULC: + P25Utils::encode(m_tdulc, data, 48U, 114U); + break; + default: + break; + } +} diff --git a/p25/NID.h b/p25/NID.h new file mode 100644 index 00000000..6b630eea --- /dev/null +++ b/p25/NID.h @@ -0,0 +1,70 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_NID_H__) +#define __P25_NID_H__ + +#include "Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents the P25 network identifier. + // --------------------------------------------------------------------------- + + class HOST_SW_API NID { + public: + /// Initializes a new instance of the NID class. + NID(uint32_t nac); + /// Finalizes a instance of the NID class. + ~NID(); + + /// Decodes P25 network identifier data. + bool decode(const uint8_t* data); + /// Encodes P25 network identifier data. + void encode(uint8_t* data, uint8_t duid) const; + + public: + /// Data unit ID. + __READONLY_PROPERTY(uint8_t, duid, DUID); + + private: + uint8_t* m_hdu; + uint8_t* m_tdu; + uint8_t* m_ldu1; + uint8_t* m_pdu; + uint8_t* m_tsdu; + uint8_t* m_ldu2; + uint8_t* m_tdulc; + }; +} // namespace p25 + +#endif // __P25_NID_H__ diff --git a/p25/P25Defines.h b/p25/P25Defines.h new file mode 100644 index 00000000..d917a9d1 --- /dev/null +++ b/p25/P25Defines.h @@ -0,0 +1,309 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_DEFINES_H__) +#define __P25_DEFINES_H__ + +#include "Defines.h" + +// Data Unit ID String(s) +#define P25_HDU_STR "P25_DUID_HDU (Header Data Unit)" +#define P25_TDU_STR "P25_DUID_TDU (Simple Terminator Data Unit)" +#define P25_LDU1_STR "P25_DUID_LDU1 (Logical Link Data Unit 1)" +#define P25_TSDU_STR "P25_DUID_TSDU (Trunking System Data Unit)" +#define P25_LDU2_STR "P25_DUID_LDU2 (Logical Link Data Unit 2)" +#define P25_PDU_STR "P25_DUID_PDU (Packet Data Unit)" +#define P25_TDULC_STR "P25_DUID_TDULC (Terminator Data Unit with Link Control)" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t P25_HDU_FRAME_LENGTH_BYTES = 99U; + const uint32_t P25_HDU_FRAME_LENGTH_BITS = P25_HDU_FRAME_LENGTH_BYTES * 8U; + + const uint32_t P25_TDU_FRAME_LENGTH_BYTES = 18U; + const uint32_t P25_TDU_FRAME_LENGTH_BITS = P25_TDU_FRAME_LENGTH_BYTES * 8U; + + const uint32_t P25_LDU_FRAME_LENGTH_BYTES = 216U; + const uint32_t P25_LDU_FRAME_LENGTH_BITS = P25_LDU_FRAME_LENGTH_BYTES * 8U; + + const uint32_t P25_TSDU_FRAME_LENGTH_BYTES = 45U; + const uint32_t P25_TSDU_FRAME_LENGTH_BITS = P25_TSDU_FRAME_LENGTH_BYTES * 8U; + + const uint32_t P25_TSDU_DOUBLE_FRAME_LENGTH_BYTES = 72U; + const uint32_t P25_TSDU_DOUBLE_FRAME_LENGTH_BITS = P25_TSDU_DOUBLE_FRAME_LENGTH_BYTES * 8U; + + const uint32_t P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES = 90U; + const uint32_t P25_TSDU_TRIPLE_FRAME_LENGTH_BITS = P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES * 8U; + + 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_NID_LENGTH_BYTES = 8U; + const uint32_t P25_NID_LENGTH_BITS = P25_NID_LENGTH_BYTES * 8U; + + 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 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; + + const uint32_t P25_LDU_FRAME_TIME = 180U; + + const uint32_t P25_HDU_LENGTH_BYTES = 81U; + const uint32_t P25_LDU_LC_LENGTH_BYTES = 18U; + + const uint32_t P25_TDULC_FEC_LENGTH_BYTES = 36U; + const uint32_t P25_TDULC_LENGTH_BYTES = 18U; + + const uint32_t P25_TSBK_FEC_LENGTH_BYTES = 25U; + const uint32_t P25_TSBK_FEC_LENGTH_BITS = P25_TSBK_FEC_LENGTH_BYTES * 8U - 4U; // Trellis is actually 196 bits + const uint32_t P25_TSBK_LENGTH_BYTES = 12U; + + const uint32_t P25_MAX_PDU_COUNT = 32U; + const uint32_t P25_MAX_PDU_LENGTH = 512U; + const uint32_t P25_PDU_HEADER_LENGTH_BYTES = 12U; + const uint32_t P25_PDU_CONFIRMED_LENGTH_BYTES = 18U; + const uint32_t P25_PDU_CONFIRMED_DATA_LENGTH_BYTES = 16U; + const uint32_t P25_PDU_UNCONFIRMED_LENGTH_BYTES = 12U; + + const uint32_t P25_PDU_FEC_LENGTH_BYTES = 25U; + const uint32_t P25_PDU_FEC_LENGTH_BITS = P25_PDU_FEC_LENGTH_BYTES * 8U - 4U; // Trellis is actually 196 bits + + const uint32_t P25_MI_LENGTH_BYTES = 9U; + + const uint32_t P25_SS0_START = 70U; + const uint32_t P25_SS1_START = 71U; + const uint32_t P25_SS_INCREMENT = 72U; + + const uint8_t P25_NULL_IMBE[] = { 0x04U, 0x0CU, 0xFDU, 0x7BU, 0xFBU, 0x7DU, 0xF2U, 0x7BU, 0x3DU, 0x9EU, 0x45U }; + + const uint8_t P25_MFG_STANDARD = 0x00U; + const uint8_t P25_MFG_MOT = 0x90U; + + const uint8_t P25_MOT_CALLSIGN_LENGTH_BYTES = 8U; + + const uint8_t P25_ALGO_UNENCRYPT = 0x80U; + + const uint8_t P25_IDEN_UP_VU_BW_625K = 0x04U; + const uint8_t P25_IDEN_UP_VU_BW_125K = 0x05U; + + const uint8_t P25_SVC_CLS_COMPOSITE = 0x01U; + const uint8_t P25_SVC_CLS_VOICE = 0x10U; + const uint8_t P25_SVC_CLS_DATA = 0x20U; + const uint8_t P25_SVC_CLS_REG = 0x40U; + const uint8_t P25_SVC_CLS_AUTH = 0x80U; + + const uint32_t P25_SYS_SRV_NET_ACTIVE = 0x0200001U; + const uint32_t P25_SYS_SRV_GROUP_VOICE = 0x0080001U; + const uint32_t P25_SYS_SRV_IND_VOICE = 0x0040001U; + const uint32_t P25_SYS_SRV_GROUP_DATA = 0x0004001U; + const uint32_t P25_SYS_SRV_IND_DATA = 0x0002001U; + + const uint8_t P25_CFVA_CONV = 0x08U; + const uint8_t P25_CFVA_FAILURE = 0x04U; + const uint8_t P25_CFVA_VALID = 0x02U; + const uint8_t P25_CFVA_NETWORK = 0x01U; + + const uint8_t P25_RSP_ACCEPT = 0x00U; + const uint8_t P25_RSP_FAIL = 0x01U; + const uint8_t P25_RSP_DENY = 0x02U; + const uint8_t P25_RSP_REFUSED = 0x03U; + + const uint8_t P25_ANS_RSP_PROCEED = 0x20U; + const uint8_t P25_ANS_RSP_DENY = 0x21U; + const uint8_t P25_ANS_RSP_WAIT = 0x22U; + + const uint8_t P25_CAN_SRV_NONE = 0x00U; + const uint8_t P25_CAN_SRV_TERM_QUE = 0x10U; + const uint8_t P25_CAN_SRV_TERM_RSRC_ASSIGN = 0x20U; + + const uint32_t P25_DENY_RSN_REQ_UNIT_NOT_VALID = 0x10U; + const uint32_t P25_DENY_RSN_REQ_UNIT_NOT_AUTH = 0x11U; + const uint32_t P25_DENY_RSN_TGT_UNIT_NOT_VALID = 0x20U; + const uint32_t P25_DENY_RSN_TGT_UNIT_NOT_AUTH = 0x21U; + const uint32_t P25_DENY_RSN_TGT_UNIT_REFUSED = 0x2FU; + const uint32_t P25_DENY_RSN_TGT_GROUP_NOT_VALID = 0x30U; + const uint32_t P25_DENY_RSN_TGT_GROUP_NOT_AUTH = 0x31U; + const uint32_t P25_DENY_RSN_SITE_ACCESS_DENIAL = 0x60U; + const uint32_t P25_DENY_RSN_PTT_COLLIDE = 0x67U; + const uint32_t P25_DENY_RSN_PTT_BONK = 0x77U; + const uint32_t P25_DENY_RSN_SYS_UNSUPPORTED_SVC = 0xFFU; + + const uint32_t P25_QUE_RSN_TGT_UNIT_QUEUED = 0x2FU; + const uint32_t P25_QUE_RSN_CHN_RESOURCE_NOT_AVAIL = 0x40U; + + const uint32_t P25_EXT_FNCT_CHECK = 0x0000U; // Radio Check + const uint32_t P25_EXT_FNCT_UNINHIBIT = 0x007EU; // Radio Uninhibit + const uint32_t P25_EXT_FNCT_INHIBIT = 0x007FU; // Radio Inhibit + const uint32_t P25_EXT_FNCT_CHECK_ACK = 0x0080U; // Radio Check Ack + const uint32_t P25_EXT_FNCT_UNINHIBIT_ACK = 0x00FEU; // Radio Uninhibit Ack + const uint32_t P25_EXT_FNCT_INHIBIT_ACK = 0x00FFU; // Radio Inhibit Ack + + const uint32_t P25_WACN_STD_DEFAULT = 0xBB800U; + + const uint32_t P25_SID_STD_DEFAULT = 0x001U; + + const uint32_t P25_WUID_FNE = 0xFFFFFCU; + const uint32_t P25_WUID_SYS = 0xFFFFFDU; + const uint32_t P25_WUID_REG = 0xFFFFFEU; + const uint32_t P25_WUID_ALL = 0xFFFFFFU; + + const uint32_t P25_TGID_ALL = 0xFFFFU; + + const uint32_t DEFAULT_SILENCE_THRESHOLD = 124U; + + // PDU Format Type(s) + const uint8_t PDU_FMT_RSP = 0x03U; + const uint8_t PDU_FMT_UNCONFIRMED = 0x15U; + const uint8_t PDU_FMT_CONFIRMED = 0x16U; + + // PDU SAP + const uint8_t PDU_SAP_USER_DATA = 0x00U; + const uint8_t PDU_SAP_ENC_USER_DATA = 0x01U; + const uint8_t PDU_SAP_PACKET_DATA = 0x04U; + const uint8_t PDU_SAP_ARP = 0x05U; + const uint8_t PDU_SAP_SNDCP_CTRL_DATA = 0x06U; + const uint8_t PDU_SAP_EXT_ADDR = 0x1FU; + const uint8_t PDU_SAP_REG = 0x20U; + const uint8_t PDU_SAP_UNENC_KMM = 0x28U; + const uint8_t PDU_SAP_ENC_KMM = 0x29U; + + // PDU Registration Type(s) + const uint8_t PDU_REG_TYPE_REQ_CNCT = 0x00U; + const uint8_t PDU_REG_TYPE_REQ_DISCNCT = 0x01U; + const uint8_t PDU_REG_TYPE_RSP_ACCPT = 0x04U; + const uint8_t PDU_REG_TYPE_RSP_DENY = 0x05U; + + // PDU SNDCP Type(s) + const uint8_t PDU_TYPE_SNDCP_ACT_TDS_CTX_ACCPT = 0x00U; + const uint8_t PDU_TYPE_SNDCP_DEACT_TDS_CTX_ACCPT = 0x01U; + const uint8_t PDU_TYPE_SNDCP_DEACT_TDS_CTX_REQ = 0x02U; + const uint8_t PDU_TYPE_SNDCP_ACT_TDS_CTX_REJECT = 0x03U; + const uint8_t PDU_TYPE_SNDCP_RF_UNCONFIRMED = 0x04U; + const uint8_t PDU_TYPE_SNDCP_RF_CONFIRMED = 0x05U; + + // LDUx/TDULC Link Control Opcode(s) + const uint8_t LC_GROUP = 0x00U; // GRP VCH USER - Group Voice Channel User + const uint8_t LC_GROUP_UPDT = 0x02U; // GRP VCH UPDT - Group Voice Channel Update + const uint8_t LC_PRIVATE = 0x03U; // UU VCH USER - Unit-to-Unit Voice Channel User + const uint8_t LC_UU_ANS_REQ = 0x05U; // UU ANS REQ - Unit to Unit Answer Request + const uint8_t LC_TEL_INT_VCH_USER = 0x06U; // TEL INT VCH USER - Telephone Interconnect Voice Channel User + const uint8_t LC_TEL_INT_ANS_RQST = 0x07U; // TEL INT ANS RQST - Telephone Interconnect Answer Request + const uint8_t LC_CALL_TERM = 0x0FU; // CALL TERM - Call Termination or Cancellation + const uint8_t LC_IDEN_UP = 0x18U; // IDEN UP - Channel Identifier Update + const uint8_t LC_SYS_SRV_BCAST = 0x20U; // SYS SRV BCAST - System Service Broadcast + const uint8_t LC_ADJ_STS_BCAST = 0x22U; // ADJ STS BCAST - Adjacent Site Status Broadcast + const uint8_t LC_RFSS_STS_BCAST = 0x23U; // RFSS STS BCAST - RFSS Status Broadcast + const uint8_t LC_NET_STS_BCAST = 0x24U; // NET STS BCAST - Network Status Broadcast + + // TSBK ISP/OSP Shared Opcode(s) + const uint8_t TSBK_IOSP_GRP_VCH = 0x00U; // GRP VCH REQ - Group Voice Channel Request (ISP), GRP VCH GRANT - Group Voice Channel Grant (OSP) + const uint8_t TSBK_IOSP_UU_VCH = 0x04U; // UU VCH REQ - Unit-to-Unit Voice Channel Request (ISP), UU VCH GRANT - Unit-to-Unit Voice Channel Grant (OSP) + const uint8_t TSBK_IOSP_UU_ANS = 0x05U; // UU ANS RSP - Unit-to-Unit Answer Response (ISP), UU ANS REQ - Unit-to-Unit Answer Request (OSP) + const uint8_t TSBK_IOSP_TELE_INT_DIAL = 0x08U; // TELE INT DIAL REQ - Telephone Interconnect Request - Explicit (ISP), TELE INT DIAL GRANT - Telephone Interconnect Grant (OSP) + const uint8_t TSBK_IOSP_TELE_INT_ANS = 0x0AU; // TELE INT ANS RSP - Telephone Interconnect Answer Response (ISP), TELE INT ANS REQ - Telephone Interconnect Answer Request (OSP) + const uint8_t TSBK_IOSP_STS_UPDT = 0x18U; // STS UPDT REQ - Status Update Request (ISP), STS UPDT - Status Update (OSP) + const uint8_t TSBK_IOSP_STS_Q = 0x1AU; // STS Q REQ - Status Query Request (ISP), STS Q - Status Query (OSP) + const uint8_t TSBK_IOSP_MSG_UPDT = 0x1CU; // MSG UPDT REQ - Message Update Request (ISP), MSG UPDT - Message Update (OSP) + const uint8_t TSBK_IOSP_CALL_ALRT = 0x1FU; // CALL ALRT REQ - Call Alert Request (ISP), CALL ALRT - Call Alert (OSP) + const uint8_t TSBK_IOSP_ACK_RSP = 0x20U; // ACK RSP U - Acknowledge Response - Unit (ISP), ACK RSP FNE - Acknowledge Response - FNE (OSP) + const uint8_t TSBK_IOSP_EXT_FNCT = 0x24U; // EXT FNCT RSP - Extended Function Response (ISP), EXT FNCT CMD - Extended Function Command (OSP) + const uint8_t TSBK_IOSP_GRP_AFF = 0x28U; // GRP AFF REQ - Group Affiliation Request (ISP), GRP AFF RSP - Group Affiliation Response (OSP) + const uint8_t TSBK_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) + const uint8_t TSBK_ISP_TELE_INT_PSTN_REQ = 0x09U; // TELE INT PSTN REQ - Telephone Interconnect Request - Implicit + const uint8_t TSBK_ISP_SNDCP_CH_REQ = 0x13U; // SNDCP CH REQ - SNDCP Data Channel Request + const uint8_t TSBK_ISP_STS_Q_RSP = 0x19U; // STS Q RSP - Status Query Response + const uint8_t TSBK_ISP_CAN_SRV_REQ = 0x23U; // CAN SRV REQ - Cancel Service Request + const uint8_t TSBK_ISP_GRP_AFF_Q_RSP = 0x29U; // GRP AFF Q RSP - Group Affiliation Query Response + const uint8_t TSBK_ISP_U_DEREG_REQ = 0x2BU; // U DE REG REQ - Unit De-Registration Request + const uint8_t TSBK_ISP_LOC_REG_REQ = 0x2DU; // LOC REG REQ - Location Registration Request + + // TSBK Outbound Signalling Packet (OSP) Opcode(s) + const uint8_t TSBK_OSP_GRP_VCH_GRANT_UPD = 0x02U; // GRP VCH GRANT UPD - Group Voice Channel Grant Update + const uint8_t TSBK_OSP_UU_VCH_GRANT_UPD = 0x06U; // UU VCH GRANT UPD - Unit-to-Unit Voice Channel Grant Update + const uint8_t TSBK_OSP_SNDCP_CH_GNT = 0x14U; // SNDCP CH GNT - SNDCP Data Channel Grant + const uint8_t TSBK_OSP_SNDCP_CH_ANN = 0x16U; // SNDCP CH ANN - SNDCP Data Channel Announcement + const uint8_t TSBK_OSP_DENY_RSP = 0x27U; // DENY RSP - Deny Response + const uint8_t TSBK_OSP_GRP_AFF_Q = 0x2AU; // GRP AFF Q - Group Affiliation Query + const uint8_t TSBK_OSP_LOC_REG_RSP = 0x2BU; // LOC REG RSP - Location Registration Response + const uint8_t TSBK_OSP_U_REG_CMD = 0x2DU; // U REG CMD - Unit Registration Command + const uint8_t TSBK_OSP_U_DEREG_ACK = 0x2FU; // U DE REG ACK - Unit De-Registration Acknowledge + const uint8_t TSBK_OSP_QUE_RSP = 0x33U; // QUE RSP - Queued Response + const uint8_t TSBK_OSP_IDEN_UP_VU = 0x34U; // IDEN UP VU - Channel Identifier Update for VHF/UHF Bands + const uint8_t TSBK_OSP_SYS_SRV_BCAST = 0x38U; // SYS SRV BCAST - System Service Broadcast + const uint8_t TSBK_OSP_SCCB = 0x39U; // SCCB - Secondary Control Channel Broadcast + const uint8_t TSBK_OSP_RFSS_STS_BCAST = 0x3AU; // RFSS STS BCAST - RFSS Status Broadcast + const uint8_t TSBK_OSP_NET_STS_BCAST = 0x3BU; // NET STS BCAST - Network Status Broadcast + const uint8_t TSBK_OSP_ADJ_STS_BCAST = 0x3CU; // ADJ STS BCAST - Adjacent Site Status Broadcast + const uint8_t TSBK_OSP_IDEN_UP = 0x3DU; // IDEN UP - Channel Identifier Update + + // TSBK Motorola Outbound Signalling Packet (OSP) Opcode(s) + const uint8_t TSBK_OSP_MOT_GRG_ADD = 0x00U; // MOT GRG ADD - Motorola / Group Regroup Add (Patch Supergroup) + const uint8_t TSBK_OSP_MOT_GRG_DEL = 0x01U; // MOT GRG DEL - Motorola / Group Regroup Delete (Unpatch Supergroup) + const uint8_t TSBK_OSP_MOT_GRG_VCH_GRANT = 0x02U; // MOT GRG GROUP VCH GRANT / Group Regroup Voice Channel Grant + const uint8_t TSBK_OSP_MOT_GRG_VCH_UPD = 0x03U; // MOT GRG GROUP VCH GRANT UPD / Group Regroup Voice Channel Grant Update + const uint8_t TSBK_OSP_MOT_CC_BSI = 0x0BU; // MOT CC BSI - Motorola / Control Channel Base Station Identifier + const uint8_t TSBK_OSP_MOT_PSH_CCH = 0x0EU; // MOT PSH CCH - Motorola / Planned Control Channel Shutdown + + // Data Unit ID(s) + const uint8_t P25_DUID_HDU = 0x00U; // Header Data Unit + const uint8_t P25_DUID_TDU = 0x03U; // Simple Terminator Data Unit + const uint8_t P25_DUID_LDU1 = 0x05U; // Logical Link Data Unit 1 + const uint8_t P25_DUID_TSDU = 0x07U; // Trunking System Data Unit + const uint8_t P25_DUID_LDU2 = 0x0AU; // Logical Link Data Unit 2 + const uint8_t P25_DUID_PDU = 0x0CU; // Packet Data Unit + const uint8_t P25_DUID_TDULC = 0x0FU; // Terminator Data Unit with Link Control + + // Data Type(s) + const uint8_t P25_DT_DATA_HEADER = 0x06U; + const uint8_t P25_DT_DATA_SEC_HEADER = 0x07U; + const uint8_t P25_DT_DATA = 0x08U; +} // namespace p25 + +// --------------------------------------------------------------------------- +// Namespace Prototypes +// --------------------------------------------------------------------------- +namespace edac { } +namespace p25 +{ + namespace edac + { + using namespace ::edac; + } // namespace edac +} // namespace p25 + +#endif // __P25_DEFINES_H__ diff --git a/p25/P25Utils.cpp b/p25/P25Utils.cpp new file mode 100644 index 00000000..a2bdc0e9 --- /dev/null +++ b/p25/P25Utils.cpp @@ -0,0 +1,184 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/P25Utils.h" + +using namespace p25; + +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Decode bit interleaving. +/// +/// +/// +/// +/// +/// +uint32_t P25Utils::decode(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop) +{ + assert(in != NULL); + assert(out != NULL); + + // Move the SSx positions to the range needed + uint32_t ss0Pos = P25_SS0_START; + uint32_t ss1Pos = P25_SS1_START; + + while (ss0Pos < start) { + ss0Pos += P25_SS_INCREMENT; + ss1Pos += P25_SS_INCREMENT; + } + + uint32_t n = 0U; + for (uint32_t i = start; i < stop; i++) { + if (i == ss0Pos) { + ss0Pos += P25_SS_INCREMENT; + } + else if (i == ss1Pos) { + ss1Pos += P25_SS_INCREMENT; + } + else { + bool b = READ_BIT(in, i); + WRITE_BIT(out, n, b); + n++; + } + } + + return n; +} + +/// +/// Encode bit interleaving. +/// +/// +/// +/// +/// +/// +uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop) +{ + assert(in != NULL); + assert(out != NULL); + + // Move the SSx positions to the range needed + uint32_t ss0Pos = P25_SS0_START; + uint32_t ss1Pos = P25_SS1_START; + + while (ss0Pos < start) { + ss0Pos += P25_SS_INCREMENT; + ss1Pos += P25_SS_INCREMENT; + } + + uint32_t n = 0U; + for (uint32_t i = start; i < stop; i++) { + if (i == ss0Pos) { + ss0Pos += P25_SS_INCREMENT; + } + else if (i == ss1Pos) { + ss1Pos += P25_SS_INCREMENT; + } + else { + bool b = READ_BIT(in, n); + WRITE_BIT(out, i, b); + n++; + } + } + + return n; +} + +/// +/// Encode bit interleaving. +/// +/// +/// +/// +/// +uint32_t P25Utils::encode(const uint8_t* in, uint8_t* out, uint32_t length) +{ + assert(in != NULL); + assert(out != NULL); + + // Move the SSx positions to the range needed + uint32_t ss0Pos = P25_SS0_START; + uint32_t ss1Pos = P25_SS1_START; + + uint32_t n = 0U; + uint32_t pos = 0U; + while (n < length) { + if (pos == ss0Pos) { + ss0Pos += P25_SS_INCREMENT; + + } + else if (pos == ss1Pos) { + ss1Pos += P25_SS_INCREMENT; + + } + else { + bool b = READ_BIT(in, n); + WRITE_BIT(out, pos, b); + n++; + + } + pos++; + + } + + return pos; +} + +/// +/// Compare two datasets for the given length. +/// +/// +/// +/// +/// +uint32_t P25Utils::compare(const uint8_t* data1, const uint8_t* data2, uint32_t length) +{ + assert(data1 != NULL); + assert(data2 != NULL); + + uint32_t errs = 0U; + for (uint32_t i = 0U; i < length; i++) { + uint8_t v = data1[i] ^ data2[i]; + while (v != 0U) { + v &= v - 1U; + errs++; + } + } + + return errs; +} diff --git a/p25/P25Utils.h b/p25/P25Utils.h new file mode 100644 index 00000000..e127c03d --- /dev/null +++ b/p25/P25Utils.h @@ -0,0 +1,57 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_UTILS_H__) +#define __P25_UTILS_H__ + +#include "Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements various helper functions for interleaving P25 + // data. + // --------------------------------------------------------------------------- + + class HOST_SW_API P25Utils { + public: + /// Decode bit interleaving. + static uint32_t decode(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop); + /// Encode bit interleaving. + static uint32_t encode(const uint8_t* in, uint8_t* out, uint32_t start, uint32_t stop); + /// Encode bit interleaving for a given length. + static uint32_t encode(const uint8_t* in, uint8_t* out, uint32_t length); + + /// Compare two datasets for the given length. + static uint32_t compare(const uint8_t* data1, const uint8_t* data2, uint32_t length); + }; +} // namespace p25 + +#endif // __P25_UTILS_H__ diff --git a/p25/SiteData.h b/p25/SiteData.h new file mode 100644 index 00000000..154d7e8e --- /dev/null +++ b/p25/SiteData.h @@ -0,0 +1,219 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Server +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_SITE_DATA_H__) +#define __P25_SITE_DATA_H__ + +#include "Defines.h" +#include "p25/P25Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents site data for P25. + // --------------------------------------------------------------------------- + + class HOST_SW_API SiteData { + public: + /// Initializes a new instance of the SiteData class. + SiteData() : + m_lra(0U), + m_netId(P25_WACN_STD_DEFAULT), + m_sysId(P25_SID_STD_DEFAULT), + m_rfssId(1U), + m_siteId(1U), + m_channelId(1U), + m_channelNo(1U), + m_isAdjSite(false) + { + /* stub */ + } + /// Initializes a new instance of the SiteData class. + /// P25 Network ID. + /// P25 System ID. + /// P25 RFSS ID. + /// P25 Site ID. + /// P25 Location Resource Area. + /// Channel ID. + /// Channel Number. + SiteData(uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t lra, uint8_t channelId, uint32_t channelNo) : + m_lra(0U), + m_netId(P25_WACN_STD_DEFAULT), + m_sysId(P25_SID_STD_DEFAULT), + m_rfssId(1U), + m_siteId(1U), + m_channelId(1U), + m_channelNo(1U), + m_isAdjSite(false) + { + // lra clamping + if (lra > 0xFFU) // clamp to $FF + lra = 0xFFU; + + // netId clamping + if (netId == 0U) // clamp to 1 + netId = 1U; + if (netId > 0xFFFFEU) // clamp to $FFFFE + netId = 0xFFFFEU; + + // sysId clamping + if (sysId == 0U) // clamp to 1 + sysId = 1U; + if (sysId > 0xFFEU) // clamp to $FFE + sysId = 0xFFEU; + + // rfssId clamping + if (rfssId == 0U) // clamp to 1 + rfssId = 1U; + if (rfssId > 0xFEU) // clamp to $FE + rfssId = 0xFEU; + + // siteId clamping + if (siteId == 0U) // clamp to 1 + siteId = 1U; + if (siteId > 0xFEU) // clamp to $FE + siteId = 0xFEU; + + // channel id clamping + if (channelId > 15U) + channelId = 15U; + + // channel number clamping + if (channelNo == 0U) // clamp to 1 + channelNo = 1U; + if (channelNo > 4095U) // clamp to 4096 + channelNo = 4095U; + + m_lra = lra; + + m_netId = netId; + m_sysId = sysId; + + m_rfssId = rfssId; + m_siteId = siteId; + + m_channelId = channelId; + m_channelNo = channelNo; + } + + /// Helper to set adjacent site data. + /// P25 System ID. + /// P25 RFSS ID. + /// P25 Site ID. + /// Channel ID. + /// Channel Number. + void setAdjSite(uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo) + { + // sysId clamping + if (sysId == 0U) // clamp to 1 + sysId = 1U; + if (sysId > 0xFFEU) // clamp to $FFE + sysId = 0xFFEU; + + // rfssId clamping + if (rfssId == 0U) // clamp to 1 + rfssId = 1U; + if (rfssId > 0xFEU) // clamp to $FE + rfssId = 0xFEU; + + // siteId clamping + if (siteId == 0U) // clamp to 1 + siteId = 1U; + if (siteId > 0xFEU) // clamp to $FE + siteId = 0xFEU; + + // channel id clamping + if (channelId > 15U) + channelId = 15U; + + // channel number clamping + if (channelNo == 0U) // clamp to 1 + channelNo = 1U; + if (channelNo > 4095U) // clamp to 4096 + channelNo = 4095U; + + m_lra = 0U; + + m_netId = 0U; + m_sysId = sysId; + + m_rfssId = rfssId; + m_siteId = siteId; + + m_channelId = channelId; + m_channelNo = channelNo; + + m_isAdjSite = true; + } + + /// Equals operator. + /// + /// + SiteData & operator=(const SiteData & data) + { + if (this != &data) { + m_lra = data.m_lra; + + m_netId = data.m_netId; + m_sysId = data.m_sysId; + + m_rfssId = data.m_rfssId; + m_siteId = data.m_siteId; + + m_channelId = data.m_channelId; + m_channelNo = data.m_channelNo; + + m_isAdjSite = data.m_isAdjSite; + } + + return *this; + } + + public: + /// P25 location resource area. + __READONLY_PROPERTY_PLAIN(uint8_t, lra, lra); + /// P25 network ID. + __READONLY_PROPERTY_PLAIN(uint32_t, netId, netId); + /// Gets the P25 system ID. + __READONLY_PROPERTY_PLAIN(uint32_t, sysId, sysId); + /// P25 RFSS ID. + __READONLY_PROPERTY_PLAIN(uint8_t, rfssId, rfssId); + /// P25 site ID. + __READONLY_PROPERTY_PLAIN(uint8_t, siteId, siteId); + /// Channel ID. + __READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId); + /// Channel number. + __READONLY_PROPERTY_PLAIN(uint32_t, channelNo, channelNo); + /// Flag indicating whether this site data is for an adjacent site. + __READONLY_PROPERTY_PLAIN(bool, isAdjSite, isAdjSite); + }; +} // namespace p25 + +#endif // __P25_SITE_DATA_H__ diff --git a/p25/Sync.cpp b/p25/Sync.cpp new file mode 100644 index 00000000..e85ef0ab --- /dev/null +++ b/p25/Sync.cpp @@ -0,0 +1,52 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/Sync.h" + +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- +/// +/// Helper to append P25 sync bytes to the passed buffer. +/// +/// +void Sync::addP25Sync(uint8_t* data) +{ + assert(data != NULL); + + ::memcpy(data, P25_SYNC_BYTES, P25_SYNC_LENGTH_BYTES); +} diff --git a/p25/Sync.h b/p25/Sync.h new file mode 100644 index 00000000..c056b485 --- /dev/null +++ b/p25/Sync.h @@ -0,0 +1,49 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_SYNC_H__) +#define __P25_SYNC_H__ + +#include "Defines.h" + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Helper class for generating P25 sync data. + // --------------------------------------------------------------------------- + + class HOST_SW_API Sync { + public: + /// Helper to append P25 sync bytes to the passed buffer. + static void addP25Sync(uint8_t* data); + }; +} // namespace p25 + +#endif // __P25_SYNC_H__ diff --git a/p25/TrunkPacket.cpp b/p25/TrunkPacket.cpp new file mode 100644 index 00000000..76aa35b5 --- /dev/null +++ b/p25/TrunkPacket.cpp @@ -0,0 +1,2321 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/TrunkPacket.h" +#include "p25/acl/AccessControl.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Macros +// --------------------------------------------------------------------------- +// Make sure control data is supported. +#define IS_SUPPORT_CONTROL_CHECK(_PCKT_STR, _PCKT, _SRCID) \ + if (!m_p25->m_control) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, unsupported service, srcId = %u", _SRCID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_SYS_UNSUPPORTED_SVC, _PCKT); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Validate the source RID. +#define VALID_SRCID(_PCKT_STR, _PCKT, _SRCID) \ + if (!acl::AccessControl::validateSrcId(_SRCID)) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, srcId = %u", _SRCID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_VALID, _PCKT); \ + denialInhibit(_SRCID); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Validate the target RID. +#define VALID_DSTID(_PCKT_STR, _PCKT, _DSTID) \ + if (!acl::AccessControl::validateSrcId(_DSTID)) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, dstId = %u", _DSTID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_TGT_UNIT_NOT_VALID, _PCKT); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Validate the talkgroup ID. +#define VALID_TGID(_PCKT_STR, _PCKT, _DSTID) \ + if (!acl::AccessControl::validateTGId(_DSTID)) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, TGID rejection, dstId = %u", _DSTID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_TGT_GROUP_NOT_VALID, _PCKT); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Verify the source RID is registered. +#define VERIFY_SRCID_REG(_PCKT_STR, _PCKT, _SRCID) \ + if (!hasSrcIdUnitReg(_SRCID) && m_verifyReg) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID not registered, srcId = %u", _SRCID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_AUTH, _PCKT); \ + writeRF_TSDU_U_Reg_Cmd(_SRCID); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Verify the source RID is affiliated. +#define VERIFY_SRCID_AFF(_PCKT_STR, _PCKT, _SRCID, _DSTID) \ + if (!hasSrcIdGrpAff(_SRCID, _DSTID) && m_verifyAff) { \ + LogWarning(LOG_RF, P25_TSDU_STR ", " _PCKT_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", _SRCID, _DSTID); \ + writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_AUTH, _PCKT); \ + writeRF_TSDU_U_Reg_Cmd(_SRCID); \ + m_p25->checkAndReject(); \ + return false; \ + } + +// Validate the source RID (network). +#define VALID_SRCID_NET(_PCKT_STR, _SRCID) \ + if (!acl::AccessControl::validateSrcId(_SRCID)) { \ + LogWarning(LOG_NET, P25_TSDU_STR ", " _PCKT_STR " denial, RID rejection, srcId = %u", _SRCID); \ + return false; \ + } + +// Validate the target RID (network). +#define VALID_DSTID_NET(_PCKT_STR, _DSTID) \ + if (!acl::AccessControl::validateSrcId(_DSTID)) { \ + LogWarning(LOG_NET, P25_TSDU_STR ", " _PCKT_STR " denial RID rejection, dstId = %u", _DSTID); \ + return false; \ + } + +#define RF_TO_WRITE_NET() \ + if (m_network != NULL) { \ + uint8_t _buf[P25_TSDU_FRAME_LENGTH_BYTES]; \ + writeNet_TSDU_From_RF(_buf); \ + writeNetworkRF(_buf, true); \ + } + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t ADJ_SITE_TIMER_TIMEOUT = 30U; +const uint32_t ADJ_SITE_UPDATE_CNT = 5U; +const uint32_t TSDU_CTRL_BURST_COUNT = 2U; +const uint32_t TSBK_MBF_CNT = 3U; +const uint32_t GRANT_TIMER_TIMEOUT = 15U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Sets local configured site data. +/// +/// P25 Network ID. +/// P25 System ID. +/// P25 RFSS ID. +/// P25 Site ID. +/// P25 Location Resource Area. +/// Channel ID. +/// Channel Number. +void TrunkPacket::setSiteData(uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t lra, + uint8_t channelId, uint32_t channelNo) +{ + m_siteData = SiteData(netId, sysId, rfssId, siteId, lra, channelId, channelNo); + + m_rfTSBK.setSiteData(m_siteData); + m_rfTDULC.setSiteData(m_siteData); + m_p25->m_voice->m_rfLC.setSiteData(m_siteData); + + m_netTSBK.setSiteData(m_siteData); + m_netTDULC.setSiteData(m_siteData); + m_p25->m_voice->m_netLC.setSiteData(m_siteData); +} + +/// +/// Sets local configured site callsign. +/// +/// +void TrunkPacket::setCallsign(std::string callsign) +{ + m_rfTSBK.setCallsign(callsign); + m_netTSBK.setCallsign(callsign); +} + +/// +/// Sets a flag indicating whether or not networking is active. +/// +/// +void TrunkPacket::setNetActive(bool active) +{ + m_rfTSBK.setNetActive(active); + m_rfTDULC.setNetActive(active); + m_netTSBK.setNetActive(active); + m_netTDULC.setNetActive(active); +} + +/// +/// Sets the total number of channels at the site. +/// +/// +void TrunkPacket::setSiteChCnt(uint8_t chCnt) +{ + m_rfTSBK.setSiteChCnt(chCnt); + m_netTSBK.setSiteChCnt(chCnt); +} + +/// +/// Resets the data states for the RF interface. +/// +void TrunkPacket::resetRF() +{ + m_rfTSBK.reset(); + m_rfTDULC.reset(); +} + +/// +/// Resets the data states for the network. +/// +void TrunkPacket::resetNet() +{ + m_netTSBK.reset(); + m_netTDULC.reset(); +} + +/// +/// Sets the RF TSBK and TDULC data to match the given LC data. +/// +/// +void TrunkPacket::setRFLC(const lc::LC& lc) +{ + m_rfTSBK.reset(); + m_rfTDULC.reset(); + + m_rfTSBK.setProtect(lc.getProtect()); + m_rfTDULC.setProtect(lc.getProtect()); + m_rfTSBK.setMFId(lc.getMFId()); + m_rfTDULC.setMFId(lc.getMFId()); + + m_rfTSBK.setSrcId(lc.getSrcId()); + m_rfTDULC.setSrcId(lc.getSrcId()); + m_rfTSBK.setDstId(lc.getDstId()); + m_rfTDULC.setDstId(lc.getDstId()); + + m_rfTSBK.setGrpVchNo(lc.getGrpVchNo()); + m_rfTDULC.setGrpVchNo(lc.getGrpVchNo()); + + m_rfTSBK.setEmergency(lc.getEmergency()); + m_rfTDULC.setEmergency(lc.getEmergency()); + m_rfTSBK.setEncrypted(lc.getEncrypted()); + m_rfTDULC.setEncrypted(lc.getEmergency()); + m_rfTSBK.setPriority(lc.getPriority()); + m_rfTDULC.setPriority(lc.getPriority()); + + m_rfTSBK.setGroup(lc.getGroup()); + m_rfTDULC.setGroup(lc.getGroup()); +} + +/// +/// Sets the network TSBK and TDULC data to match the given LC data. +/// +/// +void TrunkPacket::setNetLC(const lc::LC& lc) +{ + m_netTSBK.reset(); + m_netTDULC.reset(); + + m_netTSBK.setProtect(lc.getProtect()); + m_netTDULC.setProtect(lc.getProtect()); + m_netTSBK.setMFId(lc.getMFId()); + m_netTDULC.setMFId(lc.getMFId()); + + m_netTSBK.setSrcId(lc.getSrcId()); + m_netTDULC.setSrcId(lc.getSrcId()); + m_netTSBK.setDstId(lc.getDstId()); + m_netTDULC.setDstId(lc.getDstId()); + + m_netTSBK.setGrpVchNo(lc.getGrpVchNo()); + m_netTDULC.setGrpVchNo(lc.getGrpVchNo()); + + m_netTSBK.setEmergency(lc.getEmergency()); + m_netTDULC.setEmergency(lc.getEmergency()); + m_netTSBK.setEncrypted(lc.getEncrypted()); + m_netTDULC.setEncrypted(lc.getEmergency()); + m_netTSBK.setPriority(lc.getPriority()); + m_netTDULC.setPriority(lc.getPriority()); + + m_netTSBK.setGroup(lc.getGroup()); + m_netTDULC.setGroup(lc.getGroup()); +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool TrunkPacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + // Decode the NID + bool valid = m_p25->m_nid.decode(data + 2U); + + if (m_p25->m_rfState == RS_RF_LISTENING && !valid) + return false; + + RPT_RF_STATE prevRfState = m_p25->m_rfState; + uint8_t duid = m_p25->m_nid.getDUID(); + + // handle individual DUIDs + if (duid == P25_DUID_TSDU) { + if (m_p25->m_rfState != RS_RF_DATA) { + m_p25->m_rfState = RS_RF_DATA; + } + + m_p25->m_queue.clear(); + m_rfTSBK.reset(); + m_netTSBK.reset(); + + bool ret = m_rfTSBK.decode(data + 2U); + if (!ret) { + LogWarning(LOG_RF, P25_TSDU_STR ", undecodable LC"); + m_p25->m_rfState = prevRfState; + return false; + } + + uint32_t srcId = m_rfTSBK.getSrcId(); + uint32_t dstId = m_rfTSBK.getDstId(); + + resetStatusCommand(m_rfTSBK); + + m_p25->writeRF_Preamble(); + + switch (m_rfTSBK.getLCO()) { + case TSBK_IOSP_GRP_VCH: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId); + + // validate the source RID + VALID_SRCID("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId); + + // validate the talkgroup ID + VALID_TGID("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, dstId); + + // verify the source RID is affiliated + VERIFY_SRCID_AFF("TSBK_IOSP_GRP_VCH (Group Voice Channel Request)", TSBK_IOSP_GRP_VCH, srcId, dstId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request), srcId = %u, dstId = %u", srcId, dstId); + } + + writeRF_TSDU_Grant(true, false); + break; + case TSBK_IOSP_UU_VCH: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); + + // validate the source RID + VALID_SRCID("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); + + // validate the target RID + VALID_DSTID("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, dstId); + + // verify the source RID is registered + VERIFY_SRCID_REG("TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request)", TSBK_IOSP_UU_VCH, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), srcId = %u, dstId = %u", srcId, dstId); + } + + writeRF_TSDU_UU_Ans_Req(srcId, dstId); + break; + case TSBK_IOSP_UU_ANS: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, srcId); + + // validate the source RID + VALID_SRCID("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, srcId); + + // validate the target RID + VALID_DSTID("TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response)", TSBK_IOSP_UU_ANS, dstId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Response), response = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getResponse(), srcId, dstId); + } + + if (m_rfTSBK.getResponse() == P25_ANS_RSP_PROCEED) { + writeRF_TSDU_Grant(false, false); + } + else if (m_rfTSBK.getResponse() == P25_ANS_RSP_DENY) { + writeRF_TSDU_Deny(P25_DENY_RSN_TGT_UNIT_REFUSED, TSBK_IOSP_UU_ANS); + } + else if (m_rfTSBK.getResponse() == P25_ANS_RSP_WAIT) { + writeRF_TSDU_Queue(P25_QUE_RSN_TGT_UNIT_QUEUED, TSBK_IOSP_UU_ANS); + } + break; + case TSBK_IOSP_TELE_INT_ANS: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_TELE_INT_ANS (Telephone Interconnect Answer Response)", TSBK_IOSP_TELE_INT_ANS, srcId); + + // validate the source RID + VALID_SRCID("TSBK_IOSP_TELE_INT_ANS (Telephone Interconnect Answer Response)", TSBK_IOSP_TELE_INT_ANS, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_TELE_INT_ANS (Telephone Interconnect Answer Response), response = $%02X, srcId = %u", + m_rfTSBK.getResponse(), srcId); + } + + if (m_rfTSBK.getResponse() == P25_ANS_RSP_PROCEED) { + //writeRF_TSDU_Grant(false); + writeRF_TSDU_Deny(P25_DENY_RSN_SYS_UNSUPPORTED_SVC, TSBK_IOSP_TELE_INT_ANS); + } + else if (m_rfTSBK.getResponse() == P25_ANS_RSP_DENY) { + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_TELE_INT_ANS, true); + } + else if (m_rfTSBK.getResponse() == P25_ANS_RSP_WAIT) { + writeRF_TSDU_Queue(P25_QUE_RSN_TGT_UNIT_QUEUED, TSBK_IOSP_TELE_INT_ANS); + } + break; + case TSBK_IOSP_STS_UPDT: + // validate the source RID + VALID_SRCID("TSBK_IOSP_STS_UPDT (Status Update)", TSBK_IOSP_STS_UPDT, srcId); + + if ((m_statusSrcId == 0U) && (m_statusValue == 0U)) { + RF_TO_WRITE_NET(); + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), status = $%02X, srcId = %u", m_rfTSBK.getStatus(), srcId); + } + + ::ActivityLog("P25", true, "received status update from %u", srcId); + + if (!m_noStatusAck) { + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_STS_UPDT, false); + } + + if (m_statusCmdEnable) { + preprocessStatusCommand(); + } + break; + case TSBK_IOSP_MSG_UPDT: + // validate the source RID + VALID_SRCID("TSBK_IOSP_MSG_UPDT (Message Update)", TSBK_IOSP_MSG_UPDT, srcId); + + RF_TO_WRITE_NET(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_MSG_UPDT (Message Update), message = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getMessage(), srcId, dstId); + } + + if (!m_noMessageAck) { + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_MSG_UPDT, false); + } + + ::ActivityLog("P25", true, "received message update from %u", srcId); + break; + case TSBK_IOSP_CALL_ALRT: + // validate the source RID + VALID_SRCID("TSBK_IOSP_CALL_ALRT (Call Alert)", TSBK_IOSP_CALL_ALRT, srcId); + + // is status command mode enabled with status data? + if (m_statusCmdEnable) { + if (processStatusCommand(srcId, dstId)) { + m_p25->m_rfState = prevRfState; + return true; + } + + resetStatusCommand(); + } + + // validate the target RID + VALID_DSTID("TSBK_IOSP_CALL_ALRT (Call Alert)", TSBK_IOSP_CALL_ALRT, dstId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); + } + + ::ActivityLog("P25", true, "received call alert request from %u to %u", srcId, dstId); + + writeRF_TSDU_Call_Alrt(srcId, dstId); + break; + case TSBK_IOSP_ACK_RSP: + // validate the source RID + VALID_SRCID("TSBK_IOSP_ACK_RSP (Acknowledge Response)", TSBK_IOSP_ACK_RSP, srcId); + + // validate the target RID + VALID_DSTID("TSBK_IOSP_ACK_RSP (Acknowledge Response)", TSBK_IOSP_ACK_RSP, dstId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getAIV(), m_rfTSBK.getService(), srcId, dstId); + } + + ::ActivityLog("P25", true, "received ack response from %u to %u", srcId, dstId); + + // bryanb: HACK -- for some reason, if the AIV is false and we have a dstId + // its very likely srcId and dstId are swapped so we'll swap them + if (!m_rfTSBK.getAIV() && dstId != 0U) { + m_rfTSBK.setAIV(true); + m_rfTSBK.setSrcId(dstId); + m_rfTSBK.setDstId(srcId); + } + + writeRF_TSDU_SBF(false); + break; + case TSBK_ISP_CAN_SRV_REQ: + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_CAN_SRV_REQ (Cancel Service Request), AIV = %u, serviceType = $%02X, reason = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getAIV(), m_rfTSBK.getService(), m_rfTSBK.getResponse(), srcId, dstId); + } + + ::ActivityLog("P25", true, "received cancel service request from %u", srcId); + + writeRF_TSDU_ACK_FNE(srcId, TSBK_ISP_CAN_SRV_REQ, true); + break; + case TSBK_IOSP_EXT_FNCT: + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", + m_rfTSBK.getExtendedFunction(), dstId, srcId); + } + + // is status control mode enabled with status data? + if (m_statusCmdEnable && (m_statusValue != 0U)) { + m_rfTSBK.setLCO(TSBK_IOSP_ACK_RSP); + m_rfTSBK.setAIV(true); + m_rfTSBK.setService(TSBK_IOSP_CALL_ALRT); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), serviceType = $%02X, srcId = %u", + m_rfTSBK.getService(), m_statusSrcId); + } + } + + // generate activity log entry + if (m_rfTSBK.getExtendedFunction() == P25_EXT_FNCT_CHECK_ACK) { + ::ActivityLog("P25", true, "received radio check response from %u to %u", dstId, srcId); + } + else if (m_rfTSBK.getExtendedFunction() == P25_EXT_FNCT_INHIBIT_ACK) { + ::ActivityLog("P25", true, "received radio inhibit response from %u to %u", dstId, srcId); + } + else if (m_rfTSBK.getExtendedFunction() == P25_EXT_FNCT_UNINHIBIT_ACK) { + ::ActivityLog("P25", true, "received radio uninhibit response from %u to %u", dstId, srcId); + } + + writeRF_TSDU_SBF(true); + resetStatusCommand(); + break; + case TSBK_IOSP_GRP_AFF: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_AFF (Group Affiliation Request)", TSBK_IOSP_GRP_AFF, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Request), srcId = %u, dstId = %u", srcId, dstId); + } + + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_GRP_AFF, true); + writeRF_TSDU_Grp_Aff_Rsp(srcId, dstId); + break; + case TSBK_ISP_GRP_AFF_Q_RSP: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_IOSP_GRP_AFF (Group Affiliation Query Response)", TSBK_ISP_GRP_AFF_Q_RSP, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Query Response), srcId = %u, dstId = %u, anncId = %u", srcId, dstId, + m_rfTSBK.getPatchSuperGroupId()); + } + + ::ActivityLog("P25", true, "received group affiliation query response from %u to %s %u", srcId, "TG ", dstId); + break; + case TSBK_ISP_U_DEREG_REQ: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request)", TSBK_ISP_U_DEREG_REQ, srcId); + + // validate the source RID + VALID_SRCID("TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request)", TSBK_ISP_U_DEREG_REQ, srcId); + + // HACK: ensure the DEREG_REQ transmits something ... + if (dstId == 0U) { + dstId = P25_WUID_SYS; + } + + writeRF_TSDU_ACK_FNE(srcId, TSBK_ISP_U_DEREG_REQ, true); + writeRF_TSDU_U_Dereg_Ack(srcId); + break; + case TSBK_IOSP_U_REG: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_U_REG_REQ (Unit Registration Request)", TSBK_IOSP_U_REG, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_U_REG_REQ (Unit Registration Request), srcId = %u", srcId); + } + + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_U_REG, true); + writeRF_TSDU_U_Reg_Rsp(srcId); + break; + case TSBK_ISP_LOC_REG_REQ: + // make sure control data is supported + IS_SUPPORT_CONTROL_CHECK("TSBK_ISP_LOC_REG_REQ (Location Registration Request)", TSBK_ISP_LOC_REG_REQ, srcId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_LOC_REG_REQ (Location Registration Request), srcId = %u, dstId = %u", srcId, dstId); + } + + writeRF_TSDU_U_Reg_Cmd(srcId); + break; + default: + LogError(LOG_RF, P25_TSDU_STR ", unhandled LCO, mfId = $%02X, lco = $%02X", m_rfTSBK.getMFId(), m_rfTSBK.getLCO()); + break; + } + + // add trailing null pad; only if control data isn't being transmitted + if (!m_p25->m_ccRunning) { + m_p25->writeRF_Nulls(); + } + + m_p25->m_rfState = prevRfState; + return true; + } + else { + LogError(LOG_RF, "P25 unhandled data DUID, duid = $%02X", duid); + } + + return false; +} + +/// +/// Process a data frame from the network. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +/// +/// +/// +bool TrunkPacket::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid) +{ + if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_netState == RS_NET_IDLE) + return false; + + switch (duid) { + case P25_DUID_TSDU: + if (m_p25->m_netState == RS_NET_IDLE) { + m_rfTSBK.reset(); + m_netTSBK.reset(); + + bool ret = m_netTSBK.decode(data); + if (!ret) { + return false; + } + + // handle updating internal adjacent site information + if (m_netTSBK.getLCO() == TSBK_OSP_ADJ_STS_BCAST) { + if (!m_p25->m_control) { + return false; + } + + if (m_netTSBK.getAdjSiteId() != m_siteData.siteId()) { + // update site table data + SiteData site; + try { + site = m_adjSiteTable.at(m_netTSBK.getAdjSiteId()); + } catch (...) { + site = SiteData(); + } + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u", + m_netTSBK.getAdjSiteSysId(), m_netTSBK.getAdjSiteRFSSId(), m_netTSBK.getAdjSiteId(), m_netTSBK.getAdjSiteChnId(), m_netTSBK.getAdjSiteChnNo()); + } + + site.setAdjSite(m_netTSBK.getAdjSiteSysId(), m_netTSBK.getAdjSiteRFSSId(), + m_netTSBK.getAdjSiteId(), m_netTSBK.getAdjSiteChnId(), m_netTSBK.getAdjSiteChnNo()); + + m_adjSiteTable[site.siteId()] = site; + m_adjSiteUpdateCnt[site.siteId()] = ADJ_SITE_UPDATE_CNT; + } + + return true; + } + + uint32_t srcId = m_netTSBK.getSrcId(); + uint32_t dstId = m_netTSBK.getDstId(); + + resetStatusCommand(m_netTSBK); + + switch (m_netTSBK.getLCO()) { + case TSBK_IOSP_STS_UPDT: + // validate the source RID + VALID_SRCID_NET("TSBK_IOSP_STS_UPDT (Status Update)", srcId); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), status = $%02X, srcId = %u", + m_netTSBK.getStatus(), srcId); + } + + ::ActivityLog("P25", false, "received status update from %u", srcId); + break; + case TSBK_IOSP_MSG_UPDT: + // validate the source RID + VALID_SRCID_NET("TSBK_IOSP_MSG_UPDT (Message Update)", srcId); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_MSG_UPDT (Message Update), message = $%02X, srcId = %u, dstId = %u", + m_netTSBK.getMessage(), srcId, dstId); + } + + ::ActivityLog("P25", false, "received message update from %u", srcId); + break; + case TSBK_IOSP_CALL_ALRT: + // validate the source RID + VALID_SRCID_NET("TSBK_IOSP_CALL_ALRT (Call Alert)", srcId); + + // validate the target RID + VALID_DSTID_NET("TSBK_IOSP_CALL_ALRT (Call Alert)", dstId); + + // validate source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_NET, "P25_DUID_TSDU (Trunking System Data Unit) denial, RID rejection, srcId = %u", srcId); + return false; + } + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); + } + + ::ActivityLog("P25", false, "received call alert request from %u to %u", srcId, dstId); + break; + case TSBK_IOSP_ACK_RSP: + // validate the source RID + VALID_SRCID_NET("TSBK_IOSP_ACK_RSP (Acknowledge Response)", srcId); + + // validate the target RID + VALID_DSTID_NET("TSBK_IOSP_ACK_RSP (Acknowledge Response)", dstId); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, serviceType = $%02X, srcId = %u, dstId = %u", + m_netTSBK.getAIV(), m_netTSBK.getService(), dstId, srcId); + } + + ::ActivityLog("P25", false, "received ack response from %u to %u", srcId, dstId); + break; + case TSBK_IOSP_EXT_FNCT: + // validate the target RID + VALID_DSTID_NET("TSBK_IOSP_EXT_FNCT (Extended Function)", dstId); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), serviceType = $%02X, arg = %u, tgt = %u", + m_netTSBK.getService(), srcId, dstId); + } + + resetStatusCommand(); + break; + case TSBK_IOSP_GRP_AFF: + // ignore a network group affiliation command + break; + case TSBK_OSP_U_DEREG_ACK: + // ignore a network user deregistration command + break; + case TSBK_OSP_DENY_RSP: + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_DENY_RSP (Deny Response), reason = %u, srcId = %u, dstId = %u", + m_netTSBK.getResponse(), m_netTSBK.getSrcId(), m_netTSBK.getDstId()); + } + break; + case TSBK_OSP_QUE_RSP: + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_QUE_RSP (Queue Response), reason = %u, srcId = %u, dstId = %u", + m_netTSBK.getResponse(), m_netTSBK.getSrcId(), m_netTSBK.getDstId()); + } + break; + default: + LogError(LOG_NET, P25_TSDU_STR ", unhandled LCO, mfId = $%02X, lco = $%02X", m_netTSBK.getMFId(), m_netTSBK.getLCO()); + return false; + } + + writeNet_TSDU(); + } + break; + default: + return false; + } + + return true; +} + +/// +/// Helper to write P25 adjacent site information to the network. +/// +void TrunkPacket::writeAdjSSNetwork() +{ + if (!m_p25->m_control) { + return; + } + + m_rfTSBK.reset(); + m_netTSBK.reset(); + + if (m_network != NULL) { + if (m_verbose) { + LogMessage(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), network announce, sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u", + m_siteData.sysId(), m_siteData.rfssId(), m_siteData.siteId(), m_siteData.channelId(), m_siteData.channelNo()); + } + + // transmit adjacent site broadcast + m_rfTSBK.setLCO(TSBK_OSP_ADJ_STS_BCAST); + m_rfTSBK.setAdjSiteCFVA(P25_CFVA_CONV | P25_CFVA_VALID); + m_rfTSBK.setAdjSiteSysId(m_siteData.sysId()); + m_rfTSBK.setAdjSiteRFSSId(m_siteData.rfssId()); + m_rfTSBK.setAdjSiteId(m_siteData.siteId()); + m_rfTSBK.setAdjSiteChnId(m_siteData.channelId()); + m_rfTSBK.setAdjSiteChnNo(m_siteData.channelNo()); + + RF_TO_WRITE_NET(); + } +} + +/// +/// Helper to determine if the source ID has affiliated to the group destination ID. +/// +/// +/// +/// +bool TrunkPacket::hasSrcIdGrpAff(uint32_t srcId, uint32_t dstId) const +{ + // lookup dynamic affiliation table entry + try { + uint32_t tblDstId = m_grpAffTable.at(srcId); + if (tblDstId == dstId) { + return true; + } + else { + return false; + } + } catch (...) { + return false; + } +} + +/// +/// Helper to determine if the source ID has unit registered. +/// +/// +/// +bool TrunkPacket::hasSrcIdUnitReg(uint32_t srcId) const +{ + // lookup dynamic unit registration table entry + if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { + return true; + } + else { + return false; + } +} + +/// +/// Helper to determine if the channel number is busy. +/// +/// +/// +bool TrunkPacket::isChBusy(uint32_t chNo) const +{ + if (chNo == 0U) { + return false; + } + + // lookup dynamic channel grant table entry + for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) { + if (it->second == chNo) { + return true; + } + } + + return false; +} + +/// +/// Helper to determine if the destination ID is already granted. +/// +/// +/// +bool TrunkPacket::hasDstIdGranted(uint32_t dstId) const +{ + if (dstId == 0U) { + return false; + } + + // lookup dynamic channel grant table entry + try { + uint32_t chNo = m_grantChTable.at(dstId); + if (chNo != 0U) { + return true; + } + else { + return false; + } + } catch (...) { + return false; + } +} + +/// +/// Helper to start the destination ID grant timer. +/// +/// +/// +void TrunkPacket::touchDstIdGrant(uint32_t dstId) +{ + if (dstId == 0U) { + return; + } + + if (hasDstIdGranted(dstId)) { + m_grantTimers[dstId].start(); + } +} + +/// +/// Helper to release the channel grant for the destination ID. +/// +/// +/// +void TrunkPacket::releaseDstIdGrant(uint32_t dstId, bool releaseAll) +{ + if (dstId == 0U && !releaseAll) { + return; + } + + if (dstId == 0U && releaseAll) { + LogWarning(LOG_RF, "P25, force releasing all channel grants"); + + std::vector gntsToRel = std::vector(); + for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) { + uint32_t dstId = it->first; + gntsToRel.push_back(dstId); + } + + // release grants + for (auto it = gntsToRel.begin(); it != gntsToRel.end(); ++it) { + releaseDstIdGrant(*it, false); + } + + return; + } + + if (hasDstIdGranted(dstId)) { + uint32_t chNo = m_grantChTable.at(dstId); + + if (m_verbose) { + LogMessage(LOG_RF, "P25, releasing channel grant, chNo = %u, dstId = %u", + chNo, dstId); + } + + m_grantChTable[dstId] = 0U; + m_voiceChTable.push_back(chNo); + + if (m_voiceGrantChCnt > 0U) { + m_voiceGrantChCnt--; + setSiteChCnt(m_voiceChCnt + m_voiceGrantChCnt); + } + else { + m_voiceGrantChCnt = 0U; + setSiteChCnt(m_voiceChCnt); + } + + m_grantTimers[dstId].stop(); + } +} + +/// +/// +/// +void TrunkPacket::resetStatusCommand() +{ + // reset status control data + if (m_statusCmdEnable) { + if (m_statusSrcId != 0U && m_statusValue != 0U) { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), canceled command mode, statusCurrentStatus = $%02X", m_statusValue); + } + } + + m_statusSrcId = 0U; + m_statusValue = 0U; + } +} + +/// +/// Updates the processor by the passed number of milliseconds. +/// +/// +void TrunkPacket::clock(uint32_t ms) +{ + if (m_p25->m_control) { + // clock all the grant timers + std::vector gntsToRel = std::vector(); + for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) { + uint32_t dstId = it->first; + + m_grantTimers[dstId].clock(ms); + if (m_grantTimers[dstId].isRunning() && m_grantTimers[dstId].hasExpired()) { + gntsToRel.push_back(dstId); + } + } + + // release grants that have timed out + for (auto it = gntsToRel.begin(); it != gntsToRel.end(); ++it) { + releaseDstIdGrant(*it, false); + } + + // clock adjacent site update timers + m_adjSiteUpdateTimer.clock(ms); + if (m_adjSiteUpdateTimer.isRunning() && m_adjSiteUpdateTimer.hasExpired()) { + for (auto it = m_adjSiteUpdateCnt.begin(); it != m_adjSiteUpdateCnt.end(); ++it) { + uint8_t siteId = it->first; + + uint8_t updateCnt = it->second; + if (updateCnt > 0U) { + updateCnt--; + } + + if (updateCnt == 0U) { + SiteData siteData = m_adjSiteTable[siteId]; + LogWarning(LOG_NET, P25_TSDU_STR ", TSBK_OSP_ADJ_STS_BCAST (Adjacent Site Status Broadcast), no data [FAILED], sysId = $%03X, rfss = $%02X, site = $%02X, chId = %u, chNo = %u", + siteData.sysId(), siteData.rfssId(), siteData.siteId(), siteData.channelId(), siteData.channelNo()); + } + + m_adjSiteUpdateCnt[siteId] = updateCnt; + } + + m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); + m_adjSiteUpdateTimer.start(); + } + } +} + +/// +/// Helper to write a call alert packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Call_Alrt(uint32_t srcId, uint32_t dstId) +{ + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_CALL_ALRT (Call Alert), srcId = %u, dstId = %u", srcId, dstId); + } + + ::ActivityLog("P25", true, "received call alert request from %u to %u", srcId, dstId); + + m_rfTSBK.setLCO(TSBK_IOSP_CALL_ALRT); + m_rfTSBK.setSrcId(srcId); + m_rfTSBK.setDstId(dstId); + writeRF_TSDU_SBF(false); +} + +/// +/// Helper to write a extended function packet. +/// +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId) +{ + uint8_t lco = m_rfTSBK.getLCO(); + uint8_t mfId = m_rfTSBK.getMFId(); + + m_rfTSBK.setMFId(P25_MFG_STANDARD); + + m_rfTSBK.setLCO(TSBK_IOSP_EXT_FNCT); + m_rfTSBK.setExtendedFunction(func); + m_rfTSBK.setSrcId(arg); + m_rfTSBK.setDstId(dstId); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_EXT_FNCT (Extended Function), op = $%02X, arg = %u, tgt = %u", + m_rfTSBK.getExtendedFunction(), m_rfTSBK.getSrcId(), m_rfTSBK.getDstId()); + } + + // generate activity log entry + if (func == P25_EXT_FNCT_CHECK) { + ::ActivityLog("P25", true, "received radio check request from %u to %u", arg, dstId); + } + else if (func == P25_EXT_FNCT_INHIBIT) { + ::ActivityLog("P25", true, "received radio inhibit request from %u to %u", arg, dstId); + } + else if (func == P25_EXT_FNCT_UNINHIBIT) { + ::ActivityLog("P25", true, "received radio uninhibit request from %u to %u", arg, dstId); + } + + writeRF_TSDU_SBF(false); + + m_rfTSBK.setLCO(lco); + m_rfTSBK.setMFId(mfId); +} + +/// +/// Helper to write a group affiliation query packet. +/// +/// +void TrunkPacket::writeRF_TSDU_Grp_Aff_Q(uint32_t dstId) +{ + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_GRP_AFF_Q (Group Affiliation Query), dstId = %u", dstId); + } + + ::ActivityLog("P25", true, "received group affiliation query command from %u to %u", P25_WUID_SYS, dstId); + + m_rfTSBK.setLCO(TSBK_OSP_GRP_AFF_Q); + m_rfTSBK.setSrcId(P25_WUID_SYS); + m_rfTSBK.setDstId(dstId); + writeRF_TSDU_SBF(true); +} + +/// +/// Helper to write a unit registration command packet. +/// +/// +void TrunkPacket::writeRF_TSDU_U_Reg_Cmd(uint32_t dstId) +{ + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_U_REG_CMD (Unit Registration Command), dstId = %u", dstId); + } + + ::ActivityLog("P25", true, "received unit registration command from %u to %u", P25_WUID_SYS, dstId); + + m_rfTSBK.setLCO(TSBK_OSP_U_REG_CMD); + m_rfTSBK.setSrcId(P25_WUID_SYS); + m_rfTSBK.setDstId(dstId); + writeRF_TSDU_SBF(true); +} + +/// +/// Helper to write a Motorola patch packet. +/// +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Mot_Patch(uint32_t group1, uint32_t group2, uint32_t group3) +{ + uint8_t lco = m_rfTSBK.getLCO(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_MOT_GRG_ADD (Group Regroup Add - Patch Supergroup), superGrp = %u, group1 = %u, group2 = %u, group3 = %u", + m_patchSuperGroup, group1, group2, group3); + } + + m_rfTSBK.setLCO(TSBK_OSP_MOT_GRG_ADD); + m_rfTSBK.setMFId(P25_MFG_MOT); + m_rfTSBK.setPatchSuperGroupId(m_patchSuperGroup); + m_rfTSBK.setPatchGroup1Id(group1); + m_rfTSBK.setPatchGroup2Id(group2); + m_rfTSBK.setPatchGroup3Id(group3); + writeRF_TSDU_SBF(true); + + m_rfTSBK.setLCO(lco); + m_rfTSBK.setMFId(P25_MFG_STANDARD); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the TrunkPacket class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +TrunkPacket::TrunkPacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose) : + m_p25(p25), + m_network(network), + m_patchSuperGroup(0xFFFFU), + m_verifyAff(false), + m_verifyReg(false), + m_rfTSBK(), + m_netTSBK(), + m_rfMBF(NULL), + m_mbfCnt(0U), + m_mbfIdenCnt(0U), + m_mbfAdjSSCnt(0U), + m_rfTDULC(), + m_netTDULC(), + m_voiceChTable(), + m_adjSiteTable(), + m_adjSiteUpdateCnt(), + m_unitRegTable(), + m_grpAffTable(), + m_grantChTable(), + m_grantTimers(), + m_voiceChCnt(1U), + m_voiceGrantChCnt(0U), + m_noStatusAck(false), + m_noMessageAck(true), + m_statusCmdEnable(false), + m_statusRadioCheck(0U), + m_statusRadioInhibit(0U), + m_statusRadioUninhibit(0U), + m_statusRadioForceReg(0U), + m_statusRadioForceDereg(0U), + m_statusSrcId(0U), + m_statusValue(0U), + m_siteData(), + m_adjSiteUpdateTimer(1000U), + m_adjSiteUpdateInterval(ADJ_SITE_TIMER_TIMEOUT), + m_skipSBFPreamble(false), + m_verbose(verbose), + m_debug(debug) +{ + m_rfMBF = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(m_rfMBF, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + + // set metadata defaults + m_rfTSBK.setSiteData(m_siteData); + m_netTSBK.setSiteData(m_siteData); + m_rfTSBK.setCallsign("CHANGEME"); + m_netTSBK.setCallsign("CHANGEME"); + + m_rfTDULC.setSiteData(m_siteData); + m_netTDULC.setSiteData(m_siteData); + + m_voiceChTable.clear(); + + m_adjSiteTable.clear(); + m_adjSiteUpdateCnt.clear(); + + m_unitRegTable.clear(); + m_grpAffTable.clear(); + + m_grantChTable.clear(); + m_grantTimers.clear(); + + m_adjSiteUpdateInterval = ADJ_SITE_TIMER_TIMEOUT + m_p25->m_ccBcstInterval; + m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); + m_adjSiteUpdateTimer.start(); +} + +/// +/// Finalizes a instance of the TrunkPacket class. +/// +TrunkPacket::~TrunkPacket() +{ + delete[] m_rfMBF; +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +void TrunkPacket::writeNetworkRF(const uint8_t* data, bool autoReset) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) + return; + + m_network->writeP25TSDU(m_rfTSBK, data); + if (autoReset) + m_network->resetP25(); +} + +/// +/// Helper to write control channel packet data. +/// +/// +/// +void TrunkPacket::writeRF_ControlData(uint8_t frameCnt, bool adjSS) +{ + if (!m_p25->m_control) { + return; + } + + m_rfTSBK.reset(); + + bool alt = (frameCnt % 2) > 0U; + if (m_debug) { + LogDebug(LOG_P25, "writeRF_ControlData, mbfCnt = %u, frameCnt = %u, alt = %u, adjSS = %u", m_mbfCnt, frameCnt, alt, adjSS); + } + + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_IDEN_UP); + + if (alt) { + // write rfss-net-rfss bcast + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_RFSS_STS_BCAST); + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_NET_STS_BCAST); + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_RFSS_STS_BCAST); + } + else { + // write net-rfss-net bcast + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_NET_STS_BCAST); + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_RFSS_STS_BCAST); + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_NET_STS_BCAST); + } + + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_SNDCP_CH_ANN); + + // LogDebug(LOG_P25, "writeRF_ControlData, before adjSS, mbfCnt = %u", m_mbfCnt); + + // write ADJSS + if (adjSS && m_adjSiteTable.size() > 0) { + if (m_mbfAdjSSCnt >= m_adjSiteTable.size()) + m_mbfAdjSSCnt = 0U; + + uint8_t i = 0U; + for (auto it = m_adjSiteTable.begin(); it != m_adjSiteTable.end(); ++it) { + // no good very bad way of skipping entries... + if (i != m_mbfAdjSSCnt) { + i++; + continue; + } + else { + m_rfTSBK.reset(); + + SiteData site = it->second; + + uint8_t cfva = P25_CFVA_CONV | P25_CFVA_NETWORK; + if (m_adjSiteUpdateCnt[site.siteId()] == 0U) { + cfva |= P25_CFVA_FAILURE; + } + else { + cfva |= P25_CFVA_VALID; + } + + // transmit adjacent site broadcast + m_rfTSBK.setLCO(TSBK_OSP_ADJ_STS_BCAST); + m_rfTSBK.setAdjSiteCFVA(cfva); + m_rfTSBK.setAdjSiteSysId(site.sysId()); + m_rfTSBK.setAdjSiteRFSSId(site.rfssId()); + m_rfTSBK.setAdjSiteId(site.siteId()); + m_rfTSBK.setAdjSiteChnId(site.channelId()); + m_rfTSBK.setAdjSiteChnNo(site.channelNo()); + + m_rfTSBK.setLastBlock(true); // always set last block + writeRF_TSDU_MBF(); + + m_mbfAdjSSCnt++; + break; + } + } + } + + // LogDebug(LOG_P25, "writeRF_ControlData, after adjSS, mbfCnt = %u", m_mbfCnt); + + // should we insert the BSI bursts? + bool bsi = (frameCnt % 127) == 0U; + if (bsi) { + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_MOT_CC_BSI); + + m_rfTSBK.reset(); + + // transmit CC BSI burst + m_rfTSBK.setLCO(TSBK_OSP_MOT_CC_BSI); + m_rfTSBK.setMFId(P25_MFG_MOT); + + m_rfTSBK.setLastBlock(true); // always set last block + writeRF_TSDU_MBF(); + } + + // pad MBF if we have 1 queued TSDUs + if (m_mbfCnt == 1U) { + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_RFSS_STS_BCAST); + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_NET_STS_BCAST); + if (m_debug) { + LogDebug(LOG_P25, "writeRF_ControlData, have 1 pad 2, mbfCnt = %u", m_mbfCnt); + } + } + + // pad MBF if we have 2 queued TSDUs + if (m_mbfCnt == 2U) { + std::vector entries = m_p25->m_idenTable->list(); + if (entries.size() > 1U) { + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_IDEN_UP); + } + else { + queueRF_TSBK_Ctrl_MBF(TSBK_OSP_RFSS_STS_BCAST); + } + if (m_debug) { + LogDebug(LOG_P25, "writeRF_ControlData, have 2 pad 1, mbfCnt = %u", m_mbfCnt); + } + } + + // reset MBF count + m_mbfCnt = 0U; +} + +/// +/// Helper to write a P25 TDU w/ link control packet. +/// +/// +/// +void TrunkPacket::writeRF_TDULC(uint8_t duid, bool noNetwork) +{ + uint8_t data[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_TDULC); + + // Generate TDULC Data + m_rfTDULC.encode(data + 2U); + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_TDULC_FRAME_LENGTH_BITS, true, true); + + m_p25->m_rfTimeout.stop(); + + if (!noNetwork) + writeNetworkRF(data + 2U, P25_DUID_TDULC); + + if (m_p25->m_duplex) { + data[0U] = TAG_EOT; + data[1U] = 0x00U; + + m_p25->writeQueueRF(data, P25_TDULC_FRAME_LENGTH_BYTES + 2U); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_TDULC", data + 2U, P25_TDULC_FRAME_LENGTH_BYTES); + } +} + +/// +/// Helper to write a P25 TDU w/ link control channel grant packet. +/// +/// +/// +/// +void TrunkPacket::writeRF_TDULC_ChanGrant(bool grp, uint32_t srcId, uint32_t dstId) +{ + m_p25->writeRF_TDU(true); + m_p25->m_voice->m_lastDUID = P25_DUID_TDU; + + if ((srcId != 0U) && (dstId != 0U)) { + for (uint32_t i = 0; i < 4; i++) { + m_rfTDULC.setSrcId(srcId); + m_rfTDULC.setDstId(dstId); + m_rfTDULC.setEmergency(false); + + if (grp) { + m_rfTDULC.setLCO(LC_GROUP); + writeRF_TDULC(P25_DUID_TDULC, true); + } + else { + m_rfTDULC.setLCO(LC_PRIVATE); + writeRF_TDULC(P25_DUID_TDULC, true); + } + } + } +} + +/// +/// Helper to write a P25 TDU w/ link control channel release packet. +/// +/// +/// +/// +void TrunkPacket::writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint32_t dstId) +{ + uint32_t count = m_p25->m_hangCount / 2; + + for (uint32_t i = 0; i < count; i++) { + if ((srcId != 0U) && (dstId != 0U)) { + m_rfTDULC.setSrcId(srcId); + m_rfTDULC.setDstId(dstId); + m_rfTDULC.setEmergency(false); + + if (grp) { + m_rfTDULC.setLCO(LC_GROUP); + writeRF_TDULC(P25_DUID_TDULC, true); + } + else { + m_rfTDULC.setLCO(LC_PRIVATE); + writeRF_TDULC(P25_DUID_TDULC, true); + } + } + + m_rfTDULC.setLCO(LC_NET_STS_BCAST); + writeRF_TDULC(P25_DUID_TDULC, true); + m_rfTDULC.setLCO(LC_RFSS_STS_BCAST); + writeRF_TDULC(P25_DUID_TDULC, true); + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_TDULC_STR ", LC_CALL_TERM (Call Termination), srcId = %u, dstId = %u", m_rfTDULC.getSrcId(), m_rfTDULC.getDstId()); + } + + m_rfTDULC.setLCO(LC_CALL_TERM); + writeRF_TDULC(P25_DUID_TDULC, true); + + m_rfTDULC.reset(); +} + +/// +/// Helper to write a single-block P25 TSDU packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite) +{ + uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_TSDU); + + // Generate TSBK block + m_rfTSBK.setLastBlock(true); // always set last block -- this a Single Block TSDU + m_rfTSBK.encode(data + 2U, true); + + if (m_debug) { + Utils::dump(2U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES); + } + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, true, false); + + // Set first busy bits to 1,1 + m_p25->setBusyBits(data + 2U, P25_SS0_START, true, true); + + if (!noNetwork) + writeNetworkRF(data + 2U, true); + + if (m_p25->m_continuousControl) { + writeRF_TSDU_MBF(clearBeforeWrite); + return; + } + + if (m_p25->m_ccRunning) { + writeRF_TSDU_MBF(clearBeforeWrite); + return; + } + + if (clearBeforeWrite) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + } + + if (!m_skipSBFPreamble) { + m_p25->writeRF_Preamble(); + } + + m_skipSBFPreamble = false; + + if (m_p25->m_duplex) { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + m_p25->writeQueueRF(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - (SBF) P25_DUID_TSDU", data + 2U, P25_TSDU_FRAME_LENGTH_BYTES); + } +} + +/// +/// Helper to write a multi-block P25 TSDU packet. +/// +/// +void TrunkPacket::writeRF_TSDU_MBF(bool clearBeforeWrite) +{ + uint8_t tsbk[P25_TSBK_FEC_LENGTH_BYTES]; + ::memset(tsbk, 0x00U, P25_TSBK_FEC_LENGTH_BYTES); + + // LogDebug(LOG_P25, "writeRF_TSDU_MBF, mbfCnt = %u", m_mbfCnt); + + // can't transmit MBF with duplex disabled + if (!m_p25->m_duplex) { + ::memset(m_rfMBF, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + m_mbfCnt = 0U; + return; + } + + if (m_mbfCnt == 0U) { + ::memset(m_rfMBF, 0x00U, P25_TSBK_FEC_LENGTH_BYTES * TSBK_MBF_CNT); + } + + // trigger encoding of last block and write to queue + if (m_mbfCnt + 1U == TSBK_MBF_CNT) { + // Generate TSBK block + m_rfTSBK.setLastBlock(true); // set last block + m_rfTSBK.encode(tsbk, false); + + if (m_debug) { + Utils::dump(2U, "!!! *TSDU MBF Last TSBK Block", tsbk, P25_TSBK_FEC_LENGTH_BYTES); + } + + Utils::setBitRange(tsbk, m_rfMBF, (m_mbfCnt * P25_TSBK_FEC_LENGTH_BITS), P25_TSBK_FEC_LENGTH_BITS); + + // Generate TSDU frame + uint8_t tsdu[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES]; + ::memset(tsdu, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); + + uint32_t offset = 0U; + for (uint8_t i = 0U; i < m_mbfCnt + 1U; i++) { + ::memset(tsbk, 0x00U, P25_TSBK_FEC_LENGTH_BYTES); + Utils::getBitRange(m_rfMBF, tsbk, offset, P25_TSBK_FEC_LENGTH_BITS); + + if (m_debug) { + Utils::dump(2U, "!!! *TSDU (MBF) TSBK Block", tsbk, P25_TSBK_FEC_LENGTH_BYTES); + } + + // Add TSBK data + Utils::setBitRange(tsbk, tsdu, offset, P25_TSBK_FEC_LENGTH_BITS); + + offset += P25_TSBK_FEC_LENGTH_BITS; + } + + // Utils::dump(2U, "!!! *TSDU DEBUG - tsdu", tsdu, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); + + uint8_t data[P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_TSDU); + + // interleave + P25Utils::encode(tsdu, data + 2U, 114U, 720U); + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS, true, false); + + // Add idle bits + addIdleBits(data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BITS, true, true); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if (clearBeforeWrite) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + } + + m_p25->writeQueueRF(data, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES + 2U); + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - (MBF) P25_DUID_TSDU", data + 2U, P25_TSDU_TRIPLE_FRAME_LENGTH_BYTES); + } + + ::memset(m_rfMBF, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); + m_mbfCnt = 0U; + return; + } + + // Generate TSBK block + m_rfTSBK.setLastBlock(false); // clear last block + m_rfTSBK.encode(tsbk, false); + + if (m_debug) { + Utils::dump(2U, "!!! *TSDU MBF Block Data", tsbk, P25_TSBK_FEC_LENGTH_BYTES); + } + + Utils::setBitRange(tsbk, m_rfMBF, (m_mbfCnt * P25_TSBK_FEC_LENGTH_BITS), P25_TSBK_FEC_LENGTH_BITS); + m_mbfCnt++; +} + +/// +/// Helper to queue the given control TSBK into the MBF queue. +/// +/// +void TrunkPacket::queueRF_TSBK_Ctrl_MBF(uint8_t lco) +{ + m_rfTSBK.reset(); + + switch (lco) { + case TSBK_OSP_IDEN_UP: + { + std::vector entries = m_p25->m_idenTable->list(); + if (m_mbfIdenCnt >= entries.size()) + m_mbfIdenCnt = 0U; + + uint8_t i = 0U; + for (auto it = entries.begin(); it != entries.end(); ++it) { + // no good very bad way of skipping entries... + if (i != m_mbfIdenCnt) { + i++; + continue; + } + else { + lookups::IdenTable entry = *it; + + // LogDebug(LOG_P25, "baseFrequency = %uHz, txOffsetMhz = %fMHz, chBandwidthKhz = %fKHz, chSpaceKhz = %fKHz", + // entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); + + // handle 700/800/900 identities + if (entry.baseFrequency() >= 762000000U) { + m_rfTSBK.setIdenTable(entry); + + // transmit channel ident broadcast + m_rfTSBK.setLCO(TSBK_OSP_IDEN_UP); + } + else { + // handle as a VHF/UHF identity + m_rfTSBK.setIdenTable(entry); + + // transmit channel ident broadcast + m_rfTSBK.setLCO(TSBK_OSP_IDEN_UP_VU); + } + + m_mbfIdenCnt++; + break; + } + } + } + break; + case TSBK_OSP_NET_STS_BCAST: + if (m_debug) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_NET_STS_BCAST (Network Status Broadcast)"); + } + + // transmit net status burst + m_rfTSBK.setLCO(TSBK_OSP_NET_STS_BCAST); + break; + case TSBK_OSP_RFSS_STS_BCAST: + if (m_debug) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_RFSS_STS_BCAST (RFSS Status Broadcast)"); + } + + // transmit rfss status burst + m_rfTSBK.setLCO(TSBK_OSP_RFSS_STS_BCAST); + break; + case TSBK_OSP_SNDCP_CH_ANN: + if (m_debug) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_SNDCP_CH_ANN (SNDCP Channel Announcement)"); + } + + // transmit SNDCP announcement + m_rfTSBK.setLCO(TSBK_OSP_SNDCP_CH_ANN); + break; + + /** Motorola CC data */ + case TSBK_OSP_MOT_PSH_CCH: + if (m_debug) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_MOT_PSH_CCH (Motorola Planned Shutdown)"); + } + + // transmit motorola PSH CCH burst + m_rfTSBK.setLCO(TSBK_OSP_MOT_PSH_CCH); + m_rfTSBK.setMFId(P25_MFG_MOT); + break; + + case TSBK_OSP_MOT_CC_BSI: + if (m_debug) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_MOT_CC_BSI (Motorola Control Channel BSI)"); + } + + // transmit motorola CC BSI burst + m_rfTSBK.setLCO(TSBK_OSP_MOT_CC_BSI); + m_rfTSBK.setMFId(P25_MFG_MOT); + break; + } + + m_rfTSBK.setLastBlock(true); // always set last block + writeRF_TSDU_MBF(); +} + +/// +/// Helper to write a grant packet. +/// +/// +/// +/// +bool TrunkPacket::writeRF_TSDU_Grant(bool grp, bool skip) +{ + uint8_t lco = m_rfTSBK.getLCO(); + + if (m_rfTSBK.getDstId() == P25_TGID_ALL) { + return true; // do not generate grant packets for $FFFF (All Call) TGID + } + + // are we skipping checking? + if (!skip) { + if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_rfState != RS_RF_DATA) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request) denied, traffic in progress, dstId = %u", m_rfTSBK.getDstId()); + writeRF_TSDU_Deny(P25_DENY_RSN_PTT_COLLIDE, (grp) ? TSBK_IOSP_GRP_VCH : TSBK_IOSP_UU_VCH); + m_p25->checkAndReject(); + m_rfTSBK.setLCO(lco); + return false; + } + + if (m_p25->m_netState != RS_NET_IDLE && m_rfTSBK.getDstId() == m_p25->m_netLastDstId) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request) denied, traffic in progress, dstId = %u", m_rfTSBK.getDstId()); + writeRF_TSDU_Deny(P25_DENY_RSN_PTT_COLLIDE, (grp) ? TSBK_IOSP_GRP_VCH : TSBK_IOSP_UU_VCH); + m_p25->checkAndReject(); + m_rfTSBK.setLCO(lco); + return false; + } + + // don't transmit grants if the destination ID's don't match and the network TG hang timer is running + if (m_p25->m_rfLastDstId != 0U) { + if (m_p25->m_rfLastDstId != m_rfTSBK.getDstId() && (m_p25->m_networkTGHang.isRunning() && !m_p25->m_networkTGHang.hasExpired())) { + m_rfTSBK.setLCO(lco); + return false; + } + } + + if (!hasDstIdGranted(m_rfTSBK.getDstId())) { + if (m_voiceChTable.empty()) { + if (grp) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request) queued, no channels available, dstId = %u", m_rfTSBK.getDstId()); + writeRF_TSDU_Queue(P25_QUE_RSN_CHN_RESOURCE_NOT_AVAIL, TSBK_IOSP_GRP_VCH); + m_p25->checkAndReject(); + m_rfTSBK.setLCO(lco); + return false; + } + else { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Request) queued, no channels available, dstId = %u", m_rfTSBK.getDstId()); + writeRF_TSDU_Queue(P25_QUE_RSN_CHN_RESOURCE_NOT_AVAIL, TSBK_IOSP_UU_VCH); + m_p25->checkAndReject(); + m_rfTSBK.setLCO(lco); + return false; + } + } + else { + uint32_t chNo = m_voiceChTable.at(0); + auto it = std::find(m_voiceChTable.begin(), m_voiceChTable.end(), chNo); + m_voiceChTable.erase(it); + + m_grantChTable[m_rfTSBK.getDstId()] = chNo; + m_rfTSBK.setGrpVchNo(chNo); + + m_grantTimers[m_rfTSBK.getDstId()] = Timer(1000U, GRANT_TIMER_TIMEOUT); + m_grantTimers[m_rfTSBK.getDstId()].start(); + + m_voiceGrantChCnt++; + setSiteChCnt(m_voiceChCnt + m_voiceGrantChCnt); + } + } + else { + uint32_t chNo = m_grantChTable[m_rfTSBK.getDstId()]; + m_rfTSBK.setGrpVchNo(chNo); + + m_grantTimers[m_rfTSBK.getDstId()].start(); + } + } + + if (grp) { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + m_rfTSBK.getEmergency(), m_rfTSBK.getEncrypted(), m_rfTSBK.getPriority(), m_rfTSBK.getGrpVchNo(), m_rfTSBK.getSrcId(), m_rfTSBK.getDstId()); + } + + // transmit group grant + m_rfTSBK.setLCO(TSBK_IOSP_GRP_VCH); + writeRF_TSDU_SBF(true, true); + } + else { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_VCH (Unit-to-Unit Voice Channel Grant), emerg = %u, encrypt = %u, prio = %u, chNo = %u, srcId = %u, dstId = %u", + m_rfTSBK.getEmergency(), m_rfTSBK.getEncrypted(), m_rfTSBK.getPriority(), m_rfTSBK.getGrpVchNo(), m_rfTSBK.getSrcId(), m_rfTSBK.getDstId()); + } + + // transmit private grant + m_rfTSBK.setLCO(TSBK_IOSP_UU_VCH); + writeRF_TSDU_SBF(true, true); + } + + m_rfTSBK.setLCO(lco); + return true; +} + +/// +/// Helper to write a unit to unit answer request packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId) +{ + uint8_t lco = m_rfTSBK.getLCO(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_UU_ANS (Unit-to-Unit Answer Request), srcId = %u, dstId = %u", srcId, dstId); + } + + m_rfTSBK.setLCO(TSBK_IOSP_UU_ANS); + m_rfTSBK.setSrcId(srcId); + m_rfTSBK.setDstId(dstId); + m_rfTSBK.setVendorSkip(true); + writeRF_TSDU_SBF(true); + + m_rfTSBK.setLCO(lco); + m_rfTSBK.setVendorSkip(false); +} + +/// +/// Helper to write a acknowledge packet. +/// +/// +/// +/// +void TrunkPacket::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool noNetwork) +{ + uint8_t lco = m_rfTSBK.getLCO(); + uint8_t mfId = m_rfTSBK.getMFId(); + + m_rfTSBK.setLCO(TSBK_IOSP_ACK_RSP); + m_rfTSBK.setMFId(P25_MFG_STANDARD); + m_rfTSBK.setService(service); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_ACK_RSP (Acknowledge Response), AIV = %u, serviceType = $%02X, srcId = %u", + m_rfTSBK.getAIV(), m_rfTSBK.getService(), srcId); + } + + writeRF_TSDU_SBF(noNetwork); + + m_rfTSBK.setLCO(lco); + m_rfTSBK.setMFId(mfId); +} + +/// +/// Helper to write a deny packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Deny(uint8_t reason, uint8_t service) +{ + uint8_t lco = m_rfTSBK.getLCO(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_DENY_RSP (Deny Response), AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getAIV(), reason, m_rfTSBK.getSrcId(), m_rfTSBK.getDstId()); + } + + m_rfTSBK.setLCO(TSBK_OSP_DENY_RSP); + m_rfTSBK.setService(service); + m_rfTSBK.setResponse(reason); + writeRF_TSDU_SBF(true); + + m_rfTSBK.setLCO(lco); +} + +/// +/// Helper to write a group affiliation response packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) +{ + m_rfTSBK.setLCO(TSBK_IOSP_GRP_AFF); + m_rfTSBK.setResponse(P25_RSP_ACCEPT); + m_rfTSBK.setPatchSuperGroupId(m_patchSuperGroup); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, RID rejection, srcId = %u", srcId); + m_rfTSBK.setResponse(P25_RSP_DENY); + } + + // validate the source RID is registered + if (!hasSrcIdUnitReg(srcId) && m_verifyReg) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, RID not registered, srcId = %u", srcId); + m_rfTSBK.setResponse(P25_RSP_DENY); + } + + // validate the talkgroup ID + if (m_rfTSBK.getGroup()) { + if (!acl::AccessControl::validateTGId(dstId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response) denial, TGID rejection, dstId = %u", dstId); + m_rfTSBK.setResponse(P25_RSP_REFUSED); + } + } + + if (m_rfTSBK.getResponse() == P25_RSP_ACCEPT) { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_AFF (Group Affiliation Response), anncId = %u, srcId = %u, dstId = %u", + m_patchSuperGroup, srcId, dstId); + } + + ::ActivityLog("P25", true, "received group affiliation request from %u to %s %u", srcId, "TG ", dstId); + + // update dynamic affiliation table + m_grpAffTable[srcId] = dstId; + } + + writeRF_TSDU_SBF(false); +} + +/// +/// Helper to write a unit registration response packet. +/// +/// +void TrunkPacket::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId) +{ + m_rfTSBK.setLCO(TSBK_IOSP_U_REG); + m_rfTSBK.setResponse(P25_RSP_ACCEPT); + + // validate the system ID + if (m_rfTSBK.getSysId() != m_siteData.sysId()) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response) denial, SYSID rejection, sysId = $%03X", m_rfTSBK.getSysId()); + m_rfTSBK.setResponse(P25_RSP_DENY); + } + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response) denial, RID rejection, srcId = %u", srcId); + m_rfTSBK.setResponse(P25_RSP_DENY); + } + + if (m_rfTSBK.getResponse() == P25_RSP_ACCEPT) { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_U_REG (Unit Registration Response), srcId = %u, sysId = $%03X, netId = $%05X", srcId, + m_rfTSBK.getSysId(), m_rfTSBK.getNetId()); + } + + ::ActivityLog("P25", true, "received unit registration request from %u", srcId); + + // update dynamic unit registration table + if (!hasSrcIdUnitReg(srcId)) { + m_unitRegTable.push_back(srcId); + } + } + + // because Motorola -- we'll set both Source and Destination to the Source ID + // for the U_REG_RSP apparently should have the SUID set this way... + m_rfTSBK.setSrcId(srcId/*P25_WUID_REG*/); + m_rfTSBK.setDstId(srcId); + + writeRF_TSDU_SBF(true); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + denialInhibit(srcId); // inhibit source radio automatically + } +} + +/// +/// Helper to write a unit de-registration acknowledge packet. +/// +/// +void TrunkPacket::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) +{ + m_rfTSBK.setLCO(TSBK_OSP_U_DEREG_ACK); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_ISP_U_DEREG_REQ (Unit Deregistration Request) srcId = %u, sysId = $%03X, netId = $%05X", + srcId, m_rfTSBK.getSysId(), m_rfTSBK.getNetId()); + } + + ::ActivityLog("P25", true, "received unit deregistration request from %u", srcId); + + // remove dynamic unit registration table entry + if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { + auto it = std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId); + m_unitRegTable.erase(it); + } + + // remove dynamic affiliation table entry + try { + m_grpAffTable.at(srcId); + m_grpAffTable.erase(srcId); + } + catch (...) { + // stub + } + + m_rfTSBK.setSrcId(P25_WUID_SYS); + + writeRF_TSDU_SBF(false); +} + +/// +/// Helper to write a queue packet. +/// +/// +/// +void TrunkPacket::writeRF_TSDU_Queue(uint8_t reason, uint8_t service) +{ + uint8_t lco = m_rfTSBK.getLCO(); + + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_OSP_QUE_RSP (Queue Response), AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + m_rfTSBK.getAIV(), reason, m_rfTSBK.getSrcId(), m_rfTSBK.getDstId()); + } + + m_rfTSBK.setLCO(TSBK_OSP_QUE_RSP); + m_rfTSBK.setService(service); + m_rfTSBK.setResponse(reason); + writeRF_TSDU_SBF(true); + + m_rfTSBK.setLCO(lco); +} + +/// +/// Helper to write a network TSDU from the RF data queue. +/// +/// +void TrunkPacket::writeNet_TSDU_From_RF(uint8_t* data) +{ + ::memset(data, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data); + + // Generate NID + m_p25->m_nid.encode(data, P25_DUID_TSDU); + + // Regenerate TSDU Data + m_rfTSBK.setLastBlock(true); // always set last block -- this a Single Block TSDU + m_rfTSBK.encode(data, true); + + // Add busy bits + m_p25->addBusyBits(data, P25_TSDU_FRAME_LENGTH_BYTES, true, false); + + // Set first busy bits to 1,1 + m_p25->setBusyBits(data, P25_SS0_START, true, true); +} + +/// +/// Helper to write a network P25 TDU w/ link control packet. +/// +void TrunkPacket::writeNet_TDULC() +{ + uint8_t buffer[P25_TDULC_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_TDULC_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_EOT; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_TDULC); + + // Regenerate TDULC Data + m_netTDULC.encode(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_TDULC_FRAME_LENGTH_BITS, true, true); + + m_p25->writeQueueNet(buffer, P25_TDULC_FRAME_LENGTH_BYTES + 2U); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TDULC_STR ", lc = $%02X, srcId = %u", m_netTDULC.getLCO(), m_netTDULC.getSrcId()); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_TDULC", buffer + 2U, P25_TDULC_FRAME_LENGTH_BYTES); + } + + if (m_p25->m_voice->m_netFrames > 0) { + ::ActivityLog("P25", false, "network end of transmission, %.1f seconds, %u%% packet loss", + float(m_p25->m_voice->m_netFrames) / 50.0F, (m_p25->m_voice->m_netLost * 100U) / m_p25->m_voice->m_netFrames); + } + else { + ::ActivityLog("P25", false, "network end of transmission, %u frames", m_p25->m_voice->m_netFrames); + } + + if (m_network != NULL) + m_network->resetP25(); + + m_p25->m_netTimeout.stop(); + m_p25->m_networkWatchdog.stop(); + m_netTDULC.reset(); + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_tailOnIdle = true; +} + +/// +/// Helper to write a network single-block P25 TSDU packet. +/// +void TrunkPacket::writeNet_TSDU() +{ + uint8_t buffer[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_TSDU); + + // Regenerate TSDU Data + m_netTSBK.setLastBlock(true); // always set last block -- this a Single Block TSDU + m_netTSBK.encode(buffer + 2U, true); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES, true, false); + + // Set first busy bits to 1,1 + m_p25->setBusyBits(buffer + 2U, P25_SS0_START, true, true); + + m_p25->writeQueueNet(buffer, P25_TSDU_FRAME_LENGTH_BYTES + 2U); + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_TSDU", buffer + 2U, P25_TSDU_FRAME_LENGTH_BYTES); + } + + if (m_network != NULL) + m_network->resetP25(); +} + +/// +/// Helper to automatically inhibit a source ID on a denial. +/// +/// +void TrunkPacket::denialInhibit(uint32_t srcId) +{ + if (!m_p25->m_inhibitIllegal) { + return; + } + + // this check should have already been done -- but do it again anyway + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_P25, P25_TSDU_STR ", denial, system auto-inhibit RID, srcId = %u", srcId); + writeRF_TSDU_Ext_Func(P25_EXT_FNCT_INHIBIT, P25_WUID_SYS, srcId); + } +} + +/// +/// +/// +/// +void TrunkPacket::resetStatusCommand(const lc::TSBK& tsbk) +{ + // reset status control data if the status current mode is set and the LCO isn't CALL ALERT + if (m_statusCmdEnable && ((m_rfTSBK.getLCO() != TSBK_IOSP_CALL_ALRT) && (m_rfTSBK.getLCO() != TSBK_IOSP_EXT_FNCT))) { + resetStatusCommand(); + } +} + +/// +/// +/// +void TrunkPacket::preprocessStatusCommand() +{ + if (m_statusCmdEnable) { + m_statusSrcId = m_rfTSBK.getSrcId(); + m_statusValue = m_rfTSBK.getStatus(); + + if (m_statusValue != 0U) { + if ((m_statusValue == m_statusRadioCheck) || + (m_statusValue == m_statusRadioInhibit) || (m_statusValue == m_statusRadioUninhibit) || + (m_statusValue == m_statusRadioForceReg) || (m_statusValue == m_statusRadioForceDereg)) { + if (m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), command mode, statusCurrentStatus = $%02X", m_statusValue); + } + } + else { + resetStatusCommand(); + } + } + } +} + +/// +/// +/// +/// +/// +/// +bool TrunkPacket::processStatusCommand(uint32_t srcId, uint32_t dstId) +{ + // is status command mode enabled with status data? + if (m_statusCmdEnable && (m_statusValue != 0U)) { + // if the status srcId isn't the CALL ALERT srcId ignore + if (m_statusSrcId == srcId) { + if ((m_statusRadioCheck != 0U) && (m_statusValue == m_statusRadioCheck)) { + writeRF_TSDU_Ext_Func(P25_EXT_FNCT_CHECK, srcId, dstId); + } + else if ((m_statusRadioInhibit != 0U) && (m_statusValue == m_statusRadioInhibit)) { + writeRF_TSDU_Ext_Func(P25_EXT_FNCT_INHIBIT, P25_WUID_SYS, dstId); + } + else if ((m_statusRadioUninhibit != 0U) && (m_statusValue == m_statusRadioUninhibit)) { + writeRF_TSDU_Ext_Func(P25_EXT_FNCT_UNINHIBIT, P25_WUID_SYS, dstId); + } + else if ((m_statusRadioForceReg != 0U) && (m_statusValue == m_statusRadioForceReg)) { + // update dynamic unit registration table + if (!hasSrcIdUnitReg(srcId)) { + m_unitRegTable.push_back(srcId); + } + + writeRF_TSDU_Grp_Aff_Rsp(srcId, dstId); + } + else if ((m_statusRadioForceDereg != 0U) && (m_statusValue == m_statusRadioForceDereg)) { + writeRF_TSDU_U_Dereg_Ack(srcId); + } + else { + LogError(LOG_P25, P25_TSDU_STR ", unhandled command mode, statusCurrentStatus = $%02X, srcId = %u, dstId = %u", m_statusValue, srcId, dstId); + resetStatusCommand(); + } + + writeRF_TSDU_ACK_FNE(srcId, TSBK_IOSP_CALL_ALRT, false); + return true; + } + else { + if (m_verbose) { + LogWarning(LOG_P25, P25_TSDU_STR ", TSBK_IOSP_STS_UPDT (Status Update), illegal attempt by srcId = %u to access status command", srcId); + } + } + } + + resetStatusCommand(); + return false; +} + +/// +/// Helper to add the idle status bits on P25 frame data. +/// +/// +/// +/// +/// +void TrunkPacket::addIdleBits(uint8_t* data, uint32_t length, bool b1, bool b2) +{ + assert(data != NULL); + + for (uint32_t ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += (P25_SS_INCREMENT * 5U)) { + uint32_t ss1Pos = ss0Pos + 1U; + WRITE_BIT(data, ss0Pos, b1); + WRITE_BIT(data, ss1Pos, b2); + } +} diff --git a/p25/TrunkPacket.h b/p25/TrunkPacket.h new file mode 100644 index 00000000..ddd511e4 --- /dev/null +++ b/p25/TrunkPacket.h @@ -0,0 +1,251 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_TRUNK_PACKET_H__) +#define __P25_TRUNK_PACKET_H__ + +#include "Defines.h" +#include "p25/lc/TSBK.h" +#include "p25/lc/TDULC.h" +#include "p25/Control.h" +#include "p25/SiteData.h" +#include "network/BaseNetwork.h" +#include "network/RemoteControl.h" +#include "Timer.h" + +#include +#include +#include +#include + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API VoicePacket; + class HOST_SW_API DataPacket; + class HOST_SW_API Control; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for P25 trunking packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API TrunkPacket { + public: + /// Sets local configured site data. + void setSiteData(uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t lra, + uint8_t channelId, uint32_t channelNo); + /// Sets local configured site callsign. + void setCallsign(std::string callsign); + /// Sets a flag indicating whether or not networking is active. + void setNetActive(bool active); + /// Sets the total number of channels at the site. + void setSiteChCnt(uint8_t chCnt); + + /// Resets the data states for the RF interface. + void resetRF(); + /// Resets the data states for the network. + void resetNet(); + /// Sets the RF TSBK and TDULC data to match the given LC data. + void setRFLC(const lc::LC& lc); + /// Sets the network TSBK and TDULC data to match the given LC data. + void setNetLC(const lc::LC& lc); + + /// Process a data frame from the RF interface. + bool process(uint8_t* data, uint32_t len); + /// Process a data frame from the network. + bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + + /// Helper to write P25 adjacent site information to the network. + void writeAdjSSNetwork(); + + /// Helper to determine if the source ID has affiliated to the group destination ID. + bool hasSrcIdGrpAff(uint32_t srcId, uint32_t dstId) const; + /// Helper to determine if the source ID has unit registered. + bool hasSrcIdUnitReg(uint32_t srcId) const; + + /// Helper to determine if the channel number is busy. + bool isChBusy(uint32_t chNo) const; + /// Helper to determine if the destination ID is already granted. + bool hasDstIdGranted(uint32_t dstId) const; + /// Helper to start the destination ID grant timer. + void touchDstIdGrant(uint32_t dstId); + /// Helper to release the channel grant for the destination ID. + void releaseDstIdGrant(uint32_t dstId, bool releaseAll); + + /// + void resetStatusCommand(); + + /// Updates the processor by the passed number of milliseconds. + void clock(uint32_t ms); + + /// + void setMFId(uint8_t val) { m_rfTSBK.setMFId(val); } + /// Helper to write a call alert packet. + void writeRF_TSDU_Call_Alrt(uint32_t srcId, uint32_t dstId); + /// Helper to write a extended function packet. + void writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId); + /// Helper to write a group affiliation query packet. + void writeRF_TSDU_Grp_Aff_Q(uint32_t dstId); + /// Helper to write a unit registration command packet. + void writeRF_TSDU_U_Reg_Cmd(uint32_t dstId); + + /// Helper to write a Motorola patch packet. + void writeRF_TSDU_Mot_Patch(uint32_t group1, uint32_t group2, uint32_t group3); + + private: + friend class VoicePacket; + friend class DataPacket; + friend class Control; + Control* m_p25; + + network::BaseNetwork* m_network; + + uint32_t m_patchSuperGroup; + + bool m_verifyAff; + bool m_verifyReg; + + lc::TSBK m_rfTSBK; + lc::TSBK m_netTSBK; + uint8_t* m_rfMBF; + uint8_t m_mbfCnt; + + uint8_t m_mbfIdenCnt; + uint8_t m_mbfAdjSSCnt; + + lc::TDULC m_rfTDULC; + lc::TDULC m_netTDULC; + + std::vector m_voiceChTable; + + std::unordered_map m_adjSiteTable; + std::unordered_map m_adjSiteUpdateCnt; + + std::vector m_unitRegTable; + std::unordered_map m_grpAffTable; + + std::unordered_map m_grantChTable; + std::unordered_map m_grantTimers; + + uint8_t m_voiceChCnt; + uint8_t m_voiceGrantChCnt; + + bool m_noStatusAck; + bool m_noMessageAck; + + bool m_statusCmdEnable; + uint8_t m_statusRadioCheck; + uint8_t m_statusRadioInhibit; + uint8_t m_statusRadioUninhibit; + uint8_t m_statusRadioForceReg; + uint8_t m_statusRadioForceDereg; + + uint32_t m_statusSrcId; + uint8_t m_statusValue; + + SiteData m_siteData; + + Timer m_adjSiteUpdateTimer; + uint32_t m_adjSiteUpdateInterval; + + bool m_skipSBFPreamble; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the TrunkPacket class. + TrunkPacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose); + /// Finalizes a instance of the TrunkPacket class. + ~TrunkPacket(); + + /// Write data processed from RF to the network. + void writeNetworkRF(const uint8_t* data, bool autoReset); + + /// Helper to write control channel packet data. + void writeRF_ControlData(uint8_t frameCnt, bool adjSS); + + /// Helper to write a P25 TDU w/ link control packet. + void writeRF_TDULC(uint8_t duid, bool noNetwork); + /// Helper to write a P25 TDU w/ link control channel grant packet. + void writeRF_TDULC_ChanGrant(bool grp, uint32_t srcId, uint32_t dstId); + /// Helper to write a P25 TDU w/ link control channel release packet. + void writeRF_TDULC_ChanRelease(bool grp, uint32_t srcId, uint32_t dstId); + + /// Helper to write a single-block P25 TSDU packet. + void writeRF_TSDU_SBF(bool noNetwork, bool clearBeforeWrite = false); + /// Helper to write a multi-block P25 TSDU packet. + void writeRF_TSDU_MBF(bool clearBeforeWrite = false); + + /// Helper to queue the given control TSBK into the MBF queue. + void queueRF_TSBK_Ctrl_MBF(uint8_t lco); + + /// Helper to write a grant packet. + bool writeRF_TSDU_Grant(bool grp, bool skip); + /// Helper to write a unit to unit answer request packet. + void writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId); + /// Helper to write a acknowledge packet. + void writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool noNetwork); + /// Helper to write a deny packet. + void writeRF_TSDU_Deny(uint8_t reason, uint8_t service); + /// Helper to write a group affiliation response packet. + void writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId); + /// Helper to write a unit registration response packet. + void writeRF_TSDU_U_Reg_Rsp(uint32_t srcId); + /// Helper to write a unit de-registration acknowledge packet. + void writeRF_TSDU_U_Dereg_Ack(uint32_t srcId); + /// Helper to write a queue packet. + void writeRF_TSDU_Queue(uint8_t reason, uint8_t service); + + /// Helper to write a network TSDU from the RF data queue. + void writeNet_TSDU_From_RF(uint8_t* data); + + /// Helper to write a network P25 TDU w/ link control packet. + void writeNet_TDULC(); + /// Helper to write a network single-block P25 TSDU packet. + void writeNet_TSDU(); + + /// Helper to automatically inhibit a source ID on a denial. + void denialInhibit(uint32_t srcId); + + /// + void resetStatusCommand(const lc::TSBK& tsbk); + /// + void preprocessStatusCommand(); + /// + bool processStatusCommand(uint32_t srcId, uint32_t dstId); + + /// Helper to add the idle status bits on P25 frame data. + void addIdleBits(uint8_t* data, uint32_t length, bool b1, bool b2); + }; +} // namespace p25 + +#endif // __P25_TRUNK_PACKET_H__ diff --git a/p25/VoicePacket.cpp b/p25/VoicePacket.cpp new file mode 100644 index 00000000..1bbde575 --- /dev/null +++ b/p25/VoicePacket.cpp @@ -0,0 +1,1440 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/VoicePacket.h" +#include "p25/acl/AccessControl.h" +#include "p25/P25Utils.h" +#include "p25/Sync.h" +#include "edac/CRC.h" +#include "HostMain.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25; + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Resets the data states for the RF interface. +/// +void VoicePacket::resetRF() +{ + m_rfLC.reset(); + m_rfLastLDU1.reset(); + m_rfLastLDU2.reset(); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfUndecodableLC = 0U; +} + +/// +/// Resets the data states for the network. +/// +void VoicePacket::resetNet() +{ + m_netLC.reset(); + m_netLastLDU1.reset(); + m_netFrames = 0U; + m_netLost = 0U; +} + +/// +/// Process a data frame from the RF interface. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +bool VoicePacket::process(uint8_t* data, uint32_t len) +{ + assert(data != NULL); + + // Decode the NID + bool valid = m_p25->m_nid.decode(data + 2U); + + if (m_p25->m_rfState == RS_RF_LISTENING && !valid) + return false; + + uint8_t duid = m_p25->m_nid.getDUID(); + if (!valid) { + switch (m_lastDUID) { + case P25_DUID_HDU: + case P25_DUID_LDU2: + duid = P25_DUID_LDU1; + break; + case P25_DUID_LDU1: + duid = P25_DUID_LDU2; + break; + default: + break; + } + } + + // are we interrupting a running CC? + if (m_p25->m_ccRunning) { + g_interruptP25Control = true; + } + + if (m_p25->m_rfState != RS_RF_LISTENING) + m_p25->m_networkTGHang.start(); + + // handle individual DUIDs + if (duid == P25_DUID_HDU) { + m_p25->m_trunk->resetStatusCommand(); + + m_lastDUID = P25_DUID_HDU; + + if (m_p25->m_rfState == RS_RF_LISTENING) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + resetRF(); + resetNet(); + + m_p25->writeRF_Preamble(); + + if (!m_p25->m_ccRunning) { + m_p25->m_trunk->writeRF_ControlData(127U, false); + } + } + + if (m_p25->m_rfState == RS_RF_LISTENING || m_p25->m_rfState == RS_RF_AUDIO) { + resetRF(); + resetNet(); + + bool ret = m_rfLC.decodeHDU(data + 2U); + if (!ret) { + LogWarning(LOG_RF, P25_HDU_STR ", undecodable LC"); + m_rfUndecodableLC++; + return false; + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_HDU_STR ", HDU_BSDWNACT, dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + } + + m_rfLastHDU.reset(); + m_rfLastHDU = m_rfLC; + } + + return true; + } + else if (duid == P25_DUID_LDU1) { + m_p25->m_trunk->resetStatusCommand(); + + bool alreadyDecoded = false; + m_lastDUID = P25_DUID_LDU1; + + if (m_p25->m_rfState == RS_RF_LISTENING) { + bool ret = m_rfLC.decodeLDU1(data + 2U); + if (!ret) { + return false; + } + + m_rfLastLDU1 = m_rfLC; + alreadyDecoded = true; + + uint32_t srcId = m_rfLC.getSrcId(); + uint32_t dstId = m_rfLC.getDstId(); + + // don't process RF frames if the network isn't in a idle state + if (m_p25->m_netState != RS_NET_IDLE && dstId == m_p25->m_netLastDstId) { + LogWarning(LOG_RF, "Traffic collision detect, preempting new RF traffic to existing network traffic!"); + resetRF(); + return false; + } + + m_p25->m_trunk->setRFLC(m_rfLC); + + // validate the source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID rejection, srcId = %u", srcId); + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_VALID, (m_rfLC.getGroup() ? TSBK_IOSP_GRP_VCH : TSBK_IOSP_UU_VCH)); + m_p25->m_trunk->denialInhibit(srcId); + m_p25->checkAndReject(); + return false; + } + + // is this a group or individual operation? + if (!m_rfLC.getGroup()) { + // validate the target RID + if (!acl::AccessControl::validateSrcId(dstId)) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID rejection, dstId = %u", dstId); + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_TGT_UNIT_NOT_VALID, TSBK_IOSP_UU_VCH); + m_p25->checkAndReject(); + return false; + } + } + else { + // validate the target ID, if the target is a talkgroup + if (!acl::AccessControl::validateTGId(dstId)) { + LogWarning(LOG_RF, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_TGT_GROUP_NOT_VALID, TSBK_IOSP_GRP_VCH); + m_p25->checkAndReject(); + return false; + } + } + + // verify the source RID is affiliated to the group TGID; only if control data + // is supported + if (m_rfLC.getGroup() && m_p25->m_control) { + if (!m_p25->m_trunk->hasSrcIdGrpAff(srcId, dstId) && + m_p25->m_trunk->m_verifyAff) { + LogWarning(LOG_RF, P25_HDU_STR " denial, RID not affiliated to TGID, srcId = %u, dstId = %u", srcId, dstId); + m_p25->m_trunk->writeRF_TSDU_Deny(P25_DENY_RSN_REQ_UNIT_NOT_AUTH, TSBK_IOSP_GRP_VCH); + m_p25->m_trunk->writeRF_TSDU_U_Reg_Cmd(srcId); + m_p25->checkAndReject(); + return false; + } + } + + ::ActivityLog("P25", true, "received RF voice transmission from %u to %s%u", srcId, m_rfLC.getGroup() ? "TG " : "", dstId); + + if (m_p25->m_control) { + if (m_rfLC.getGroup() && (m_lastPatchGroup != dstId) && + (dstId != m_p25->m_trunk->m_patchSuperGroup)) { + m_p25->m_trunk->writeRF_TSDU_Mot_Patch(dstId, 0U, 0U); + m_lastPatchGroup = dstId; + } + + // if the group wasn't granted out -- explicitly grant the group + if (!m_p25->m_trunk->hasDstIdGranted(dstId)) { + if (m_p25->m_legacyGroupGrnt) { + m_p25->m_trunk->m_skipSBFPreamble = true; // HACK: force an SBF to skip generating preambles + if (!m_p25->m_trunk->writeRF_TSDU_Grant(m_rfLC.getGroup(), false)) { + return false; + } + } + else { + return false; + } + } + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + m_p25->m_ccRunning = false; // otherwise the grant will be bundled with other packets + m_p25->m_trunk->writeRF_TSDU_Grant(m_rfLC.getGroup(), true); + } + + m_p25->m_trunk->writeRF_TDULC_ChanGrant(m_rfLC.getGroup(), srcId, dstId); + + // perform lost/corrupt HDU checking + if (m_rfLastHDU.getAlgId() != P25_ALGO_UNENCRYPT && m_rfLastHDU.getKId() != 0) { + if ((m_rfLC.getAlgId() == P25_ALGO_UNENCRYPT && m_rfLC.getAlgId() != m_rfLastHDU.getAlgId()) && + (m_rfLC.getKId() == 0 && m_rfLC.getKId() != m_rfLastHDU.getKId())) { + LogWarning(LOG_RF, P25_HDU_STR ", lost or changed data, using last HDU LC"); + LogWarning(LOG_RF, P25_HDU_STR ", algo = $%02X, kid = $%04X doesn't match last HDU algo = $%02X, kid = $%04X, fixing", + m_rfLC.getAlgId(), m_rfLC.getKId(), m_rfLastHDU.getAlgId(), m_rfLastHDU.getKId()); + m_rfLC.setAlgId(m_rfLastHDU.getAlgId()); + m_rfLC.setKId(m_rfLastHDU.getKId()); + + uint8_t mi[P25_MI_LENGTH_BYTES]; + m_rfLastHDU.getMI(mi); + m_rfLC.setMI(mi); + + m_rfLastHDU.reset(); + } + } + + m_p25->m_rfState = RS_RF_AUDIO; + m_p25->m_rfLastDstId = dstId; + + uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_HDU); + + // Generate HDU + m_rfLC.encodeHDU(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false, true); + + m_rfFrames = 0U; + m_rfErrs = 0U; + m_rfBits = 1U; + m_rfUndecodableLC = 0U; + m_p25->m_rfTimeout.start(); + m_lastDUID = P25_DUID_HDU; + + writeNetworkRF(buffer, P25_DUID_HDU); + + if (m_p25->m_duplex) { + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + m_p25->writeQueueRF(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_rfLC.getDstId(), m_rfLC.getAlgId(), m_rfLC.getKId()); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_HDU", buffer + 2U, P25_HDU_FRAME_LENGTH_BYTES); + } + } + + if (m_p25->m_rfState == RS_RF_AUDIO) { + if (!alreadyDecoded) { + bool ret = m_rfLC.decodeLDU1(data + 2U); + if (!ret) { + LogWarning(LOG_RF, P25_LDU1_STR ", undecodable LC, using last LDU1 LC"); + m_rfLC = m_rfLastLDU1; + m_rfUndecodableLC++; + } + else { + m_rfLastLDU1 = m_rfLC; + } + } + + alreadyDecoded = false; + + if (m_p25->m_control) { + m_p25->m_trunk->touchDstIdGrant(m_rfLC.getDstId()); + } + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_LDU1); + + // Generate LDU1 Data + m_rfLC.encodeLDU1(data + 2U); + + // Generate Low Speed Data + m_rfLSD.process(data + 2U); + + // Regenerate Audio + uint32_t errors = m_audio.process(data + 2U); + + // replace audio with silence in cases where the error rate + // has exceeded the configured threshold + if (errors > m_silenceThreshold) { + // generate null audio + uint8_t buffer[9U * 25U]; + ::memset(buffer, 0x00U, 9U * 25U); + + insertNullAudio(buffer); + + LogWarning(LOG_RF, P25_LDU1_STR ", exceeded lost audio threshold, filling in"); + + // Add the Audio + m_audio.encode(data + 2U, buffer + 10U, 0U); + m_audio.encode(data + 2U, buffer + 26U, 1U); + m_audio.encode(data + 2U, buffer + 55U, 2U); + m_audio.encode(data + 2U, buffer + 80U, 3U); + m_audio.encode(data + 2U, buffer + 105U, 4U); + m_audio.encode(data + 2U, buffer + 130U, 5U); + m_audio.encode(data + 2U, buffer + 155U, 6U); + m_audio.encode(data + 2U, buffer + 180U, 7U); + m_audio.encode(data + 2U, buffer + 204U, 8U); + } + + m_rfBits += 1233U; + m_rfErrs += errors; + m_rfFrames++; + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + writeNetworkRF(data + 2U, P25_DUID_LDU1); + + if (m_p25->m_duplex) { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + m_p25->writeQueueRF(data, P25_LDU_FRAME_LENGTH_BYTES + 2U); + } + + if (m_verbose) { + LogMessage(LOG_RF, P25_LDU1_STR ", audio, srcId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, errs = %u/1233 (%.1f%%)", + m_rfLC.getSrcId(), m_rfLC.getGroup(), m_rfLC.getEmergency(), m_rfLC.getEncrypted(), m_rfLC.getPriority(), errors, float(errors) / 12.33F); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_LDU1", data + 2U, P25_LDU_FRAME_LENGTH_BYTES); + } + + return true; + } + } + else if (duid == P25_DUID_LDU2) { + m_p25->m_trunk->resetStatusCommand(); + + m_lastDUID = P25_DUID_LDU2; + + if (m_p25->m_rfState == RS_RF_LISTENING) { + return false; + } + else if (m_p25->m_rfState == RS_RF_AUDIO) { + bool ret = m_rfLC.decodeLDU2(data + 2U); + if (!ret) { + LogWarning(LOG_RF, P25_LDU2_STR ", undecodable LC, using last LDU2 LC"); + m_rfLC = m_rfLastLDU2; + + // ensure our srcId and dstId are sane from the last LDU1 + if (m_rfLastLDU1.getDstId() != 0U) { + if (m_rfLC.getDstId() != m_rfLastLDU1.getDstId()) { + LogWarning(LOG_RF, P25_LDU2_STR ", dstId = %u doesn't match last LDU1 dstId = %u, fixing", + m_rfLC.getDstId(), m_rfLastLDU1.getDstId()); + m_rfLC.setDstId(m_rfLastLDU1.getDstId()); + } + } + else { + LogWarning(LOG_RF, P25_LDU2_STR ", last LDU1 LC has bad data, dstId = 0"); + } + + if (m_rfLastLDU1.getSrcId() != 0U) { + if (m_rfLC.getSrcId() != m_rfLastLDU1.getSrcId()) { + LogWarning(LOG_RF, P25_LDU2_STR ", srcId = %u doesn't match last LDU1 srcId = %u, fixing", + m_rfLC.getSrcId(), m_rfLastLDU1.getSrcId()); + m_rfLC.setSrcId(m_rfLastLDU1.getSrcId()); + } + } + else { + LogWarning(LOG_RF, P25_LDU2_STR ", last LDU1 LC has bad data, srcId = 0"); + } + + m_rfUndecodableLC++; + } + else { + m_rfLastLDU2 = m_rfLC; + } + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate NID + m_p25->m_nid.encode(data + 2U, P25_DUID_LDU2); + + // Generate LDU2 data + m_rfLC.encodeLDU2(data + 2U); + + // Generate Low Speed Data + m_rfLSD.process(data + 2U); + + // Regenerate Audio + uint32_t errors = m_audio.process(data + 2U); + + // replace audio with silence in cases where the error rate + // has exceeded the configured threshold + if (errors > m_silenceThreshold) { + // generate null audio + uint8_t buffer[9U * 25U]; + ::memset(buffer, 0x00U, 9U * 25U); + + insertNullAudio(buffer); + + LogWarning(LOG_RF, P25_LDU2_STR ", exceeded lost audio threshold, filling in"); + + // Add the Audio + m_audio.encode(data + 2U, buffer + 10U, 0U); + m_audio.encode(data + 2U, buffer + 26U, 1U); + m_audio.encode(data + 2U, buffer + 55U, 2U); + m_audio.encode(data + 2U, buffer + 80U, 3U); + m_audio.encode(data + 2U, buffer + 105U, 4U); + m_audio.encode(data + 2U, buffer + 130U, 5U); + m_audio.encode(data + 2U, buffer + 155U, 6U); + m_audio.encode(data + 2U, buffer + 180U, 7U); + m_audio.encode(data + 2U, buffer + 204U, 8U); + } + + m_rfBits += 1233U; + m_rfErrs += errors; + m_rfFrames++; + + // Add busy bits + m_p25->addBusyBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + writeNetworkRF(data + 2U, P25_DUID_LDU2); + + if (m_p25->m_duplex) { + data[0U] = TAG_DATA; + data[1U] = 0x00U; + m_p25->writeQueueRF(data, P25_LDU_FRAME_LENGTH_BYTES + 2U); + } + + if (m_verbose) { + LogMessage(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_debug) { + Utils::dump(2U, "!!! *TX P25 Frame - P25_DUID_LDU2", data + 2U, P25_LDU_FRAME_LENGTH_BYTES); + } + + return true; + } + } + else if (duid == P25_DUID_TDU || duid == P25_DUID_TDULC) { + m_p25->m_trunk->resetStatusCommand(); + + if (m_p25->m_control) { + m_p25->m_trunk->releaseDstIdGrant(m_rfLC.getDstId(), false); + } + + if (duid == P25_DUID_TDU) { + m_p25->writeRF_TDU(false); + + m_lastDUID = duid; + + m_p25->m_rfTimeout.stop(); + } + else + m_p25->m_trunk->writeRF_TDULC(duid, false); + + if (m_p25->m_rfState == RS_RF_AUDIO) { + if (m_p25->m_rssi != 0U) { + ::ActivityLog("P25", true, "received RF end of transmission, %.1f seconds, BER: %.1f%%, RSSI : -%u / -%u / -%u dBm", + float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits), m_p25->m_minRSSI, m_p25->m_maxRSSI, + m_p25->m_aveRSSI / m_p25->m_rssiCount); + } + else { + ::ActivityLog("P25", true, "received RF end of transmission, %.1f seconds, BER: %.1f%%", + 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%%", + m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); + + if (m_p25->m_continuousControl) { + m_p25->m_tailOnIdle = false; + writeRF_EndOfVoice(); + } + else { + m_p25->m_tailOnIdle = true; + } + } + + m_p25->m_rfState = RS_RF_LISTENING; + return true; + } + else { + LogError(LOG_RF, "P25 unhandled voice DUID, duid = $%02X", duid); + } + + return false; +} + +/// +/// Process a data frame from the network. +/// +/// Buffer containing data frame. +/// Length of data frame. +/// +/// +/// +/// +bool VoicePacket::processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid) +{ + // don't process network frames if the RF modem isn't in a listening state + if (m_p25->m_rfState != RS_RF_LISTENING) { + LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic!"); + resetNet(); + return false; + } + + uint32_t count = 0U; + + switch (duid) { + case P25_DUID_LDU1: + // The '62', '63', '64', '65', '66', '67', '68', '69', '6A' records are LDU1 + if ((data[0U] == 0x62U) && (data[22U] == 0x63U) && + (data[36U] == 0x64U) && (data[53U] == 0x65U) && + (data[70U] == 0x66U) && (data[87U] == 0x67U) && + (data[104U] == 0x68U) && (data[121U] == 0x69U) && + (data[138U] == 0x6AU)) { + // The '62' record + ::memcpy(m_netLDU1 + 0U, data + count, 22U); + count += 22U; + + // The '63' record + ::memcpy(m_netLDU1 + 25U, data + count, 14U); + count += 14U; + + // The '64' record + ::memcpy(m_netLDU1 + 50U, data + count, 17U); + count += 17U; + + // The '65' record + ::memcpy(m_netLDU1 + 75U, data + count, 17U); + count += 17U; + + // The '66' record + ::memcpy(m_netLDU1 + 100U, data + count, 17U); + count += 17U; + + // The '67' record + ::memcpy(m_netLDU1 + 125U, data + count, 17U); + count += 17U; + + // The '68' record + ::memcpy(m_netLDU1 + 150U, data + count, 17U); + count += 17U; + + // The '69' record + ::memcpy(m_netLDU1 + 175U, data + count, 17U); + count += 17U; + + // The '6A' record + ::memcpy(m_netLDU1 + 200U, data + count, 16U); + count += 17U; + + m_p25->m_trunk->resetStatusCommand(); + + m_netLastLDU1 = control; + + checkNet_LDU2(control, lsd); + if (m_p25->m_netState != RS_NET_IDLE) { + writeNet_LDU1(control, lsd); + } + } + break; + case P25_DUID_LDU2: + // The '6B', '6C', '6D', '6E', '6F', '70', '71', '72', '73' records are LDU2 + if ((data[0U] == 0x6BU) && (data[22U] == 0x6CU) && + (data[36U] == 0x6DU) && (data[53U] == 0x6EU) && + (data[70U] == 0x6FU) && (data[87U] == 0x70U) && + (data[104U] == 0x71U) && (data[121U] == 0x72U) && + (data[138U] == 0x73U)) { + // The '6B' record + ::memcpy(m_netLDU2 + 0U, data + count, 22U); + count += 22U; + + // The '6C' record + ::memcpy(m_netLDU2 + 25U, data + count, 14U); + count += 14U; + + // The '6D' record + ::memcpy(m_netLDU2 + 50U, data + count, 17U); + count += 17U; + + // The '6E' record + ::memcpy(m_netLDU2 + 75U, data + count, 17U); + count += 17U; + + // The '6F' record + ::memcpy(m_netLDU2 + 100U, data + count, 17U); + count += 17U; + + // The '70' record + ::memcpy(m_netLDU2 + 125U, data + count, 17U); + count += 17U; + + // The '71' record + ::memcpy(m_netLDU2 + 150U, data + count, 17U); + count += 17U; + + // The '72' record + ::memcpy(m_netLDU2 + 175U, data + count, 17U); + count += 17U; + + // The '73' record + ::memcpy(m_netLDU2 + 200U, data + count, 16U); + count += 17U; + + m_p25->m_trunk->resetStatusCommand(); + + if (m_p25->m_netState == RS_NET_IDLE) { + m_p25->m_modem->clearP25Data(); + m_p25->m_queue.clear(); + + resetRF(); + m_rfLastHDU.reset(); + + m_netLC.reset(); + m_netFrames = 0U; + m_netLost = 0U; + + m_p25->m_trunk->resetRF(); + m_p25->m_trunk->resetNet(); + + if (!m_p25->m_ccRunning) { + m_p25->m_trunk->writeRF_ControlData(127U, false); + } + + writeNet_HDU(control, lsd); + if (m_p25->m_netState != RS_NET_IDLE) { + writeNet_LDU1(control, lsd); + } + } + else { + checkNet_LDU1(control, lsd); + } + + if (m_p25->m_netState != RS_NET_IDLE) { + writeNet_LDU2(control, lsd); + } + } + break; + case P25_DUID_TDU: + case P25_DUID_TDULC: + if (m_p25->m_control) { + m_p25->m_trunk->releaseDstIdGrant(m_netLC.getDstId(), false); + } + + if (m_p25->m_netState != RS_NET_IDLE) { + m_p25->m_trunk->resetStatusCommand(); + + if (duid == P25_DUID_TDU) + writeNet_TDU(); + else + m_p25->m_trunk->writeNet_TDULC(); + } + break; + } + + return true; +} + +/// +/// Helper to write end of frame data. +/// +/// +bool VoicePacket::writeEndRF() +{ + if (m_p25->m_netState == RS_NET_IDLE && m_p25->m_rfState == RS_RF_LISTENING) { + writeRF_EndOfVoice(); + + if (!m_p25->m_ccRunning) { + m_p25->m_trunk->writeRF_ControlData(127U, false); + m_p25->writeControlEndRF(); + } + + m_p25->m_tailOnIdle = false; + + if (m_network != NULL) + m_network->resetP25(); + + return true; + } + + return false; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the VoicePacket class. +/// +/// Instance of the Control class. +/// Instance of the BaseNetwork class. +/// Flag indicating whether P25 debug is enabled. +/// Flag indicating whether P25 verbose logging is enabled. +VoicePacket::VoicePacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose) : + m_p25(p25), + m_network(network), + m_rfFrames(0U), + m_rfBits(0U), + m_rfErrs(0U), + m_rfUndecodableLC(0U), + m_netFrames(0U), + m_netLost(0U), + m_audio(), + m_rfLC(), + m_rfLastHDU(), + m_rfLastLDU1(), + m_rfLastLDU2(), + m_netLC(), + m_netLastLDU1(), + m_rfLSD(), + m_netLSD(), + m_netLDU1(NULL), + m_netLDU2(NULL), + m_lastDUID(P25_DUID_TDU), + m_lastIMBE(NULL), + m_lastPatchGroup(0U), + m_silenceThreshold(124U), + m_verbose(verbose), + m_debug(debug) +{ + m_netLDU1 = new uint8_t[9U * 25U]; + m_netLDU2 = new uint8_t[9U * 25U]; + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_lastIMBE = new uint8_t[11U]; + ::memcpy(m_lastIMBE, P25_NULL_IMBE, 11U); +} + +/// +/// Finalizes a instance of the VoicePacket class. +/// +VoicePacket::~VoicePacket() +{ + delete[] m_netLDU1; + delete[] m_netLDU2; + delete[] m_lastIMBE; +} + +/// +/// Write data processed from RF to the network. +/// +/// +/// +void VoicePacket::writeNetworkRF(const uint8_t *data, uint8_t duid) +{ + assert(data != NULL); + + if (m_network == NULL) + return; + + if (m_p25->m_rfTimeout.isRunning() && m_p25->m_rfTimeout.hasExpired()) + return; + + switch (duid) { + case P25_DUID_HDU: + // ignore HDU + break; + case P25_DUID_LDU1: + m_network->writeP25LDU1(m_rfLC, m_rfLSD, data); + break; + case P25_DUID_LDU2: + m_network->writeP25LDU2(m_rfLC, m_rfLSD, data); + break; + case P25_DUID_TDU: + case P25_DUID_TDULC: + m_network->writeP25TDU(m_rfLC, m_rfLSD); + break; + default: + LogError(LOG_NET, "P25 unhandled voice DUID, duid = $%02X", duid); + break; + } +} + +/// +/// Helper to write end of frame data. +/// +/// +void VoicePacket::writeRF_EndOfVoice() +{ + bool grp = m_rfLC.getGroup(); + uint32_t srcId = m_rfLC.getSrcId(); + uint32_t dstId = m_rfLC.getDstId(); + + resetRF(); + m_rfLastHDU.reset(); + + resetNet(); + + // transmit channelNo release burst + m_p25->m_trunk->writeRF_TDULC_ChanRelease(grp, srcId, dstId); +/* + if (grp && (m_lastPatchGroup != 0U)) { + m_p25->m_trunk->writeRF_TSDU_Mot_Patch(m_p25->m_trunk->m_patchSuperGroup, 0U, 0U); + } +*/ +} + +/// +/// Helper to write a network P25 HDU packet. +/// +/// +/// +void VoicePacket::writeNet_HDU(const lc::LC& control, const data::LowSpeedData& lsd) +{ + uint8_t lco = control.getLCO(); + uint8_t mfId = control.getMFId(); + uint32_t dstId = control.getDstId(); + uint32_t srcId = control.getSrcId(); + + // don't process network frames if the destination ID's don't match and the network TG hang timer is running + if (m_p25->m_rfLastDstId != 0U) { + if (m_p25->m_rfLastDstId != dstId && (m_p25->m_networkTGHang.isRunning() && !m_p25->m_networkTGHang.hasExpired())) { + resetNet(); + return; + } + } + + // ensure our srcId and dstId are sane from the last LDU1 + if (m_netLastLDU1.getDstId() != 0U) { + if (dstId != m_netLastLDU1.getDstId()) { + LogWarning(LOG_NET, P25_HDU_STR ", dstId = %u doesn't match last LDU1 dstId = %u, fixing", + m_rfLC.getDstId(), m_rfLastLDU1.getDstId()); + dstId = m_netLastLDU1.getDstId(); + } + } + else { + LogWarning(LOG_NET, P25_HDU_STR ", last LDU1 LC has bad data, dstId = 0"); + } + + if (m_netLastLDU1.getSrcId() != 0U) { + if (srcId != m_netLastLDU1.getSrcId()) { + LogWarning(LOG_NET, P25_HDU_STR ", srcId = %u doesn't match last LDU1 srcId = %u, fixing", + m_rfLC.getSrcId(), m_rfLastLDU1.getSrcId()); + srcId = m_netLastLDU1.getSrcId(); + } + } + else { + LogWarning(LOG_NET, P25_HDU_STR ", last LDU1 LC has bad data, srcId = 0"); + } + + uint8_t algId = m_netLDU2[126U]; + uint32_t kId = (m_netLDU2[127U] << 8) + m_netLDU2[128U]; + bool group = control.getLCO() == LC_GROUP; + + uint8_t mi[P25_MI_LENGTH_BYTES]; + ::memcpy(mi + 0U, m_netLDU2 + 51U, 3U); + ::memcpy(mi + 3U, m_netLDU2 + 76U, 3U); + ::memcpy(mi + 6U, m_netLDU2 + 101U, 3U); + + uint8_t serviceOptions = (uint8_t)(m_netLDU1[201U]); + + m_netLC.reset(); + m_netLC.setMI(mi); + m_netLC.setAlgId(algId); + m_netLC.setKId(kId); + m_netLC.setLCO(lco); + m_netLC.setMFId(mfId); + m_netLC.setSrcId(srcId); + m_netLC.setDstId(dstId); + m_netLC.setGroup(group); + m_netLC.setEmergency((serviceOptions & 0x80U) == 0x80U); + m_netLC.setEncrypted((serviceOptions & 0x40U) == 0x40U); + m_netLC.setPriority((serviceOptions & 0x07U)); + + m_p25->m_trunk->setNetLC(m_netLC); + + // validate source RID + if (!acl::AccessControl::validateSrcId(srcId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, RID rejection, srcId = %u", srcId); + return; + } + + // is this a group or individual operation? + if (!group) { + // validate the target RID + if (!acl::AccessControl::validateSrcId(dstId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, RID rejection, dstId = %u", dstId); + return; + } + } + else { + // validate the target ID, if the target is a talkgroup + if (!acl::AccessControl::validateTGId(dstId)) { + LogWarning(LOG_NET, P25_HDU_STR " denial, TGID rejection, dstId = %u", dstId); + return; + } + } + + ::ActivityLog("P25", false, "received network transmission from %u to %s%u", srcId, group ? "TG " : "", dstId); + + m_rfLC.reset(); + m_rfLC.setMI(mi); + m_rfLC.setAlgId(algId); + m_rfLC.setKId(kId); + m_rfLC.setMFId(mfId); + m_rfLC.setSrcId(srcId); + m_rfLC.setDstId(dstId); + m_rfLC.setGroup(group); + m_rfLC.setEmergency((serviceOptions & 0x80U) == 0x80U); + m_rfLC.setEncrypted((serviceOptions & 0x40U) == 0x40U); + m_rfLC.setPriority((serviceOptions & 0x07U)); + + m_p25->m_trunk->setRFLC(m_rfLC); + + if (m_p25->m_control) { + if (group && (m_lastPatchGroup != dstId) && + (dstId != m_p25->m_trunk->m_patchSuperGroup)) { + m_p25->m_trunk->writeRF_TSDU_Mot_Patch(dstId, 0U, 0U); + m_lastPatchGroup = dstId; + } + + if (!m_p25->m_trunk->writeRF_TSDU_Grant(group, false)) { + if (m_network != NULL) + m_network->resetP25(); + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_p25->m_netTimeout.stop(); + m_p25->m_networkWatchdog.stop(); + m_netLC.reset(); + m_netLastLDU1.reset(); + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_netLastDstId = 0U; + if (m_p25->m_rfState == RS_RF_REJECTED) { + m_p25->m_rfState = RS_RF_LISTENING; + } + return; + } + } + + // single-channel trunking or voice on control support? + if (m_p25->m_control && m_p25->m_voiceOnControl) { + m_p25->m_ccRunning = false; // otherwise the grant will be bundled with other packets + m_p25->m_trunk->writeRF_TSDU_Grant(m_rfLC.getGroup(), true); + } + + m_p25->m_trunk->writeRF_TDULC_ChanGrant(group, srcId, dstId); + + if (m_verbose) { + LogMessage(LOG_NET, P25_HDU_STR ", dstId = %u, algo = $%02X, kid = $%04X", m_netLC.getDstId(), m_netLC.getAlgId(), m_netLC.getKId()); + } + + m_p25->m_netState = RS_NET_AUDIO; + m_p25->m_netLastDstId = dstId; + m_p25->m_netTimeout.start(); + m_netFrames = 0U; + m_netLost = 0U; + + uint8_t buffer[P25_HDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_HDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_HDU); + + // Generate header + m_netLC.encodeHDU(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_HDU_FRAME_LENGTH_BITS, false, true); + + m_p25->writeQueueNet(buffer, P25_HDU_FRAME_LENGTH_BYTES + 2U); + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_HDU", buffer + 2U, P25_HDU_FRAME_LENGTH_BYTES); + } +} + +/// +/// Helper to write a network P25 TDU packet. +/// +void VoicePacket::writeNet_TDU() +{ + if (m_p25->m_control) { + m_p25->m_trunk->releaseDstIdGrant(m_netLC.getDstId(), false); + } + + uint8_t buffer[P25_TDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_TDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_EOT; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_TDU); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_TDU_FRAME_LENGTH_BITS, true, true); + + m_p25->writeQueueNet(buffer, P25_TDU_FRAME_LENGTH_BYTES + 2U); + + if (m_verbose) { + LogMessage(LOG_NET, P25_TDU_STR ", srcId = %u", m_netLC.getSrcId()); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_TDU", buffer + 2U, P25_TDU_FRAME_LENGTH_BYTES); + } + + if (m_netFrames > 0) { + ::ActivityLog("P25", false, "network end of transmission, %.1f seconds, %u%% packet loss", + float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames); + } + else { + ::ActivityLog("P25", false, "network end of transmission, %u frames", m_netFrames); + } + + if (m_network != NULL) + m_network->resetP25(); + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_p25->m_netTimeout.stop(); + m_p25->m_networkWatchdog.stop(); + m_netLC.reset(); + m_netLastLDU1.reset(); + m_p25->m_netState = RS_NET_IDLE; + m_p25->m_netLastDstId = 0U; + m_p25->m_tailOnIdle = true; +} + +/// +/// Helper to check for an unflushed LDU1 packet. +/// +/// +/// +void VoicePacket::checkNet_LDU1(const lc::LC& control, const data::LowSpeedData& lsd) +{ + if (m_p25->m_netState == RS_NET_IDLE) + return; + + // Check for an unflushed LDU1 + if (m_netLDU1[0U] != 0x00U || m_netLDU1[25U] != 0x00U || m_netLDU1[50U] != 0x00U || + m_netLDU1[75U] != 0x00U || m_netLDU1[100U] != 0x00U || m_netLDU1[125U] != 0x00U || + m_netLDU1[150U] != 0x00U || m_netLDU1[175U] != 0x00U || m_netLDU1[200U] != 0x00U) + writeNet_LDU1(control, lsd); +} + +/// +/// Helper to write a network P25 LDU1 packet. +/// +/// +/// +void VoicePacket::writeNet_LDU1(const lc::LC& control, const data::LowSpeedData& lsd) +{ + uint8_t lco = control.getLCO(); + uint8_t mfId = control.getMFId(); + uint32_t dstId = control.getDstId(); + uint32_t srcId = control.getSrcId(); + + // don't process network frames if the destination ID's don't match and the network TG hang timer is running + if (m_p25->m_rfLastDstId != 0U) { + if (m_p25->m_rfLastDstId != dstId && (m_p25->m_networkTGHang.isRunning() && !m_p25->m_networkTGHang.hasExpired())) { + resetNet(); + return; + } + } + + uint8_t serviceOptions = (uint8_t)(m_netLDU1[201U]); + + if (m_p25->m_control) { + m_p25->m_trunk->touchDstIdGrant(m_rfLC.getDstId()); + } + + m_netLC.setLCO(lco); + m_netLC.setMFId(mfId); + m_netLC.setSrcId(srcId); + m_netLC.setDstId(dstId); + m_netLC.setEmergency((serviceOptions & 0x80U) == 0x80U); + m_netLC.setEncrypted((serviceOptions & 0x40U) == 0x40U); + m_netLC.setPriority((serviceOptions & 0x07U)); + + insertMissingAudio(m_netLDU1); + + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_LDU1); + + // Generate LDU1 data + m_netLC.encodeLDU1(buffer + 2U); + + // Add the Audio + m_audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U); + m_audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U); + m_audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U); + m_audio.encode(buffer + 2U, m_netLDU1 + 80U, 3U); + m_audio.encode(buffer + 2U, m_netLDU1 + 105U, 4U); + m_audio.encode(buffer + 2U, m_netLDU1 + 130U, 5U); + m_audio.encode(buffer + 2U, m_netLDU1 + 155U, 6U); + m_audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U); + m_audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U); + + // Add the Low Speed Data + m_netLSD.setLSD1(lsd.getLSD1()); + m_netLSD.setLSD2(lsd.getLSD2()); + m_netLSD.encode(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + m_p25->writeQueueNet(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + if (m_verbose) { + uint32_t loss = 0; + if (m_netFrames != 0) { + loss = (m_netLost * 100U) / m_netFrames; + } + else { + loss = (m_netLost * 100U) / 1U; + if (loss > 100) { + loss = 100; + } + } + + LogMessage(LOG_NET, P25_LDU1_STR " audio, srcId = %u, group = %u, emerg = %u, encrypt = %u, prio = %u, %u%% packet loss", + m_netLC.getSrcId(), m_netLC.getGroup(), m_netLC.getEmergency(), m_netLC.getEncrypted(), m_netLC.getPriority(), loss); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_LDU1", buffer + 2U, P25_LDU_FRAME_LENGTH_BYTES); + } + + ::memset(m_netLDU1, 0x00U, 9U * 25U); + + m_netFrames += 9U; +} + +/// +/// Helper to check for an unflushed LDU2 packet. +/// +/// +/// +void VoicePacket::checkNet_LDU2(const lc::LC& control, const data::LowSpeedData& lsd) +{ + if (m_p25->m_netState == RS_NET_IDLE) + return; + + // Check for an unflushed LDU2 + if (m_netLDU2[0U] != 0x00U || m_netLDU2[25U] != 0x00U || m_netLDU2[50U] != 0x00U || + m_netLDU2[75U] != 0x00U || m_netLDU2[100U] != 0x00U || m_netLDU2[125U] != 0x00U || + m_netLDU2[150U] != 0x00U || m_netLDU2[175U] != 0x00U || m_netLDU2[200U] != 0x00U) + writeNet_LDU2(control, lsd); +} + +/// +/// Helper to write a network P25 LDU2 packet. +/// +/// +/// +void VoicePacket::writeNet_LDU2(const lc::LC& control, const data::LowSpeedData& lsd) +{ + uint8_t algId = m_netLDU2[126U]; + uint32_t kId = (m_netLDU2[127U] << 8) + m_netLDU2[128U]; + + uint8_t mi[P25_MI_LENGTH_BYTES]; + ::memcpy(mi + 0U, m_netLDU2 + 51U, 3U); + ::memcpy(mi + 3U, m_netLDU2 + 76U, 3U); + ::memcpy(mi + 6U, m_netLDU2 + 101U, 3U); + + m_netLC.setMI(mi); + m_netLC.setAlgId(algId); + m_netLC.setKId(kId); + + insertMissingAudio(m_netLDU2); + + uint8_t buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + buffer[0U] = TAG_DATA; + buffer[1U] = 0x00U; + + // Generate Sync + Sync::addP25Sync(buffer + 2U); + + // Generate NID + m_p25->m_nid.encode(buffer + 2U, P25_DUID_LDU2); + + // Generate LDU2 data + m_netLC.encodeLDU2(buffer + 2U); + + // Add the Audio + m_audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U); + m_audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U); + m_audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U); + m_audio.encode(buffer + 2U, m_netLDU2 + 80U, 3U); + m_audio.encode(buffer + 2U, m_netLDU2 + 105U, 4U); + m_audio.encode(buffer + 2U, m_netLDU2 + 130U, 5U); + m_audio.encode(buffer + 2U, m_netLDU2 + 155U, 6U); + m_audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U); + m_audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U); + + // Add the Low Speed Data + m_netLSD.setLSD1(lsd.getLSD1()); + m_netLSD.setLSD2(lsd.getLSD2()); + m_netLSD.encode(buffer + 2U); + + // Add busy bits + m_p25->addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true); + + m_p25->writeQueueNet(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U); + + if (m_verbose) { + uint32_t loss = 0; + if (m_netFrames != 0) { + loss = (m_netLost * 100U) / m_netFrames; + } + else { + loss = (m_netLost * 100U) / 1U; + if (loss > 100) { + loss = 100; + } + } + + LogMessage(LOG_NET, P25_LDU2_STR " audio, algo = $%02X, kid = $%04X, %u%% packet loss", m_netLC.getAlgId(), m_netLC.getKId(), loss); + } + + if (m_debug) { + Utils::dump(2U, "!!! *TX P25 Network Frame - P25_DUID_LDU2", buffer + 2U, P25_LDU_FRAME_LENGTH_BYTES); + } + + ::memset(m_netLDU2, 0x00U, 9U * 25U); + + m_netFrames += 9U; +} + +/// +/// Helper to insert IMBE silence frames for missing audio. +/// +/// +void VoicePacket::insertMissingAudio(uint8_t* data) +{ + if (data[0U] == 0x00U) { + ::memcpy(data + 10U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 10U, 11U); + } + + if (data[25U] == 0x00U) { + ::memcpy(data + 26U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 26U, 11U); + } + + if (data[50U] == 0x00U) { + ::memcpy(data + 55U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 55U, 11U); + } + + if (data[75U] == 0x00U) { + ::memcpy(data + 80U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 80U, 11U); + } + + if (data[100U] == 0x00U) { + ::memcpy(data + 105U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 105U, 11U); + } + + if (data[125U] == 0x00U) { + ::memcpy(data + 130U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 130U, 11U); + } + + if (data[150U] == 0x00U) { + ::memcpy(data + 155U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 155U, 11U); + } + + if (data[175U] == 0x00U) { + ::memcpy(data + 180U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 180U, 11U); + } + + if (data[200U] == 0x00U) { + ::memcpy(data + 204U, m_lastIMBE, 11U); + m_netLost++; + } + else { + ::memcpy(m_lastIMBE, data + 204U, 11U); + } +} + +/// +/// Helper to insert IMBE null frames for missing audio. +/// +/// +void VoicePacket::insertNullAudio(uint8_t* data) +{ + if (data[0U] == 0x00U) { + ::memcpy(data + 10U, P25_NULL_IMBE, 11U); + } + + if (data[25U] == 0x00U) { + ::memcpy(data + 26U, P25_NULL_IMBE, 11U); + } + + if (data[50U] == 0x00U) { + ::memcpy(data + 55U, P25_NULL_IMBE, 11U); + } + + if (data[75U] == 0x00U) { + ::memcpy(data + 80U, P25_NULL_IMBE, 11U); + } + + if (data[100U] == 0x00U) { + ::memcpy(data + 105U, P25_NULL_IMBE, 11U); + } + + if (data[125U] == 0x00U) { + ::memcpy(data + 130U, P25_NULL_IMBE, 11U); + } + + if (data[150U] == 0x00U) { + ::memcpy(data + 155U, P25_NULL_IMBE, 11U); + } + + if (data[175U] == 0x00U) { + ::memcpy(data + 180U, P25_NULL_IMBE, 11U); + } + + if (data[200U] == 0x00U) { + ::memcpy(data + 204U, P25_NULL_IMBE, 11U); + } +} diff --git a/p25/VoicePacket.h b/p25/VoicePacket.h new file mode 100644 index 00000000..85b4b350 --- /dev/null +++ b/p25/VoicePacket.h @@ -0,0 +1,138 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_VOICE_PACKET_H__) +#define __P25_VOICE_PACKET_H__ + +#include "Defines.h" +#include "p25/data/LowSpeedData.h" +#include "p25/lc/LC.h" +#include "p25/Control.h" +#include "p25/Audio.h" +#include "network/BaseNetwork.h" + +#include +#include + +namespace p25 +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API TrunkPacket; + class HOST_SW_API Control; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements handling logic for P25 voice packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API VoicePacket { + public: + /// Resets the data states for the RF interface. + void resetRF(); + /// Resets the data states for the network. + void resetNet(); + + /// Process a data frame from the RF interface. + bool process(uint8_t* data, uint32_t len); + /// Process a data frame from the network. + bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, uint8_t& duid); + + /// Helper to write end of frame data. + bool writeEndRF(); + + private: + friend class TrunkPacket; + friend class Control; + Control* m_p25; + + network::BaseNetwork* m_network; + + uint32_t m_rfFrames; + uint32_t m_rfBits; + uint32_t m_rfErrs; + uint32_t m_rfUndecodableLC; + uint32_t m_netFrames; + uint32_t m_netLost; + + Audio m_audio; + lc::LC m_rfLC; + lc::LC m_rfLastHDU; + lc::LC m_rfLastLDU1; + lc::LC m_rfLastLDU2; + lc::LC m_netLC; + lc::LC m_netLastLDU1; + data::LowSpeedData m_rfLSD; + data::LowSpeedData m_netLSD; + uint8_t* m_netLDU1; + uint8_t* m_netLDU2; + uint8_t m_lastDUID; + uint8_t* m_lastIMBE; + + uint32_t m_lastPatchGroup; + + uint32_t m_silenceThreshold; + + bool m_verbose; + bool m_debug; + + /// Initializes a new instance of the VoicePacket class. + VoicePacket(Control* p25, network::BaseNetwork* network, bool debug, bool verbose); + /// Finalizes a instance of the VoicePacket class. + ~VoicePacket(); + + /// Write data processed from RF to the network. + void writeNetworkRF(const uint8_t* data, uint8_t duid); + + /// Helper to write end of voice frame data. + void writeRF_EndOfVoice(); + + /// Helper to write a network P25 HDU packet. + void writeNet_HDU(const lc::LC& control, const data::LowSpeedData& lsd); + /// Helper to write a network P25 TDU packet. + void writeNet_TDU(); + /// Helper to check for an unflushed LDU1 packet. + void checkNet_LDU1(const lc::LC& control, const data::LowSpeedData& lsd); + /// Helper to write a network P25 LDU1 packet. + void writeNet_LDU1(const lc::LC& control, const data::LowSpeedData& lsd); + /// Helper to check for an unflushed LDU2 packet. + void checkNet_LDU2(const lc::LC& control, const data::LowSpeedData& lsd); + /// Helper to write a network P25 LDU1 packet. + void writeNet_LDU2(const lc::LC& control, const data::LowSpeedData& lsd); + + /// Helper to insert IMBE silence frames for missing audio. + void insertMissingAudio(uint8_t* data); + /// Helper to insert IMBE null frames for missing audio. + void insertNullAudio(uint8_t* data); + }; +} // namespace p25 + +#endif // __P25_VOICE_PACKET_H__ diff --git a/p25/acl/AccessControl.cpp b/p25/acl/AccessControl.cpp new file mode 100644 index 00000000..b598b635 --- /dev/null +++ b/p25/acl/AccessControl.cpp @@ -0,0 +1,102 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019 Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +*/ +#include "Defines.h" +#include "p25/acl/AccessControl.h" +#include "Log.h" + +using namespace p25::acl; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Static Class Members +// --------------------------------------------------------------------------- + +RadioIdLookup* AccessControl::m_ridLookup; +TalkgroupIdLookup* AccessControl::m_tidLookup; + +/// +/// Initializes the P25 access control. +/// +/// Instance of the RadioIdLookup class. +/// Instance of the TalkgroupIdLookup class. +void AccessControl::init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup) +{ + m_ridLookup = ridLookup; + m_tidLookup = tidLookup; +} + +/// +/// Helper to validate a source radio ID. +/// +/// Source Radio ID. +/// True, if source radio ID is valid, otherwise false. +bool AccessControl::validateSrcId(uint32_t id) +{ + // check if RID ACLs are enabled + if (m_ridLookup->getACL() == false) { + return true; + } + + // lookup RID and perform test for validity + RadioId rid = m_ridLookup->find(id); + if (!rid.radioEnabled()) + return false; + + return true; +} + +/// +/// Helper to validate a talkgroup ID. +/// +/// DMR slot number. +/// Talkgroup ID. +/// True, if talkgroup ID is valid, otherwise false. +bool AccessControl::validateTGId(uint32_t id) +{ + // TG0 is never valid + if (id == 0U) + return false; + + // check if TID ACLs are enabled + if (m_tidLookup->getACL() == false) { + return true; + } + + // lookup TID and perform test for validity + TalkgroupId tid = m_tidLookup->find(id); + if (!tid.tgEnabled()) + return false; + + return true; +} diff --git a/p25/acl/AccessControl.h b/p25/acl/AccessControl.h new file mode 100644 index 00000000..ccee3cd0 --- /dev/null +++ b/p25/acl/AccessControl.h @@ -0,0 +1,66 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Simon Rune G7RZU +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017,2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +*/ +#if !defined(__P25_ACL__ACCESS_CONTROL_H__) +#define __P25_ACL__ACCESS_CONTROL_H__ + +#include "Defines.h" +#include "lookups/RadioIdLookup.h" +#include "lookups/TalkgroupIdLookup.h" + +namespace p25 +{ + namespace acl + { + using namespace ::lookups; + + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements radio and talkgroup ID access control checking. + // --------------------------------------------------------------------------- + + class HOST_SW_API AccessControl { + public: + /// Initializes the P25 access control. + static void init(RadioIdLookup* ridLookup, TalkgroupIdLookup* tidLookup); + + /// Helper to validate a source radio ID. + static bool validateSrcId(uint32_t id); + /// Helper to validate a talkgroup ID. + static bool validateTGId(uint32_t id); + + private: + static RadioIdLookup* m_ridLookup; + static TalkgroupIdLookup* m_tidLookup; + }; + } // namespace ACL +} // namespace p25 + +#endif // __P25_ACL__ACCESS_CONTROL_H__ diff --git a/p25/data/DataBlock.cpp b/p25/data/DataBlock.cpp new file mode 100644 index 00000000..83af467b --- /dev/null +++ b/p25/data/DataBlock.cpp @@ -0,0 +1,230 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/data/DataBlock.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25::data; +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DataBlock class. +/// +DataBlock::DataBlock() : + m_halfRateTrellis(false), + m_serialNo(0U), + m_trellis(), + m_fmt(PDU_FMT_CONFIRMED), + m_data(NULL) +{ + m_data = new uint8_t[P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; +} + +/// +/// Finalizes a instance of the DataBlock class. +/// +DataBlock::~DataBlock() +{ + delete[] m_data; +} + +/// +/// Decodes P25 PDU data block. +/// +/// +/// Instance of the DataHeader class. +/// True, if data block was decoded, otherwise false. +bool DataBlock::decode(const uint8_t* data, const DataHeader header) +{ + assert(data != NULL); + assert(m_data != NULL); + + uint8_t buffer[P25_PDU_CONFIRMED_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + + // decode 3/4 rate Trellis + m_halfRateTrellis = false; + bool valid = m_trellis.decode34(data, buffer); + if (!valid) { + // decode 1/2 rate Trellis + m_halfRateTrellis = true; + valid = m_trellis.decode12(data, buffer); + if (!valid) { + return false; + } + } + + // Utils::dump(1U, "PDU Data Block", buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); + + m_fmt = header.getFormat(); + + if (m_fmt == PDU_FMT_CONFIRMED) { + m_serialNo = buffer[0] & 0xFEU; // Confirmed Data Serial No. + uint16_t crc = ((buffer[0] & 0x01U) << 8) + buffer[1]; // CRC-9 Check Sum + uint32_t count = P25_PDU_CONFIRMED_LENGTH_BYTES; + if (m_serialNo == (header.getBlocksToFollow() - 1)) + count = P25_PDU_CONFIRMED_LENGTH_BYTES - 4U; + + for (uint32_t i = 2U; i < count; i++) { + m_data[i - 2U] = buffer[i]; // Payload Data + } + + // compute CRC-9 for the packet + ::memset(buffer, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + + buffer[0U] = (m_serialNo & 0x7FU) << 1; + ::memcpy(buffer + 1U, m_data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + uint16_t computedCRC = edac::CRC::crc9(buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); + + LogMessage(LOG_P25, "P25_DUID_PDU, fmt = $%02X, crc = $%04X, computedCRC = $%04X", m_fmt, crc, computedCRC); + } + else if ((m_fmt == PDU_FMT_UNCONFIRMED) || (m_fmt == PDU_FMT_RSP)) { + for (uint32_t i = 0U; i < P25_PDU_UNCONFIRMED_LENGTH_BYTES; i++) { + m_data[i] = buffer[i]; // Payload Data + } + } + else { + LogError(LOG_P25, "unknown FMT value in P25_DUID_PDU, fmt = $%02X", m_fmt); + } + + return true; +} + +/// +/// Encodes a P25 PDU data block. +/// +/// Buffer to encode data block to. +void DataBlock::encode(uint8_t* data) +{ + assert(data != NULL); + assert(m_data != NULL); + + if (m_fmt == PDU_FMT_CONFIRMED) { + uint8_t buffer[P25_PDU_CONFIRMED_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_CONFIRMED_LENGTH_BYTES); + + // compute CRC-9 for the packet + buffer[0U] = (m_serialNo & 0x7FU) << 1; + ::memcpy(buffer + 1U, m_data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + uint16_t crc = edac::CRC::crc9(buffer, P25_PDU_CONFIRMED_LENGTH_BYTES); + + ::memcpy(buffer + 2U, m_data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + buffer[0] = ((m_serialNo & 0x7FU) << 1) + // Confirmed Data Serial No. + (crc >> 8); // CRC-9 Check Sum (b8) + buffer[1] = (crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) + + if (!m_halfRateTrellis) { + m_trellis.encode34(buffer, data); + } + else { + m_trellis.encode12(buffer, data); + } + } + else if ((m_fmt == PDU_FMT_UNCONFIRMED) || (m_fmt == PDU_FMT_RSP)) { + uint8_t buffer[P25_PDU_UNCONFIRMED_LENGTH_BYTES]; + ::memset(buffer, 0x00U, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + ::memcpy(buffer, m_data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + + if (!m_halfRateTrellis) { + m_trellis.encode34(buffer, data); + } + else { + m_trellis.encode12(buffer, data); + } + } + else { + LogError(LOG_P25, "unknown FMT value in P25_DUID_PDU, fmt = $%02X", m_fmt); + return; + } +} + +/// Sets the data format. +/// +void DataBlock::setFormat(const DataHeader header) +{ + m_fmt = header.getFormat(); +} + +/// Gets the data format. +/// +uint8_t DataBlock::getFormat() const +{ + return m_fmt; +} + +/// Sets the raw data stored in the data block. +/// +void DataBlock::setData(const uint8_t* buffer) +{ + assert(buffer != NULL); + assert(m_data != NULL); + + if (m_fmt == PDU_FMT_CONFIRMED) { + ::memcpy(m_data, buffer, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + } + else if ((m_fmt == PDU_FMT_UNCONFIRMED) || (m_fmt == PDU_FMT_RSP)) { + ::memcpy(m_data, buffer, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + } + else { + LogError(LOG_P25, "unknown FMT value in P25_DUID_PDU, fmt = $%02X", m_fmt); + } +} + +/// Gets the raw data stored in the data block. +/// +uint32_t DataBlock::getData(uint8_t* buffer) const +{ + assert(buffer != NULL); + assert(m_data != NULL); + + if (m_fmt == PDU_FMT_CONFIRMED) { + ::memcpy(buffer, m_data, P25_PDU_CONFIRMED_DATA_LENGTH_BYTES); + return P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; + } + else if ((m_fmt == PDU_FMT_UNCONFIRMED) || (m_fmt == PDU_FMT_RSP)) { + ::memcpy(buffer, m_data, P25_PDU_UNCONFIRMED_LENGTH_BYTES); + return P25_PDU_UNCONFIRMED_LENGTH_BYTES; + } + else { + LogError(LOG_P25, "unknown FMT value in P25_DUID_PDU, fmt = $%02X", m_fmt); + return 0U; + } +} diff --git a/p25/data/DataBlock.h b/p25/data/DataBlock.h new file mode 100644 index 00000000..07f3900c --- /dev/null +++ b/p25/data/DataBlock.h @@ -0,0 +1,87 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_DATA__DATA_BLOCK_H__) +#define __P25_DATA__DATA_BLOCK_H__ + +#include "Defines.h" +#include "p25/data/DataHeader.h" +#include "p25/edac/Trellis.h" + +#include + +namespace p25 +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents a data block for PDU P25 packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API DataBlock { + public: + /// Initializes a new instance of the DataBlock class. + DataBlock(); + /// Finalizes a instance of the DataBlock class. + ~DataBlock(); + + /// Decodes P25 PDU data block. + bool decode(const uint8_t* data, const DataHeader header); + /// Encodes a P25 PDU data block. + void encode(uint8_t* data); + + /// Sets the data format. + void setFormat(const DataHeader header); + /// Gets the data format. + uint8_t getFormat() const; + + /// Sets the raw data stored in the data block. + void setData(const uint8_t* buffer); + /// Gets the raw data stored in the data block. + uint32_t getData(uint8_t* buffer) const; + + public: + /// Flag indicating whether or not the data block uses 1/2 rate Trellis. + __PROPERTY(bool, halfRateTrellis, HalfRateTrellis); + + /// Sets the data block serial number. + __PROPERTY(uint8_t, serialNo, SerialNo); + + private: + edac::Trellis m_trellis; + + uint8_t m_fmt; + + uint8_t* m_data; + }; + } // namespace data +} // namespace p25 + +#endif // __P25_DATA__DATA_BLOCK_H__ diff --git a/p25/data/DataHeader.cpp b/p25/data/DataHeader.cpp new file mode 100644 index 00000000..bc836c5f --- /dev/null +++ b/p25/data/DataHeader.cpp @@ -0,0 +1,250 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/data/DataHeader.h" +#include "edac/CRC.h" +#include "Utils.h" + +using namespace p25::data; +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the DataHeader class. +/// +DataHeader::DataHeader() : + m_ackNeeded(false), + m_outbound(false), + m_fmt(PDU_FMT_CONFIRMED), + m_sap(0U), + m_mfId(P25_MFG_STANDARD), + m_llId(0U), + m_fullMessage(false), + m_sync(false), + m_n(0U), + m_seqNo(0U), + m_headerOffset(0U), + m_trellis(), + m_blocksToFollow(0U), + m_padCount(0U), + m_dataOctets(0U) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the DataHeader class. +/// +DataHeader::~DataHeader() +{ + /* stub */ +} + +/// +/// Decodes P25 PDU data header. +/// +/// +/// True, if PDU data header was decoded, otherwise false. +bool DataHeader::decode(const uint8_t* data) +{ + assert(data != NULL); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + // decode 1/2 rate Trellis & check CRC-CCITT 16 + bool valid = m_trellis.decode12(data, header); + if (valid) + valid = edac::CRC::checkCCITT162(header, P25_PDU_HEADER_LENGTH_BYTES); + if (!valid) { + return false; + } + + // Utils::dump(1U, "PDU Header Data", data, P25_PDU_HEADER_LENGTH_BYTES); + + m_ackNeeded = (header[0U] & 0x40U) == 0x40U; // Acknowledge Needed + m_outbound = (header[0U] & 0x20U) == 0x20U; // Inbound/Outbound + + m_fmt = header[0U] & 0x1F; // Packet Format + + m_sap = header[1U] & 0x3FU; // Service Access Point + m_mfId = header[2U]; // Mfg Id. + + m_llId = (header[3U] << 16) + (header[4U] << 8) + header[5U]; // Logical Link ID + + m_fullMessage = (header[6U] & 0x80U) == 0x80U; // Full Message Flag + m_blocksToFollow = header[6U] & 0x7FU; // Block Frames to Follow + m_padCount = header[7U] & 0x1FU; // Pad Count + + if (m_fmt == PDU_FMT_CONFIRMED) { + m_dataOctets = 16 * m_blocksToFollow - 4 - m_padCount; + } + else { + m_dataOctets = 12 * m_blocksToFollow - 4 - m_padCount; + } + + m_sync = (header[8U] & 0x80U) == 0x80U; // Re-synchronize Flag + + m_n = (header[8U] >> 4) & 0x07U; // Packet Sequence No. + m_seqNo = header[8U] & 0x0FU; // Fragment Sequence No. + + m_headerOffset = header[9U] & 0x3FU; // Data Header Offset + + return true; +} + +/// +/// Encodes P25 PDU data header. +/// +/// +void DataHeader::encode(uint8_t* data) +{ + assert(data != NULL); + + uint8_t header[P25_PDU_HEADER_LENGTH_BYTES]; + ::memset(header, 0x00U, P25_PDU_HEADER_LENGTH_BYTES); + + header[0U] = (m_ackNeeded ? 0x40U : 0x00U) + // Acknowledge Needed + (m_outbound ? 0x20U : 0x00U) + // Inbound/Outbound + (m_fmt & 0x1FU); // Packet Format + + header[1U] = m_sap & 0x3FU; // Service Access Point + header[1U] |= 0xC0; + + header[2U] = m_mfId; // Mfg Id. + + header[3U] = (m_llId >> 16) & 0xFFU; // Logical Link ID + header[4U] = (m_llId >> 8) & 0xFFU; + header[5U] = (m_llId >> 0) & 0xFFU; + + header[6U] = (m_fullMessage ? 0x80U : 0x00U) + // Full Message Flag + (m_blocksToFollow & 0x7FU); // Blocks Frames to Follow + + header[7U] = (m_padCount & 0x1FU); // Pad Count + + header[8U] = (m_sync ? 0x80U : 0x00U) + // Re-synchronize Flag + ((m_n << 4) && 0x07U) + // Packet Sequence No. + (m_seqNo & 0x0F); // Fragment Sequence No. + + header[9U] = m_headerOffset & 0x3FU; // Data Header Offset + + // compute CRC-CCITT 16 + edac::CRC::addCCITT162(header, P25_PDU_HEADER_LENGTH_BYTES); + + // encode 1/2 rate Trellis + m_trellis.encode12(header, data); +} + +/// +/// Helper to reset data values to defaults. +/// +void DataHeader::reset() +{ + m_ackNeeded = false; + m_outbound = false; + + m_fmt = PDU_FMT_CONFIRMED; + + m_sap = PDU_SAP_USER_DATA; + m_mfId = P25_MFG_STANDARD; + m_llId = 0U; + + m_fullMessage = false; + m_blocksToFollow = 0U; + m_padCount = 0U; + + m_dataOctets = 0U; + + m_sync = false; + + m_n = 0U; + m_seqNo = 0U; + + m_headerOffset = 0U; +} + +/// Gets the total number of data octets. +/// +uint32_t DataHeader::getDataOctets() const +{ + return m_dataOctets; +} + +/** Common Data */ +/// Sets the total number of blocks to follow this header. +/// +void DataHeader::setBlocksToFollow(uint8_t blocksToFollow) +{ + m_blocksToFollow = blocksToFollow; + + // recalculate count of data octets + if (m_fmt == PDU_FMT_CONFIRMED) { + m_dataOctets = 16 * m_blocksToFollow - 4 - m_padCount; + } + else { + m_dataOctets = 12 * m_blocksToFollow - 4 - m_padCount; + } +} + +/// Gets the total number of blocks to follow this header. +/// +uint8_t DataHeader::getBlocksToFollow() const +{ + return m_blocksToFollow; +} + +/// +/// +void DataHeader::setPadCount(uint8_t padCount) +{ + m_padCount = padCount; + + // recalculate count of data octets + if (m_fmt == PDU_FMT_CONFIRMED) { + m_dataOctets = 16 * m_blocksToFollow - 4 - m_padCount; + } + else { + m_dataOctets = 12 * m_blocksToFollow - 4 - m_padCount; + } +} + +/// +/// +uint8_t DataHeader::getPadCount() const +{ + return m_padCount; +} diff --git a/p25/data/DataHeader.h b/p25/data/DataHeader.h new file mode 100644 index 00000000..0bf445b8 --- /dev/null +++ b/p25/data/DataHeader.h @@ -0,0 +1,109 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2018 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_DATA__DATA_HEADER_H__) +#define __P25_DATA__DATA_HEADER_H__ + +#include "Defines.h" +#include "p25/edac/Trellis.h" + +#include + +namespace p25 +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents the data header for PDU P25 packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API DataHeader { + public: + /// Initializes a new instance of the DataHeader class. + DataHeader(); + /// Finalizes a instance of the DataHeader class. + ~DataHeader(); + + /// Decodes P25 PDU data header. + bool decode(const uint8_t* data); + /// Encodes P25 PDU data header. + void encode(uint8_t* data); + + /// Helper to reset data values to defaults. + void reset(); + + /// Gets the total number of data octets. + uint32_t getDataOctets() const; + + /** Common Data */ + /// Sets the total number of blocks to follow this header. + void setBlocksToFollow(uint8_t blocksToFollow); + /// Gets the total number of blocks to follow this header. + uint8_t getBlocksToFollow() const; + /// + void setPadCount(uint8_t padCount); + /// + uint8_t getPadCount() const; + + public: + /// Flag indicating if acknowledgement is needed. + __PROPERTY(bool, ackNeeded, AckNeeded); + /// Flag indicating if this is an outbound data packet. + __PROPERTY(bool, outbound, Outbound); + /// Data packet format. + __PROPERTY(uint8_t, fmt, Format); + /// Service access point. + __PROPERTY(uint8_t, sap, SAP); + /// Manufacturer ID. + __PROPERTY(uint8_t, mfId, MFId); + /// Logical link ID. + __PROPERTY(uint32_t, llId, LLId); + /// Flag indicating whether or not this data packet is a full message. + __PROPERTY(bool, fullMessage, FullMessage); + /// + __PROPERTY(bool, sync, Sync); + /// + __PROPERTY(uint8_t, n, N); + /// Data packet sequence number. + __PROPERTY(uint8_t, seqNo, SeqNo); + /// + __PROPERTY(uint8_t, headerOffset, HeaderOffset); + + private: + edac::Trellis m_trellis; + + uint8_t m_blocksToFollow; + uint8_t m_padCount; + uint32_t m_dataOctets; + }; + } // namespace data +} // namespace p25 + +#endif // __P25_DATA__DATA_HEADER_H__ diff --git a/p25/data/LowSpeedData.cpp b/p25/data/LowSpeedData.cpp new file mode 100644 index 00000000..28b57dc5 --- /dev/null +++ b/p25/data/LowSpeedData.cpp @@ -0,0 +1,155 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/data/LowSpeedData.h" +#include "p25/P25Utils.h" + +using namespace p25::data; +using namespace p25; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t CCS_PARITY[] = { + 0x00U, 0x39U, 0x72U, 0x4BU, 0xE4U, 0xDDU, 0x96U, 0xAFU, 0xF1U, 0xC8U, 0x83U, 0xBAU, 0x15U, 0x2CU, 0x67U, 0x5EU, + 0xDBU, 0xE2U, 0xA9U, 0x90U, 0x3FU, 0x06U, 0x4DU, 0x74U, 0x2AU, 0x13U, 0x58U, 0x61U, 0xCEU, 0xF7U, 0xBCU, 0x85U, + 0x8FU, 0xB6U, 0xFDU, 0xC4U, 0x6BU, 0x52U, 0x19U, 0x20U, 0x7EU, 0x47U, 0x0CU, 0x35U, 0x9AU, 0xA3U, 0xE8U, 0xD1U, + 0x54U, 0x6DU, 0x26U, 0x1FU, 0xB0U, 0x89U, 0xC2U, 0xFBU, 0xA5U, 0x9CU, 0xD7U, 0xEEU, 0x41U, 0x78U, 0x33U, 0x0AU, + 0x27U, 0x1EU, 0x55U, 0x6CU, 0xC3U, 0xFAU, 0xB1U, 0x88U, 0xD6U, 0xEFU, 0xA4U, 0x9DU, 0x32U, 0x0BU, 0x40U, 0x79U, + 0xFCU, 0xC5U, 0x8EU, 0xB7U, 0x18U, 0x21U, 0x6AU, 0x53U, 0x0DU, 0x34U, 0x7FU, 0x46U, 0xE9U, 0xD0U, 0x9BU, 0xA2U, + 0xA8U, 0x91U, 0xDAU, 0xE3U, 0x4CU, 0x75U, 0x3EU, 0x07U, 0x59U, 0x60U, 0x2BU, 0x12U, 0xBDU, 0x84U, 0xCFU, 0xF6U, + 0x73U, 0x4AU, 0x01U, 0x38U, 0x97U, 0xAEU, 0xE5U, 0xDCU, 0x82U, 0xBBU, 0xF0U, 0xC9U, 0x66U, 0x5FU, 0x14U, 0x2DU, + 0x4EU, 0x77U, 0x3CU, 0x05U, 0xAAU, 0x93U, 0xD8U, 0xE1U, 0xBFU, 0x86U, 0xCDU, 0xF4U, 0x5BU, 0x62U, 0x29U, 0x10U, + 0x95U, 0xACU, 0xE7U, 0xDEU, 0x71U, 0x48U, 0x03U, 0x3AU, 0x64U, 0x5DU, 0x16U, 0x2FU, 0x80U, 0xB9U, 0xF2U, 0xCBU, + 0xC1U, 0xF8U, 0xB3U, 0x8AU, 0x25U, 0x1CU, 0x57U, 0x6EU, 0x30U, 0x09U, 0x42U, 0x7BU, 0xD4U, 0xEDU, 0xA6U, 0x9FU, + 0x1AU, 0x23U, 0x68U, 0x51U, 0xFEU, 0xC7U, 0x8CU, 0xB5U, 0xEBU, 0xD2U, 0x99U, 0xA0U, 0x0FU, 0x36U, 0x7DU, 0x44U, + 0x69U, 0x50U, 0x1BU, 0x22U, 0x8DU, 0xB4U, 0xFFU, 0xC6U, 0x98U, 0xA1U, 0xEAU, 0xD3U, 0x7CU, 0x45U, 0x0EU, 0x37U, + 0xB2U, 0x8BU, 0xC0U, 0xF9U, 0x56U, 0x6FU, 0x24U, 0x1DU, 0x43U, 0x7AU, 0x31U, 0x08U, 0xA7U, 0x9EU, 0xD5U, 0xECU, + 0xE6U, 0xDFU, 0x94U, 0xADU, 0x02U, 0x3BU, 0x70U, 0x49U, 0x17U, 0x2EU, 0x65U, 0x5CU, 0xF3U, 0xCAU, 0x81U, 0xB8U, + 0x3DU, 0x04U, 0x4FU, 0x76U, 0xD9U, 0xE0U, 0xABU, 0x92U, 0xCCU, 0xF5U, 0xBEU, 0x87U, 0x28U, 0x11U, 0x5AU, 0x63U }; + +const uint32_t MAX_CCS_ERRS = 4U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the LowSpeedData class. +/// +LowSpeedData::LowSpeedData() : + m_lsd1(0x00U), + m_lsd2(0x00U) +{ + /* stub */ +} + +/// +/// Finalizes a new instance of the LowSpeedData class. +/// +LowSpeedData::~LowSpeedData() +{ + /* stub */ +} + +/// +/// Decodes embedded low speed data. +/// +/// +void LowSpeedData::process(uint8_t* data) +{ + assert(data != NULL); + + uint8_t lsd[4U]; + P25Utils::decode(data, lsd, 1546U, 1578U); + + for (uint32_t a = 0x00U; a < 0x100U; a++) { + uint8_t ccs[2U]; + ccs[0U] = a; + ccs[1U] = encode(ccs[0U]); + + uint32_t errs = P25Utils::compare(ccs, lsd + 0U, 2U); + if (errs < MAX_CCS_ERRS) { + lsd[0U] = ccs[0U]; + lsd[1U] = ccs[1U]; + break; + } + } + + for (uint32_t a = 0x00U; a < 0x100U; a++) { + uint8_t ccs[2U]; + ccs[0U] = a; + ccs[1U] = encode(ccs[0U]); + + uint32_t errs = P25Utils::compare(ccs, lsd + 2U, 2U); + if (errs < MAX_CCS_ERRS) { + lsd[2U] = ccs[0U]; + lsd[3U] = ccs[1U]; + break; + } + } + + m_lsd1 = lsd[0U]; + m_lsd2 = lsd[2U]; + + P25Utils::encode(lsd, data, 1546U, 1578U); +} + +/// +/// Encode embedded low speed data. +/// +/// +void LowSpeedData::encode(uint8_t* data) const +{ + assert(data != NULL); + + uint8_t lsd[4U]; + lsd[0U] = m_lsd1; + lsd[1U] = encode(m_lsd1); + lsd[2U] = m_lsd2; + lsd[3U] = encode(m_lsd2); + + P25Utils::encode(lsd, data, 1546U, 1578U); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +uint8_t LowSpeedData::encode(uint8_t in) const +{ + return CCS_PARITY[in]; +} diff --git a/p25/data/LowSpeedData.h b/p25/data/LowSpeedData.h new file mode 100644 index 00000000..1bddebbf --- /dev/null +++ b/p25/data/LowSpeedData.h @@ -0,0 +1,69 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_DATA__LOW_SPEED_DATA_H__) +#define __P25_DATA__LOW_SPEED_DATA_H__ + +#include "Defines.h" + +namespace p25 +{ + namespace data + { + // --------------------------------------------------------------------------- + // Class Declaration + // Represents embedded low speed data in P25 LDUs. + // --------------------------------------------------------------------------- + + class HOST_SW_API LowSpeedData { + public: + /// Initializes a new instance of the LowSpeedData class. + LowSpeedData(); + /// Finalizes a new instance of the LowSpeedData class. + ~LowSpeedData(); + + /// Decodes embedded low speed data. + void process(uint8_t* data); + /// Encode embedded low speed data. + void encode(uint8_t* data) const; + + public: + /// Low speed data 1 value. + __PROPERTY(uint8_t, lsd1, LSD1); + /// Low speed data 2 value. + __PROPERTY(uint8_t, lsd2, LSD2); + + private: + /// + uint8_t encode(const uint8_t in) const; + }; + } // namespace data +} // namespace p25 + +#endif // __P25_DATA__LOW_SPEED_DATA_H__ diff --git a/p25/edac/Trellis.cpp b/p25/edac/Trellis.cpp new file mode 100644 index 00000000..e69d7246 --- /dev/null +++ b/p25/edac/Trellis.cpp @@ -0,0 +1,641 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2018 by Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include "Defines.h" +#include "p25/edac/Trellis.h" + +using namespace p25::edac; + +#include +#include + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t INTERLEAVE_TABLE[] = { + 0U, 1U, 8U, 9U, 16U, 17U, 24U, 25U, 32U, 33U, 40U, 41U, 48U, 49U, 56U, 57U, 64U, 65U, 72U, 73U, 80U, 81U, 88U, 89U, 96U, 97U, + 2U, 3U, 10U, 11U, 18U, 19U, 26U, 27U, 34U, 35U, 42U, 43U, 50U, 51U, 58U, 59U, 66U, 67U, 74U, 75U, 82U, 83U, 90U, 91U, + 4U, 5U, 12U, 13U, 20U, 21U, 28U, 29U, 36U, 37U, 44U, 45U, 52U, 53U, 60U, 61U, 68U, 69U, 76U, 77U, 84U, 85U, 92U, 93U, + 6U, 7U, 14U, 15U, 22U, 23U, 30U, 31U, 38U, 39U, 46U, 47U, 54U, 55U, 62U, 63U, 70U, 71U, 78U, 79U, 86U, 87U, 94U, 95U }; + +const uint8_t ENCODE_TABLE_34[] = { + 0U, 8U, 4U, 12U, 2U, 10U, 6U, 14U, + 4U, 12U, 2U, 10U, 6U, 14U, 0U, 8U, + 1U, 9U, 5U, 13U, 3U, 11U, 7U, 15U, + 5U, 13U, 3U, 11U, 7U, 15U, 1U, 9U, + 3U, 11U, 7U, 15U, 1U, 9U, 5U, 13U, + 7U, 15U, 1U, 9U, 5U, 13U, 3U, 11U, + 2U, 10U, 6U, 14U, 0U, 8U, 4U, 12U, + 6U, 14U, 0U, 8U, 4U, 12U, 2U, 10U }; + +const uint8_t ENCODE_TABLE_12[] = { + 0U, 15U, 12U, 3U, + 4U, 11U, 8U, 7U, + 13U, 2U, 1U, 14U, + 9U, 6U, 5U, 10U }; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the Trellis class. +/// +Trellis::Trellis() +{ + /* stub */ +} + +/// +/// Finalizes a instance of the Trellis class. +/// +Trellis::~Trellis() +{ + /* stub */ +} + +/// +/// Decodes 3/4 rate Trellis. +/// +/// +/// +/// +bool Trellis::decode34(const uint8_t* data, uint8_t* payload) +{ + assert(data != NULL); + assert(payload != NULL); + + int8_t dibits[98U]; + deinterleave(data, dibits); + + uint8_t points[49U]; + dibitsToPoints(dibits, points); + + // Check the original code + uint8_t tribits[49U]; + uint32_t failPos = checkCode34(points, tribits); + if (failPos == 999U) { + tribitsToBits(tribits, payload); + return true; + } + + uint8_t savePoints[49U]; + for (uint32_t i = 0U; i < 49U; i++) + savePoints[i] = points[i]; + + bool ret = fixCode34(points, failPos, payload); + if (ret) + return true; + + if (failPos == 0U) + return false; + + // Backtrack one place for a last go + return fixCode34(savePoints, failPos - 1U, payload); +} + +/// +/// Encodes 3/4 rate Trellis. +/// +/// +/// +/// +void Trellis::encode34(const uint8_t* payload, uint8_t* data) +{ + assert(payload != NULL); + assert(data != NULL); + + uint8_t tribits[49U]; + bitsToTribits(payload, tribits); + + uint8_t points[49U]; + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + uint8_t tribit = tribits[i]; + + points[i] = ENCODE_TABLE_34[state * 8U + tribit]; + + state = tribit; + } + + int8_t dibits[98U]; + pointsToDibits(points, dibits); + + interleave(dibits, data); +} + +/// +/// Decodes 1/2 rate Trellis. +/// +/// +/// +/// +bool Trellis::decode12(const uint8_t* data, uint8_t* payload) +{ + assert(data != NULL); + assert(payload != NULL); + + int8_t dibits[98U]; + deinterleave(data, dibits); + + uint8_t points[49U]; + dibitsToPoints(dibits, points); + + // Check the original code + uint8_t bits[49U]; + uint32_t failPos = checkCode12(points, bits); + if (failPos == 999U) { + dibitsToBits(bits, payload); + return true; + } + + uint8_t savePoints[49U]; + for (uint32_t i = 0U; i < 49U; i++) + savePoints[i] = points[i]; + + bool ret = fixCode12(points, failPos, payload); + if (ret) + return true; + + if (failPos == 0U) + return false; + + // Backtrack one place for a last go + return fixCode12(savePoints, failPos - 1U, payload); +} + +/// +/// Encodes 1/2 rate Trellis. +/// +/// +/// +/// +void Trellis::encode12(const uint8_t* payload, uint8_t* data) +{ + assert(payload != NULL); + assert(data != NULL); + + uint8_t bits[49U]; + bitsToDibits(payload, bits); + + uint8_t points[49U]; + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + uint8_t bit = bits[i]; + + points[i] = ENCODE_TABLE_12[state * 4U + bit]; + + state = bit; + } + + int8_t dibits[98U]; + pointsToDibits(points, dibits); + + interleave(dibits, data); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// +/// +/// +/// +void Trellis::deinterleave(const uint8_t* data, int8_t* dibits) const +{ + for (uint32_t i = 0U; i < 98U; i++) { + uint32_t n = i * 2U + 0U; + bool b1 = READ_BIT(data, n) != 0x00U; + + n = i * 2U + 1U; + bool b2 = READ_BIT(data, n) != 0x00U; + + int8_t dibit; + if (!b1 && b2) + dibit = +3; + else if (!b1 && !b2) + dibit = +1; + else if (b1 && !b2) + dibit = -1; + else + dibit = -3; + + n = INTERLEAVE_TABLE[i]; + dibits[n] = dibit; + } +} + +/// +/// +/// +/// +/// +void Trellis::interleave(const int8_t* dibits, uint8_t* data) const +{ + for (uint32_t i = 0U; i < 98U; i++) { + uint32_t n = INTERLEAVE_TABLE[i]; + + bool b1, b2; + switch (dibits[n]) { + case +3: + b1 = false; + b2 = true; + break; + case +1: + b1 = false; + b2 = false; + break; + case -1: + b1 = true; + b2 = false; + break; + default: + b1 = true; + b2 = true; + break; + } + + n = i * 2U + 0U; + WRITE_BIT(data, n, b1); + + n = i * 2U + 1U; + WRITE_BIT(data, n, b2); + } +} + +/// +/// +/// +/// +/// +void Trellis::dibitsToPoints(const int8_t* dibits, uint8_t* points) const +{ + for (uint32_t i = 0U; i < 49U; i++) { + if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -1) + points[i] = 0U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -1) + points[i] = 1U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -3) + points[i] = 2U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -3) + points[i] = 3U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == -1) + points[i] = 4U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == -1) + points[i] = 5U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == -3) + points[i] = 6U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == -3) + points[i] = 7U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +3) + points[i] = 8U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +3) + points[i] = 9U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +1) + points[i] = 10U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +1) + points[i] = 11U; + else if (dibits[i * 2U + 0U] == +1 && dibits[i * 2U + 1U] == +3) + points[i] = 12U; + else if (dibits[i * 2U + 0U] == -1 && dibits[i * 2U + 1U] == +3) + points[i] = 13U; + else if (dibits[i * 2U + 0U] == +3 && dibits[i * 2U + 1U] == +1) + points[i] = 14U; + else if (dibits[i * 2U + 0U] == -3 && dibits[i * 2U + 1U] == +1) + points[i] = 15U; + } +} + +/// +/// +/// +/// +/// +void Trellis::pointsToDibits(const uint8_t* points, int8_t* dibits) const +{ + for (uint32_t i = 0U; i < 49U; i++) { + switch (points[i]) { + case 0U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -1; + break; + case 1U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -1; + break; + case 2U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -3; + break; + case 3U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -3; + break; + case 4U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = -1; + break; + case 5U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = -1; + break; + case 6U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = -3; + break; + case 7U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = -3; + break; + case 8U: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +3; + break; + case 9U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +3; + break; + case 10U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +1; + break; + case 11U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +1; + break; + case 12U: + dibits[i * 2U + 0U] = +1; + dibits[i * 2U + 1U] = +3; + break; + case 13U: + dibits[i * 2U + 0U] = -1; + dibits[i * 2U + 1U] = +3; + break; + case 14U: + dibits[i * 2U + 0U] = +3; + dibits[i * 2U + 1U] = +1; + break; + default: + dibits[i * 2U + 0U] = -3; + dibits[i * 2U + 1U] = +1; + break; + } + } +} + +/// +/// +/// +/// +/// +void Trellis::bitsToTribits(const uint8_t* payload, uint8_t* tribits) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint32_t n = i * 3U; + + bool b1 = READ_BIT(payload, n) != 0x00U; + n++; + bool b2 = READ_BIT(payload, n) != 0x00U; + n++; + bool b3 = READ_BIT(payload, n) != 0x00U; + + uint8_t tribit = 0U; + tribit |= b1 ? 4U : 0U; + tribit |= b2 ? 2U : 0U; + tribit |= b3 ? 1U : 0U; + + tribits[i] = tribit; + } + + tribits[48U] = 0U; +} + +/// +/// +/// +/// +/// +void Trellis::bitsToDibits(const uint8_t* payload, uint8_t* dibits) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint32_t n = i * 2U; + + bool b1 = READ_BIT(payload, n) != 0x00U; + n++; + bool b2 = READ_BIT(payload, n) != 0x00U; + + uint8_t dibit = 0U; + dibit |= b1 ? 2U : 0U; + dibit |= b2 ? 1U : 0U; + + dibits[i] = dibit; + } + + dibits[48U] = 0U; +} + +/// +/// +/// +/// +/// +void Trellis::tribitsToBits(const uint8_t* tribits, uint8_t* payload) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint8_t tribit = tribits[i]; + + bool b1 = (tribit & 0x04U) == 0x04U; + bool b2 = (tribit & 0x02U) == 0x02U; + bool b3 = (tribit & 0x01U) == 0x01U; + + uint32_t n = i * 3U; + + WRITE_BIT(payload, n, b1); + n++; + WRITE_BIT(payload, n, b2); + n++; + WRITE_BIT(payload, n, b3); + } +} + +/// +/// +/// +/// +/// +void Trellis::dibitsToBits(const uint8_t* dibits, uint8_t* payload) const +{ + for (uint32_t i = 0U; i < 48U; i++) { + uint8_t dibit = dibits[i]; + + bool b1 = (dibit & 0x02U) == 0x02U; + bool b2 = (dibit & 0x01U) == 0x01U; + + uint32_t n = i * 2U; + + WRITE_BIT(payload, n, b1); + n++; + WRITE_BIT(payload, n, b2); + } +} + +/// +/// +/// +/// +/// +/// +/// +bool Trellis::fixCode34(uint8_t* points, uint32_t failPos, uint8_t* payload) const +{ + for (unsigned j = 0U; j < 20U; j++) { + uint32_t bestPos = 0U; + uint32_t bestVal = 0U; + + for (uint32_t i = 0U; i < 16U; i++) { + points[failPos] = i; + + uint8_t tribits[49U]; + uint32_t pos = checkCode34(points, tribits); + if (pos == 999U) { + tribitsToBits(tribits, payload); + return true; + } + + if (pos > bestPos) { + bestPos = pos; + bestVal = i; + } + } + + points[failPos] = bestVal; + failPos = bestPos; + } + + return false; +} + +/// +/// +/// +/// +/// +/// +uint32_t Trellis::checkCode34(const uint8_t* points, uint8_t* tribits) const +{ + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + tribits[i] = 9U; + + for (uint32_t j = 0U; j < 8U; j++) { + if (points[i] == ENCODE_TABLE_34[state * 8U + j]) { + tribits[i] = j; + break; + } + } + + if (tribits[i] == 9U) + return i; + + state = tribits[i]; + } + + if (tribits[48U] != 0U) + return 48U; + + return 999U; +} + + +/// +/// +/// +/// +/// +/// +/// +bool Trellis::fixCode12(uint8_t* points, uint32_t failPos, uint8_t* payload) const +{ + for (unsigned j = 0U; j < 20U; j++) { + uint32_t bestPos = 0U; + uint32_t bestVal = 0U; + + for (uint32_t i = 0U; i < 4U; i++) { + points[failPos] = i; + + uint8_t dibits[49U]; + uint32_t pos = checkCode12(points, dibits); + if (pos == 999U) { + dibitsToBits(dibits, payload); + return true; + } + + if (pos > bestPos) { + bestPos = pos; + bestVal = i; + } + } + + points[failPos] = bestVal; + failPos = bestPos; + } + + return false; +} + +/// +/// +/// +/// +/// +/// +uint32_t Trellis::checkCode12(const uint8_t* points, uint8_t* dibits) const +{ + uint8_t state = 0U; + + for (uint32_t i = 0U; i < 49U; i++) { + dibits[i] = 5U; + + for (uint32_t j = 0U; j < 4U; j++) { + if (points[i] == ENCODE_TABLE_12[state * 4U + j]) { + dibits[i] = j; + break; + } + } + + if (dibits[i] == 5U) + return i; + + state = dibits[i]; + } + + if (dibits[48U] != 0U) + return 48U; + + return 999U; +} diff --git a/p25/edac/Trellis.h b/p25/edac/Trellis.h new file mode 100644 index 00000000..a55edf7d --- /dev/null +++ b/p25/edac/Trellis.h @@ -0,0 +1,87 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2018 by Jonathan Naylor, G4KLX +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; version 2 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#if !defined(__P25_EDAC__TRELLIS_H__) +#define __P25_EDAC__TRELLIS_H__ + +#include "Defines.h" + +namespace p25 +{ + namespace edac + { + // --------------------------------------------------------------------------- + // Class Declaration + // Implements 1/2 rate and 3/4 rate Trellis for P25. + // --------------------------------------------------------------------------- + + class HOST_SW_API Trellis { + public: + /// Initializes a new instance of the Trellis class. + Trellis(); + /// Finalizes a instance of the Trellis class. + ~Trellis(); + + /// Decodes 3/4 rate Trellis. + bool decode34(const uint8_t* data, uint8_t* payload); + /// Encodes 3/4 rate Trellis. + void encode34(const uint8_t* payload, uint8_t* data); + + /// Decodes 1/2 rate Trellis. + bool decode12(const uint8_t* data, uint8_t* payload); + /// Encodes 1/2 rate Trellis. + void encode12(const uint8_t* payload, uint8_t* data); + + private: + /// + void deinterleave(const uint8_t* in, int8_t* dibits) const; + /// + void interleave(const int8_t* dibits, uint8_t* out) const; + /// + void dibitsToPoints(const int8_t* dibits, uint8_t* points) const; + /// + void pointsToDibits(const uint8_t* points, int8_t* dibits) const; + /// + void bitsToTribits(const uint8_t* payload, uint8_t* tribits) const; + /// + void bitsToDibits(const uint8_t* payload, uint8_t* dibits) const; + /// + void tribitsToBits(const uint8_t* tribits, uint8_t* payload) const; + /// + void dibitsToBits(const uint8_t* dibits, uint8_t* payload) const; + + /// + bool fixCode34(uint8_t* points, uint32_t failPos, uint8_t* payload) const; + /// + uint32_t checkCode34(const uint8_t* points, uint8_t* tribits) const; + + /// + bool fixCode12(uint8_t* points, uint32_t failPos, uint8_t* payload) const; + /// + uint32_t checkCode12(const uint8_t* points, uint8_t* dibits) const; + }; + } // namespace edac +} // namespace p25 + +#endif // __P25_EDAC__TRELLIS_H__ diff --git a/p25/lc/LC.cpp b/p25/lc/LC.cpp new file mode 100644 index 00000000..f8b438fc --- /dev/null +++ b/p25/lc/LC.cpp @@ -0,0 +1,768 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016,2017 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/lc/LC.h" +#include "p25/P25Utils.h" +#include "edac/CRC.h" +#include "edac/Golay24128.h" +#include "edac/Hamming.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25::lc; +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the LC class. +/// +LC::LC() : + m_protect(false), + m_lco(LC_GROUP), + m_mfId(P25_MFG_STANDARD), + m_srcId(0U), + m_dstId(0U), + m_emergency(false), + m_encrypted(false), + m_priority(4U), + m_group(true), + m_algId(P25_ALGO_UNENCRYPT), + m_kId(0U), + m_rs(), + m_encryptOverride(false), + m_tsbkVendorSkip(false), + m_callTimer(0U), + m_mi(NULL), + m_siteData() +{ + m_mi = new uint8_t[P25_MI_LENGTH_BYTES]; + + reset(); +} + +/// +/// Finalizes a instance of LC class. +/// +LC::~LC() +{ + delete[] m_mi; +} + +/// +/// Equals operator. +/// +/// +/// +LC& LC::operator=(const LC& data) +{ + if (this != &data) { + m_protect = data.m_protect; + m_mfId = data.m_mfId; + + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + + m_grpVchNo = data.m_grpVchNo; + + m_emergency = data.m_emergency; + m_encrypted = data.m_encrypted; + m_priority = data.m_priority; + + m_group = data.m_group; + + m_callTimer = data.m_callTimer; + + m_algId = data.m_algId; + if (m_algId != P25_ALGO_UNENCRYPT) { + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + ::memcpy(m_mi, data.m_mi, P25_MI_LENGTH_BYTES); + + m_kId = data.m_kId; + if (!m_encrypted) { + m_encryptOverride = true; + m_encrypted = true; + } + } + else { + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + + m_kId = 0x0000U; + if (m_encrypted) { + m_encryptOverride = true; + m_encrypted = false; + } + } + } + + return *this; +} + +/// +/// Decode a header data unit. +/// +/// +/// True, if HDU was decoded, otherwise false. +bool LC::decodeHDU(const uint8_t* data) +{ + assert(data != NULL); + + // deinterleave + uint8_t rs[P25_HDU_LENGTH_BYTES + 1U]; + uint8_t raw[P25_HDU_LENGTH_BYTES + 1U]; + P25Utils::decode(data, raw, 114U, 780U); + + // decode Golay (18,6,8) FEC + decodeHDUGolay(raw, rs); + + // Utils::dump(2U, "HDU RS", rs, P25_HDU_LENGTH_BYTES); + + // decode RS (36,20,17) FEC + try { + bool ret = m_rs.decode362017(rs); + if (!ret) + return false; + } + catch (...) { + Utils::dump(2U, "P25, RS crashed with input data", rs, P25_HDU_LENGTH_BYTES); + return false; + } + + // Utils::dump(2U, "HDU", rs, P25_HDU_LENGTH_BYTES); + + m_mfId = rs[9U]; // Mfg Id. + m_algId = rs[10U]; // Algorithm ID + if (m_algId != P25_ALGO_UNENCRYPT) { + m_mi = new uint8_t[P25_MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + ::memcpy(m_mi, rs, P25_MI_LENGTH_BYTES); // Message Indicator + + m_kId = (rs[11U] << 8) + rs[12U]; // Key ID + if (!m_encrypted) { + m_encryptOverride = true; + m_encrypted = true; + } + } + else { + m_mi = new uint8_t[P25_MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + + m_kId = 0x0000U; + if (m_encrypted) { + m_encryptOverride = true; + m_encrypted = false; + } + } + + ulong64_t rsValue = 0U; + + // combine bytes into rs value + rsValue = rs[12U]; + rsValue = (rsValue << 8) + rs[13U]; + rsValue = (rsValue << 8) + rs[14U]; + + m_dstId = (uint32_t)(rsValue & 0xFFFFU); // Talkgroup Address + + return true; +} + +/// +/// Encode a header data unit. +/// +/// +void LC::encodeHDU(uint8_t * data) +{ + assert(data != NULL); + assert(m_mi != NULL); + + uint8_t rs[P25_HDU_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_HDU_LENGTH_BYTES); + + for (uint32_t i = 0; i < P25_MI_LENGTH_BYTES; i++) + rs[i] = m_mi[i]; // Message Indicator + + rs[9U] = m_mfId; // Mfg Id. + rs[10U] = m_algId; // Algorithm ID + rs[11U] = (m_kId >> 8) & 0xFFU; // Key ID + rs[12U] = (m_kId >> 0) & 0xFFU; // ... + rs[13U] = (m_dstId >> 8) & 0xFFU; // Talkgroup Address + rs[14U] = (m_dstId >> 0) & 0xFFU; // ... + + // Utils::dump(2U, "HDU", rs, P25_HDU_LENGTH_BYTES); + + // encode RS (36,20,17) FEC + m_rs.encode362017(rs); + + // Utils::dump(2U, "HDU RS", rs, P25_HDU_LENGTH_BYTES); + + uint8_t raw[P25_HDU_LENGTH_BYTES + 1U]; + ::memset(raw, 0x00U, P25_HDU_LENGTH_BYTES + 1U); + + // encode Golay (18,6,8) FEC + encodeHDUGolay(raw, rs); + + // interleave + P25Utils::encode(raw, data, 114U, 780U); + + // Utils::dump(2U, "HDU Interleave", data, P25_HDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); +} + +/// +/// Decode a logical link data unit 1. +/// +/// +/// True, if LDU1 was decoded, otherwise false. +bool LC::decodeLDU1(const uint8_t * data) +{ + assert(data != NULL); + + uint8_t rs[P25_LDU_LC_LENGTH_BYTES + 1U]; + + // deinterleave and decode Hamming (10,6,3) for LC data + uint8_t raw[5U]; + P25Utils::decode(data, raw, 410U, 452U); + decodeLDUHamming(raw, rs + 0U); + + P25Utils::decode(data, raw, 600U, 640U); + decodeLDUHamming(raw, rs + 3U); + + P25Utils::decode(data, raw, 788U, 830U); + decodeLDUHamming(raw, rs + 6U); + + P25Utils::decode(data, raw, 978U, 1020U); + decodeLDUHamming(raw, rs + 9U); + + P25Utils::decode(data, raw, 1168U, 1208U); + decodeLDUHamming(raw, rs + 12U); + + P25Utils::decode(data, raw, 1356U, 1398U); + decodeLDUHamming(raw, rs + 15U); + + // Utils::dump(2U, "LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES); + + // decode RS (24,12,13) FEC + try { + bool ret = m_rs.decode241213(rs); + if (!ret) + return false; + } + catch (...) { + Utils::dump(2U, "P25, RS crashed with input data", rs, P25_LDU_LC_LENGTH_BYTES); + return false; + } + + // Utils::dump(2U, "LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES); + + return decodeLC(rs); +} + +/// +/// Encode a logical link data unit 1. +/// +/// +void LC::encodeLDU1(uint8_t * data) +{ + assert(data != NULL); + + uint8_t rs[P25_LDU_LC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_LENGTH_BYTES); + + encodeLC(rs); + + // Utils::dump(2U, "LDU1 LC", rs, P25_LDU_LC_LENGTH_BYTES); + + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); + + // Utils::dump(2U, "LDU1 RS", rs, P25_LDU_LC_LENGTH_BYTES); + + // encode Hamming (10,6,3) FEC and interleave for LC data + uint8_t raw[5U]; + encodeLDUHamming(raw, rs + 0U); + P25Utils::encode(raw, data, 410U, 452U); + + encodeLDUHamming(raw, rs + 3U); + P25Utils::encode(raw, data, 600U, 640U); + + encodeLDUHamming(raw, rs + 6U); + P25Utils::encode(raw, data, 788U, 830U); + + encodeLDUHamming(raw, rs + 9U); + P25Utils::encode(raw, data, 978U, 1020U); + + encodeLDUHamming(raw, rs + 12U); + P25Utils::encode(raw, data, 1168U, 1208U); + + encodeLDUHamming(raw, rs + 15U); + P25Utils::encode(raw, data, 1356U, 1398U); + + // Utils::dump(2U, "LDU1 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); +} + +/// +/// Decode a logical link data unit 2. +/// +/// +/// True, if LDU2 was decoded, otherwise false. +bool LC::decodeLDU2(const uint8_t * data) +{ + assert(data != NULL); + + uint8_t rs[P25_LDU_LC_LENGTH_BYTES + 1U]; + + // deinterleave and decode Hamming (10,6,3) for LC data + uint8_t raw[5U]; + P25Utils::decode(data, raw, 410U, 452U); + decodeLDUHamming(raw, rs + 0U); + + P25Utils::decode(data, raw, 600U, 640U); + decodeLDUHamming(raw, rs + 3U); + + P25Utils::decode(data, raw, 788U, 830U); + decodeLDUHamming(raw, rs + 6U); + + P25Utils::decode(data, raw, 978U, 1020U); + decodeLDUHamming(raw, rs + 9U); + + P25Utils::decode(data, raw, 1168U, 1208U); + decodeLDUHamming(raw, rs + 12U); + + P25Utils::decode(data, raw, 1356U, 1398U); + decodeLDUHamming(raw, rs + 15U); + + // Utils::dump(2U, "LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES); + + // decode RS (24,16,9) FEC + try { + bool ret = m_rs.decode24169(rs); + if (!ret) + return false; + } + catch (...) { + Utils::dump(2U, "P25, RS crashed with input data", rs, P25_LDU_LC_LENGTH_BYTES); + return false; + } + + // Utils::dump(2U, "LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES); + + m_algId = rs[9U]; // Algorithm ID + if (m_algId != P25_ALGO_UNENCRYPT) { + m_mi = new uint8_t[P25_MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + ::memcpy(m_mi, rs, P25_MI_LENGTH_BYTES); // Message Indicator + + m_kId = (rs[10U] << 8) + rs[11U]; // Key ID + if (!m_encrypted) { + m_encryptOverride = true; + m_encrypted = true; + } + } + else { + m_mi = new uint8_t[P25_MI_LENGTH_BYTES]; + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); + + m_kId = 0x0000U; + if (m_encrypted) { + m_encryptOverride = true; + m_encrypted = false; + } + } + + return true; +} + +/// +/// Encode a logical link data unit 2. +/// +/// +void LC::encodeLDU2(uint8_t * data) +{ + assert(data != NULL); + assert(m_mi != NULL); + + uint8_t rs[P25_LDU_LC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_LDU_LC_LENGTH_BYTES); + + for (uint32_t i = 0; i < P25_MI_LENGTH_BYTES; i++) + rs[i] = m_mi[i]; // Message Indicator + + rs[9U] = m_algId; // Algorithm ID + rs[10U] = (m_kId >> 8) & 0xFFU; // Key ID + rs[11U] = (m_kId >> 0) & 0xFFU; // ... + + // Utils::dump(2U, "LDU2 LC", rs, P25_LDU_LC_LENGTH_BYTES); + + // encode RS (24,16,9) FEC + m_rs.encode24169(rs); + + // Utils::dump(2U, "LDU2 RS", rs, P25_LDU_LC_LENGTH_BYTES); + + // encode Hamming (10,6,3) FEC and interleave for LC data + uint8_t raw[5U]; + encodeLDUHamming(raw, rs + 0U); + P25Utils::encode(raw, data, 410U, 452U); + + encodeLDUHamming(raw, rs + 3U); + P25Utils::encode(raw, data, 600U, 640U); + + encodeLDUHamming(raw, rs + 6U); + P25Utils::encode(raw, data, 788U, 830U); + + encodeLDUHamming(raw, rs + 9U); + P25Utils::encode(raw, data, 978U, 1020U); + + encodeLDUHamming(raw, rs + 12U); + P25Utils::encode(raw, data, 1168U, 1208U); + + encodeLDUHamming(raw, rs + 15U); + P25Utils::encode(raw, data, 1356U, 1398U); + + // Utils::dump(2U, "LDU2 Interleave", data, P25_LDU_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); +} + +/// +/// Helper to reset data values to defaults. +/// +void LC::reset() +{ + m_encryptOverride = false; + m_tsbkVendorSkip = false; + + m_protect = false; + m_lco = LC_GROUP; + m_mfId = P25_MFG_STANDARD; + + m_srcId = 0U; + m_dstId = 0U; + + m_callTimer = 0U; + + m_grpVchNo = m_siteData.channelNo(); + + /* Service Options */ + m_emergency = false; + m_encrypted = false; + m_priority = 4U; + m_group = true; + + /* HDU/LDU2 data */ + m_algId = P25_ALGO_UNENCRYPT; + m_kId = 0x0000U; + + ::memset(m_mi, 0x00U, P25_MI_LENGTH_BYTES); +} + +/** Local Site data */ +/// Sets local configured site data. +/// +void LC::setSiteData(SiteData siteData) +{ + m_siteData = siteData; +} + +/** Encryption data */ +/// Sets the encryption message indicator. +/// +void LC::setMI(const uint8_t* mi) +{ + assert(mi != NULL); + + ::memcpy(m_mi, mi, P25_MI_LENGTH_BYTES); +} + +/// Gets the encryption message indicator. +/// +void LC::getMI(uint8_t* mi) const +{ + assert(mi != NULL); + + ::memcpy(mi, m_mi, P25_MI_LENGTH_BYTES); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Decode link control. +/// +/// +/// +bool LC::decodeLC(const uint8_t * rs) +{ + ulong64_t rsValue = 0U; + + // combine bytes into rs value + rsValue = rs[1U]; + rsValue = (rsValue << 8) + rs[2U]; + rsValue = (rsValue << 8) + rs[3U]; + rsValue = (rsValue << 8) + rs[4U]; + rsValue = (rsValue << 8) + rs[5U]; + rsValue = (rsValue << 8) + rs[6U]; + rsValue = (rsValue << 8) + rs[7U]; + rsValue = (rsValue << 8) + rs[8U]; + + m_protect = (rs[0U] & 0x80U) == 0x80U; // Protect Flag + m_lco = rs[0U] & 0x3FU; // LCO + + // standard P25 reference opcodes + switch (m_lco) { + case LC_GROUP: + m_mfId = rs[1U]; // Mfg Id. + m_group = true; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + if (!m_encryptOverride) { + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + } + m_priority = (rs[2U] & 0x07U); // Priority + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Talkgroup Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; + case LC_PRIVATE: + m_mfId = rs[1U]; // Mfg Id. + m_group = false; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + if (!m_encryptOverride) { + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + } + m_priority = (rs[2U] & 0x07U); // Priority + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; + case LC_TEL_INT_VCH_USER: + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + if (!m_encryptOverride) { + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + } + m_priority = (rs[2U] & 0x07U); // Priority + m_callTimer = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Call Timer + if (m_srcId == 0U) { + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source/Target Address + } + break; + default: + LogError(LOG_P25, "unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + return false; + } + + // sanity check priority (per TIA-102.AABC-B) it should never be 0, if its 0, default to 4 + if (m_priority == 0) { + m_priority = 4U; + } + + return true; +} + +/// +/// Encode link control. +/// +/// +void LC::encodeLC(uint8_t * rs) +{ + ulong64_t rsValue = 0U; + rs[0U] = m_lco; // LCO + + // standard P25 reference opcodes + switch (m_lco) { + case LC_GROUP: + rsValue = m_mfId; + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; + case LC_GROUP_UPDT: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = m_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 << 12) + m_grpVchNo; // Group B - Channel Number + rsValue = (rsValue << 16) + m_dstId; // Group B - Talkgroup Address + break; + case LC_PRIVATE: + rsValue = m_mfId; + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Target Radio Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; + case LC_TEL_INT_VCH_USER: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 16) + m_callTimer; // Call Timer + rsValue = (rsValue << 24) + m_srcId; // Source/Target Radio Address + break; + default: + LogError(LOG_P25, "unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + break; + } + + // split rs value into bytes + rs[1U] = (uint8_t)((rsValue >> 56) & 0xFFU); + rs[2U] = (uint8_t)((rsValue >> 48) & 0xFFU); + rs[3U] = (uint8_t)((rsValue >> 40) & 0xFFU); + rs[4U] = (uint8_t)((rsValue >> 32) & 0xFFU); + rs[5U] = (uint8_t)((rsValue >> 24) & 0xFFU); + rs[6U] = (uint8_t)((rsValue >> 16) & 0xFFU); + rs[7U] = (uint8_t)((rsValue >> 8) & 0xFFU); + rs[8U] = (uint8_t)((rsValue >> 0) & 0xFFU); +} + +/// +/// Decode LDU hamming FEC. +/// +/// +/// +void LC::decodeLDUHamming(const uint8_t * data, uint8_t * raw) +{ + uint32_t n = 0U; + uint32_t m = 0U; + for (uint32_t i = 0U; i < 4U; i++) { + bool hamming[10U]; + + for (uint32_t j = 0U; j < 10U; j++) { + hamming[j] = READ_BIT(data, n); + n++; + } + + edac::Hamming::decode1063(hamming); + + for (uint32_t j = 0U; j < 6U; j++) { + WRITE_BIT(raw, m, hamming[j]); + m++; + } + } +} + +/// +/// Encode LDU hamming FEC. +/// +/// +/// +void LC::encodeLDUHamming(uint8_t * data, const uint8_t * raw) +{ + uint32_t n = 0U; + uint32_t m = 0U; + for (uint32_t i = 0U; i < 4U; i++) { + bool hamming[10U]; + + for (uint32_t j = 0U; j < 6U; j++) { + hamming[j] = READ_BIT(raw, m); + m++; + } + + edac::Hamming::encode1063(hamming); + + for (uint32_t j = 0U; j < 10U; j++) { + WRITE_BIT(data, n, hamming[j]); + n++; + } + } +} + +/// +/// Decode HDU Golay FEC. +/// +/// +/// +void LC::decodeHDUGolay(const uint8_t * data, uint8_t * raw) +{ + // shortened Golay (18,6,8) decode + uint32_t n = 0U; + uint32_t m = 0U; + for (uint32_t i = 0U; i < 36U; i++) { + bool golay[18U]; + for (uint32_t j = 0U; j < 18U; j++) { + golay[j] = READ_BIT(data, n); + n++; + } + + uint32_t g0 = 0U; + for (uint32_t j = 0U; j < 18U; j++) + g0 = (g0 << 1) | (golay[j] ? 0x01U : 0x00U); + uint32_t c0data = edac::Golay24128::decode24128(g0); + for (int j = 5; j >= 0; j--) { + golay[j] = (c0data & 0x01U) == 0x01U; + c0data >>= 1; + } + + for (uint32_t j = 0U; j < 6U; j++) { + WRITE_BIT(raw, m, golay[j]); + m++; + } + } +} + +/// +/// Encode HDU Golay FEC. +/// +/// +/// +void LC::encodeHDUGolay(uint8_t * data, const uint8_t * raw) +{ + // shortened Golay (18,6,8) encode + uint32_t n = 0U; + uint32_t m = 0U; + for (uint32_t i = 0U; i < 36U; i++) { + bool golay[18U]; + for (uint32_t j = 0U; j < 6U; j++) { + golay[j] = READ_BIT(raw, m); + m++; + } + + uint32_t c0data = 0U; + for (uint32_t j = 0U; j < 6U; j++) + c0data = (c0data << 1) | (golay[j] ? 0x01U : 0x00U); + uint32_t g0 = edac::Golay24128::encode24128(c0data); + for (int j = 17; j >= 0; j--) { + golay[j] = (g0 & 0x01U) == 0x01U; + g0 >>= 1; + } + + for (uint32_t j = 0U; j < 18U; j++) { + WRITE_BIT(data, n, golay[j]); + n++; + } + } +} diff --git a/p25/lc/LC.h b/p25/lc/LC.h new file mode 100644 index 00000000..8e7f43e2 --- /dev/null +++ b/p25/lc/LC.h @@ -0,0 +1,162 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_LC__LC_H__) +#define __P25_LC__LC_H__ + +#include "Defines.h" +#include "p25/lc/TSBK.h" +#include "p25/lc/TDULC.h" +#include "p25/SiteData.h" +#include "edac/RS634717.h" +#include "lookups/IdenTableLookup.h" + +#include + +namespace p25 +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API TSBK; + class HOST_SW_API TDULC; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents link control data for HDU, LDU1 and 2 packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API LC { + public: + /// Initializes a new instance of the LC class. + LC(); + /// Finalizes a instance of the LC class. + ~LC(); + + /// Equals operator. + LC& operator=(const LC& data); + + /// Decode a header data unit. + bool decodeHDU(const uint8_t* data); + /// Encode a header data unit. + void encodeHDU(uint8_t* data); + + /// Decode a logical link data unit 1. + bool decodeLDU1(const uint8_t* data); + /// Encode a logical link data unit 1. + void encodeLDU1(uint8_t* data); + + /// Decode a logical link data unit 2. + bool decodeLDU2(const uint8_t* data); + /// Encode a logical link data unit 2. + void encodeLDU2(uint8_t* data); + + /// Helper to reset data values to defaults. + void reset(); + + /** Local Site data */ + /// Sets local configured site data. + void setSiteData(SiteData siteData); + + /** Encryption data */ + /// Sets the encryption message indicator. + void setMI(const uint8_t* mi); + /// Gets the encryption message indicator. + void getMI(uint8_t* mi) const; + + public: + /** Common Data */ + /// Flag indicating the link control data is protected. + __PROPERTY(bool, protect, Protect); + /// Link control opcode. + __PROPERTY(uint8_t, lco, LCO); + /// Manufacturer ID. + __PROPERTY(uint8_t, mfId, MFId); + + /// Source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + /// Voice channel number. + __PROPERTY(uint32_t, grpVchNo, GrpVchNo); + + /** Service Options */ + /// Flag indicating the emergency bits are set. + __PROPERTY(bool, emergency, Emergency); + /// Flag indicating that encryption is enabled. + __PROPERTY(bool, encrypted, Encrypted); + /// Priority level for the traffic. + __PROPERTY(uint8_t, priority, Priority); + /// Flag indicating a group/talkgroup operation. + __PROPERTY(bool, group, Group); + + /** Encryption data */ + /// Encryption algorithm ID. + __PROPERTY(uint8_t, algId, AlgId); + /// Encryption key ID. + __PROPERTY(uint32_t, kId, KId); + + private: + friend class TSBK; + friend class TDULC; + edac::RS634717 m_rs; + bool m_encryptOverride; + bool m_tsbkVendorSkip; + + uint32_t m_callTimer; + + /** Encryption data */ + uint8_t* m_mi; + + /** Local Site data */ + SiteData m_siteData; + + /// Decode link control. + bool decodeLC(const uint8_t* rs); + /// Encode link control. + void encodeLC(uint8_t* rs); + + /// Decode LDU hamming FEC. + void decodeLDUHamming(const uint8_t* raw, uint8_t* data); + /// Encode LDU hamming FEC. + void encodeLDUHamming(uint8_t* data, const uint8_t* raw); + + /// Decode HDU Golay FEC. + void decodeHDUGolay(const uint8_t* raw, uint8_t* data); + /// Encode HDU Golay FEC. + void encodeHDUGolay(uint8_t* data, const uint8_t* raw); + }; + } // namespace lc +} // namespace p25 + +#endif // __P25_LC__LC_H__ diff --git a/p25/lc/TDULC.cpp b/p25/lc/TDULC.cpp new file mode 100644 index 00000000..a89215d0 --- /dev/null +++ b/p25/lc/TDULC.cpp @@ -0,0 +1,413 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/lc/TDULC.h" +#include "p25/P25Utils.h" +#include "edac/CRC.h" +#include "edac/Golay24128.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25::lc; +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the TDULC class. +/// +TDULC::TDULC() : + m_protect(false), + m_lco(LC_GROUP), + m_mfId(P25_MFG_STANDARD), + m_srcId(0U), + m_dstId(0U), + m_emergency(false), + m_encrypted(false), + m_priority(4U), + m_group(true), + m_rs(), + m_callTimer(0U), + m_siteData(), + m_siteNetActive(false) +{ + m_siteIdenEntry = lookups::IdenTable(); + + reset(); +} + +/// +/// Finalizes a instance of TDULC class. +/// +TDULC::~TDULC() +{ + /* stub */ +} + +/// +/// Decode a terminator data unit w/ link control. +/// +/// +/// True, if TDULC was decoded, otherwise false. +bool TDULC::decode(const uint8_t* data) +{ + assert(data != NULL); + + // deinterleave + uint8_t rs[P25_TDULC_LENGTH_BYTES + 1U]; + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + P25Utils::decode(data, raw, 114U, 410U); + + // decode Golay (24,12,8) FEC + edac::Golay24128::decode24128(rs, raw, P25_TDULC_LENGTH_BYTES); + + // Utils::dump(2U, "TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + + // decode RS (24,12,13) FEC + try { + bool ret = m_rs.decode241213(rs); + if (!ret) + return false; + } + catch (...) { + Utils::dump(2U, "P25, RS crashed with input data", rs, P25_TDULC_LENGTH_BYTES); + return false; + } + + // Utils::dump(2U, "TDULC", rs, P25_TDULC_LENGTH_BYTES); + + return decodeLC(rs); +} + +/// +/// Encode a terminator data unit w/ link control. +/// +/// +/// True, if TDULC was decoded, otherwise false. +void TDULC::encode(uint8_t * data) +{ + assert(data != NULL); + + uint8_t rs[P25_TDULC_LENGTH_BYTES]; + ::memset(rs, 0x00U, P25_TDULC_LENGTH_BYTES); + + encodeLC(rs); + + // Utils::dump(2U, "TDULC", rs, P25_TDULC_LENGTH_BYTES); + + // encode RS (24,12,13) FEC + m_rs.encode241213(rs); + + // Utils::dump(2U, "TDULC RS", rs, P25_TDULC_LENGTH_BYTES); + + uint8_t raw[P25_TDULC_FEC_LENGTH_BYTES + 1U]; + ::memset(raw, 0x00U, P25_TDULC_FEC_LENGTH_BYTES + 1U); + + // encode Golay (24,12,8) FEC + edac::Golay24128::encode24128(raw, rs, P25_TDULC_LENGTH_BYTES); + + // interleave + P25Utils::encode(raw, data, 114U, 410U); + + // Utils::dump(2U, "TDULC Interleave", data, P25_TDULC_FRAME_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); +} + +/// +/// Helper to reset data values to defaults. +/// +void TDULC::reset() +{ + m_protect = false; + m_lco = LC_GROUP; + m_mfId = P25_MFG_STANDARD; + + m_srcId = 0U; + m_dstId = 0U; + + m_callTimer = 0U; + + m_grpVchNo = m_siteData.channelNo(); + + m_adjCFVA = P25_CFVA_CONV | P25_CFVA_FAILURE; + m_adjRfssId = 0U; + m_adjSiteId = 0U; + m_adjChannelId = 0U; + m_adjChannelNo = 0U; + + /* Service Options */ + m_emergency = false; + m_encrypted = false; + m_priority = 4U; + m_group = true; +} + +/** Local Site data */ +/// Sets local configured site data. +/// +void TDULC::setSiteData(SiteData siteData) +{ + m_siteData = siteData; +} + +/// +/// +void TDULC::setIdenTable(lookups::IdenTable entry) +{ + m_siteIdenEntry = entry; +} + +/// Sets a flag indicating whether or not networking is active. +/// +void TDULC::setNetActive(bool netActive) +{ + m_siteNetActive = netActive; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- +/// +/// Decode link control. +/// +/// +/// +bool TDULC::decodeLC(const uint8_t* rs) +{ + ulong64_t rsValue = 0U; + + // combine bytes into rs value + rsValue = rs[1U]; + rsValue = (rsValue << 8) + rs[2U]; + rsValue = (rsValue << 8) + rs[3U]; + rsValue = (rsValue << 8) + rs[4U]; + rsValue = (rsValue << 8) + rs[5U]; + rsValue = (rsValue << 8) + rs[6U]; + rsValue = (rsValue << 8) + rs[7U]; + rsValue = (rsValue << 8) + rs[8U]; + + m_protect = (rs[0U] & 0x80U) == 0x80U; // Protect Flag + m_lco = rs[0U] & 0x3FU; // LCO + + // standard P25 reference opcodes + switch (m_lco) { + case LC_GROUP: + m_mfId = rs[1U]; // Mfg Id. + m_group = true; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + m_priority = (rs[2U] & 0x07U); // Priority + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Talkgroup Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; + case LC_PRIVATE: + m_mfId = rs[1U]; // Mfg Id. + m_group = false; + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + m_priority = (rs[2U] & 0x07U); // Priority + m_dstId = (uint32_t)((rsValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source Radio Address + break; + case LC_TEL_INT_VCH_USER: + m_emergency = (rs[2U] & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (rs[2U] & 0x40U) == 0x40U; // Encryption Flag + m_priority = (rs[2U] & 0x07U); // Priority + m_callTimer = (uint32_t)((rsValue >> 24) & 0xFFFFU); // Call Timer + if (m_srcId == 0U) { + m_srcId = (uint32_t)(rsValue & 0xFFFFFFU); // Source/Target Address + } + break; + default: + LogError(LOG_P25, "unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + return false; + } + + // sanity check priority (per TIA-102.AABC-B) it should never be 0, if its 0, default to 4 + if (m_priority == 0) { + m_priority = 4U; + } + + return true; +} + +/// +/// Encode link control. +/// +/// +void TDULC::encodeLC(uint8_t* rs) +{ + const uint32_t services = (m_siteNetActive) ? P25_SYS_SRV_NET_ACTIVE : 0U | + P25_SYS_SRV_GROUP_DATA | P25_SYS_SRV_GROUP_VOICE | P25_SYS_SRV_IND_DATA | P25_SYS_SRV_IND_VOICE; + + ulong64_t rsValue = 0U; + rs[0U] = m_lco; // LCO + + // standard P25 reference opcodes + switch (m_lco) { + case LC_GROUP: + rsValue = m_mfId; + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Talkgroup Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; + case LC_GROUP_UPDT: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = m_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 << 12) + m_grpVchNo; // Group B - Channel Number + rsValue = (rsValue << 16) + m_dstId; // Group B - Talkgroup Address + break; + case LC_PRIVATE: + rsValue = m_mfId; + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 24) + m_dstId; // Target Radio Address + rsValue = (rsValue << 24) + m_srcId; // Source Radio Address + break; + case LC_TEL_INT_VCH_USER: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = (rsValue << 8) + + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + rsValue = (rsValue << 16) + m_callTimer; // Call Timer + rsValue = (rsValue << 24) + m_srcId; // Source/Target Radio Address + break; + case LC_CALL_TERM: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = 0U; + rsValue = (rsValue << 24) + P25_WUID_SYS; // System Radio Address + break; + case LC_IDEN_UP: + rs[0U] |= 0x40U; // Implicit Operation + { + if ((m_siteIdenEntry.chBandwidthKhz() != 0.0F) && (m_siteIdenEntry.chSpaceKhz() != 0.0F) && + (m_siteIdenEntry.txOffsetMhz() != 0U) && (m_siteIdenEntry.baseFrequency() != 0U)) { + uint32_t calcSpace = (uint32_t)(m_siteIdenEntry.chSpaceKhz() / 0.125); + uint32_t calcTxOffset = (uint32_t)((abs(m_siteIdenEntry.txOffsetMhz()) / m_siteIdenEntry.chSpaceKhz()) * 1000); + if (m_siteIdenEntry.txOffsetMhz() > 0.0F) + calcTxOffset |= 0x2000U; // this sets a positive offset ... + + uint32_t calcBaseFreq = (uint32_t)(m_siteIdenEntry.baseFrequency() / 5); + uint8_t chanBw = (m_siteIdenEntry.chBandwidthKhz() >= 12.5F) ? P25_IDEN_UP_VU_BW_125K : P25_IDEN_UP_VU_BW_625K; + + rsValue = m_siteIdenEntry.channelId(); // Channel ID + rsValue = (rsValue << 4) + chanBw; // Channel Bandwidth + rsValue = (rsValue << 14) + calcTxOffset; // Transmit Offset + rsValue = (rsValue << 10) + calcSpace; // Channel Spacing + rsValue = (rsValue << 32) + calcBaseFreq; // Base Frequency + } + else { + LogError(LOG_P25, "invalid values for LC_IDEN_UP, baseFrequency = %uHz, txOffsetMhz = %uMHz, chBandwidthKhz = %fKHz, chSpaceKhz = %fKHz", + m_siteIdenEntry.baseFrequency(), m_siteIdenEntry.txOffsetMhz(), m_siteIdenEntry.chBandwidthKhz(), + m_siteIdenEntry.chSpaceKhz()); + return; // blatently ignore creating this TSBK + } + } + break; + case LC_SYS_SRV_BCAST: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = 0U; // + rsValue = (rsValue << 16) + services; // System Services Available + rsValue = (rsValue << 24) + services; // System Services Supported + break; + case LC_ADJ_STS_BCAST: + rs[0U] |= 0x40U; // Implicit Operation + { + if ((m_adjRfssId != 0U) && (m_adjSiteId != 0U) && (m_adjChannelNo != 0U)) { + if (m_adjSysId == 0U) { + m_adjSysId = m_siteData.sysId(); + } + + rsValue = m_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 + rsValue = (rsValue << 4) + m_adjChannelId; // Channel ID + rsValue = (rsValue << 12) + m_adjChannelNo; // Channel Number + rsValue = (rsValue << 8) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + } + else { + LogError(LOG_P25, "invalid values for LC_ADJ_STS_BCAST, tsbkAdjSiteRFSSId = $%02X, tsbkAdjSiteId = $%02X, tsbkAdjSiteChannel = $%02X", + m_adjRfssId, m_adjSiteId, m_adjChannelNo); + return; // blatently ignore creating this TSBK + } + } + break; + case LC_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) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + break; + case LC_NET_STS_BCAST: + rs[0U] |= 0x40U; // Implicit Operation + rsValue = 0U; // + 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) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + break; + default: + LogError(LOG_P25, "unknown LC value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + break; + } + + // split rs value into bytes + rs[1U] = (uint8_t)((rsValue >> 56) & 0xFFU); + rs[2U] = (uint8_t)((rsValue >> 48) & 0xFFU); + rs[3U] = (uint8_t)((rsValue >> 40) & 0xFFU); + rs[4U] = (uint8_t)((rsValue >> 32) & 0xFFU); + rs[5U] = (uint8_t)((rsValue >> 24) & 0xFFU); + rs[6U] = (uint8_t)((rsValue >> 16) & 0xFFU); + rs[7U] = (uint8_t)((rsValue >> 8) & 0xFFU); + rs[8U] = (uint8_t)((rsValue >> 0) & 0xFFU); +} diff --git a/p25/lc/TDULC.h b/p25/lc/TDULC.h new file mode 100644 index 00000000..de0335ea --- /dev/null +++ b/p25/lc/TDULC.h @@ -0,0 +1,143 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_LC__TDULC_H__) +#define __P25_LC__TDULC_H__ + +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/lc/LC.h" +#include "p25/lc/TSBK.h" +#include "p25/SiteData.h" +#include "edac/RS634717.h" +#include "lookups/IdenTableLookup.h" + +#include + +namespace p25 +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API LC; + class HOST_SW_API TSBK; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents link control data for TDULC packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API TDULC { + public: + /// Initializes a new instance of the TDULC class. + TDULC(); + /// Finalizes a instance of the TDULC class. + ~TDULC(); + + /// Decode a trunking signalling block. + bool decode(const uint8_t* data); + /// Encode a trunking signalling block. + void encode(uint8_t* data); + + /// Helper to reset data values to defaults. + void reset(); + + /** Local Site data */ + /// Sets local configured site data. + void setSiteData(SiteData siteData); + /// + void setIdenTable(lookups::IdenTable entry); + /// Sets a flag indicating whether or not networking is active. + void setNetActive(bool netActive); + + public: + /** Common Data */ + /// Flag indicating the link control data is protected. + __PROPERTY(bool, protect, Protect); + /// Link control opcode. + __PROPERTY(uint8_t, lco, LCO); + /// Manufacturer ID. + __PROPERTY(uint8_t, mfId, MFId); + + /// Source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + /// Voice channel number. + __PROPERTY(uint32_t, grpVchNo, GrpVchNo); + + /** Adjacent Site Data */ + /// Adjacent site CFVA flags. + __PROPERTY(uint8_t, adjCFVA, AdjSiteCFVA); + /// Adjacent site system ID. + __PROPERTY(uint32_t, adjSysId, AdjSiteSysId); + /// Adjacent site RFSS ID. + __PROPERTY(uint8_t, adjRfssId, AdjSiteRFSSId); + /// Adjacent site ID. + __PROPERTY(uint8_t, adjSiteId, AdjSiteId); + /// Adjacent site channel ID. + __PROPERTY(uint8_t, adjChannelId, AdjSiteChnId); + /// Adjacent site channel number. + __PROPERTY(uint32_t, adjChannelNo, AdjSiteChnNo); + + /** Service Options */ + /// Flag indicating the emergency bits are set. + __PROPERTY(bool, emergency, Emergency); + /// Flag indicating that encryption is enabled. + __PROPERTY(bool, encrypted, Encrypted); + /// Priority level for the traffic. + __PROPERTY(uint8_t, priority, Priority); + /// Flag indicating a group/talkgroup operation. + __PROPERTY(bool, group, Group); + + private: + friend class LC; + friend class TSBK; + edac::RS634717 m_rs; + + uint32_t m_callTimer; + + /** Local Site data */ + SiteData m_siteData; + lookups::IdenTable m_siteIdenEntry; + bool m_siteNetActive; + + /// Decode link control. + bool decodeLC(const uint8_t* rs); + /// Encode link control. + void encodeLC(uint8_t* rs); + }; + } // namespace lc +} // namespace p25 + +#endif // __P25_LC__TDULC_H__ diff --git a/p25/lc/TSBK.cpp b/p25/lc/TSBK.cpp new file mode 100644 index 00000000..b5722106 --- /dev/null +++ b/p25/lc/TSBK.cpp @@ -0,0 +1,799 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2017-2020 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "Defines.h" +#include "p25/P25Defines.h" +#include "p25/lc/TSBK.h" +#include "p25/P25Utils.h" +#include "edac/CRC.h" +#include "Log.h" +#include "Utils.h" + +using namespace p25::lc; +using namespace p25; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the TSBK class. +/// +TSBK::TSBK() : + m_protect(false), + m_lco(LC_GROUP), + m_mfId(P25_MFG_STANDARD), + m_srcId(0U), + m_dstId(0U), + m_lastBlock(false), + m_aivFlag(true), + m_emergency(false), + m_encrypted(false), + m_priority(4U), + m_group(true), + m_rs(), + m_trellis(), + m_vendorSkip(false), + m_sndcpAutoAccess(true), + m_sndcpReqAccess(false), + m_sndcpDAC(1U), + m_siteData(), + m_siteNetActive(false), + m_siteChCnt(1U) +{ + m_siteCallsign = new uint8_t[P25_MOT_CALLSIGN_LENGTH_BYTES]; + ::memset(m_siteCallsign, 0x00U, P25_MOT_CALLSIGN_LENGTH_BYTES); + + m_siteCallsign[0] = 'C'; + m_siteCallsign[1] = 'H'; + m_siteCallsign[2] = 'A'; + m_siteCallsign[3] = 'N'; + m_siteCallsign[4] = 'G'; + m_siteCallsign[5] = 'E'; + m_siteCallsign[6] = 'M'; + m_siteCallsign[7] = 'E'; + + m_siteIdenEntry = lookups::IdenTable(); + + reset(); +} + +/// +/// Finalizes a instance of TSBK class. +/// +TSBK::~TSBK() +{ + delete[] m_siteCallsign; +} + +/// +/// Decode a trunking signalling block. +/// +/// +/// True, if TSBK was decoded, otherwise false. +bool TSBK::decode(const uint8_t* data) +{ + assert(data != NULL); + + // deinterleave + uint8_t tsbk[P25_TSBK_LENGTH_BYTES + 1U]; + uint8_t raw[P25_TSBK_FEC_LENGTH_BYTES]; + P25Utils::decode(data, raw, 114U, 318U); + + // decode 1/2 rate Trellis & check CRC-CCITT 16 + try { + bool ret = m_trellis.decode12(raw, tsbk); + if (ret) + ret = edac::CRC::checkCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); + if (!ret) + return false; + } + catch (...) { + Utils::dump(2U, "P25, CRC failed with input data", tsbk, P25_TSBK_LENGTH_BYTES); + return false; + } + + // Utils::dump(2U, "TSDU TSBK", tsbk, P25_TSBK_LENGTH_BYTES); + + m_lco = tsbk[0U] & 0x3F; // LCO + m_lastBlock = (tsbk[0U] & 0x80U) == 0x80U; // Protect Flag + m_mfId = tsbk[1U]; // Mfg Id. + + ulong64_t tsbkValue = 0U; + + // combine bytes into rs value + tsbkValue = tsbk[2U]; + tsbkValue = (tsbkValue << 8) + tsbk[3U]; + tsbkValue = (tsbkValue << 8) + tsbk[4U]; + tsbkValue = (tsbkValue << 8) + tsbk[5U]; + tsbkValue = (tsbkValue << 8) + tsbk[6U]; + tsbkValue = (tsbkValue << 8) + tsbk[7U]; + tsbkValue = (tsbkValue << 8) + tsbk[8U]; + tsbkValue = (tsbkValue << 8) + tsbk[9U]; + + // Motorola P25 vendor opcodes + if (m_mfId == P25_MFG_MOT) { + switch (m_lco) { + case TSBK_IOSP_GRP_VCH: + case TSBK_IOSP_UU_VCH: + case TSBK_IOSP_UU_ANS: + case TSBK_IOSP_TELE_INT_ANS: + case TSBK_IOSP_STS_UPDT: + case TSBK_IOSP_STS_Q: + case TSBK_IOSP_MSG_UPDT: + case TSBK_IOSP_CALL_ALRT: + case TSBK_IOSP_ACK_RSP: + case TSBK_IOSP_GRP_AFF: + case TSBK_IOSP_U_REG: + case TSBK_ISP_CAN_SRV_REQ: + case TSBK_ISP_GRP_AFF_Q_RSP: + case TSBK_ISP_U_DEREG_REQ: + case TSBK_OSP_U_DEREG_ACK: + case TSBK_ISP_LOC_REG_REQ: + m_mfId = P25_MFG_STANDARD; + break; + default: + LogError(LOG_P25, "unknown TSBK LCO value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + break; + } + + if (m_mfId == P25_MFG_MOT) { + return true; + } + else { + m_mfId = tsbk[1U]; + } + } + + // standard P25 reference opcodes + switch (m_lco) { + case TSBK_IOSP_GRP_VCH: + m_emergency = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (((tsbkValue >> 56) & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + m_priority = (((tsbkValue >> 56) & 0xFFU) & 0x07U); // Priority + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_UU_VCH: + m_emergency = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (((tsbkValue >> 56) & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + m_priority = (((tsbkValue >> 56) & 0xFFU) & 0x07U); // Priority + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_UU_ANS: + m_emergency = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (((tsbkValue >> 56) & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + m_priority = (((tsbkValue >> 56) & 0xFFU) & 0x07U); // Priority + m_response = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Answer Response + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_TELE_INT_ANS: + m_emergency = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag + m_encrypted = (((tsbkValue >> 56) & 0xFFU) & 0x40U) == 0x40U; // Encryption Flag + m_priority = (((tsbkValue >> 56) & 0xFFU) & 0x07U); // Priority + m_response = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Answer Response + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_STS_UPDT: + m_statusValue = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Status Value + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_MSG_UPDT: + m_messageValue = (uint32_t)((tsbkValue >> 48) & 0xFFFFU); // Message Value + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_CALL_ALRT: + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_ACK_RSP: + m_aivFlag = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Additional Info. Flag + m_service = (uint8_t)((tsbkValue >> 56) & 0x3FU); // Service Type + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_EXT_FNCT: + m_extendedFunction = (uint32_t)((tsbkValue >> 48) & 0xFFFFU); // Extended Function + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Argument + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Target Radio Address + break; + case TSBK_IOSP_GRP_AFF: + m_sysId = (uint32_t)((tsbkValue >> 16) & 0xFFFU); // System ID + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Talkgroup Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_IOSP_U_REG: + m_netId = (uint32_t)((tsbkValue >> 36) & 0xFFFFFU); // Network ID + m_sysId = (uint32_t)((tsbkValue >> 24) & 0xFFFU); // System ID + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_ISP_CAN_SRV_REQ: + m_aivFlag = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Additional Info. Flag + m_service = (uint8_t)((tsbkValue >> 56) & 0x3FU); // Service Type + m_response = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Reason + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_ISP_GRP_AFF_Q_RSP: + m_patchSuperGroupId = (uint32_t)((tsbkValue >> 40) & 0xFFFFU); // Announcement Group Address + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Talkgroup Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_ISP_U_DEREG_REQ: + case TSBK_OSP_U_DEREG_ACK: + m_netId = (uint32_t)((tsbkValue >> 36) & 0xFFFFFU); // Network ID + m_sysId = (uint32_t)((tsbkValue >> 24) & 0xFFFU); // System ID + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_ISP_LOC_REG_REQ: + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Talkgroup Address + m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + break; + case TSBK_OSP_ADJ_STS_BCAST: + m_adjSysId = (uint32_t)((tsbkValue >> 40) & 0xFFFU); // Site System ID + m_adjRfssId = (uint8_t)((tsbkValue >> 32) & 0xFFU); // Site RFSS ID + m_adjSiteId = (uint8_t)((tsbkValue >> 24) & 0xFFU); // Site ID + m_adjChannelId = (uint8_t)((tsbkValue >> 20) & 0xFU); // Site Channel ID + m_adjChannelNo = (uint32_t)((tsbkValue >> 8) & 0xFFFU); // Site Channel ID + break; + default: + LogError(LOG_P25, "unknown TSBK LCO value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + break; + } + + return true; +} + +/// +/// Encode a trunking signalling block. +/// +/// +void TSBK::encode(uint8_t * data, bool singleBlock) +{ + assert(data != NULL); + + const uint32_t services = (m_siteNetActive) ? P25_SYS_SRV_NET_ACTIVE : 0U | + P25_SYS_SRV_GROUP_DATA | P25_SYS_SRV_GROUP_VOICE | P25_SYS_SRV_IND_DATA | P25_SYS_SRV_IND_VOICE; + + uint8_t tsbk[P25_TSBK_LENGTH_BYTES]; + ::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES); + + ulong64_t tsbkValue = 0U; + tsbk[0U] = m_lco; // LCO + tsbk[0U] |= (m_lastBlock) ? 0x80U : 0x00U; // Last Block Marker + + tsbk[1U] = m_mfId; + + // standard P25 reference opcodes + switch (m_lco) { + case TSBK_IOSP_GRP_VCH: + tsbkValue = + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + tsbkValue = (tsbkValue << 4) + m_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 + break; + case TSBK_IOSP_UU_VCH: + tsbkValue = + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + m_grpVchNo; // Channel Number + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_UU_ANS: + tsbkValue = + (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U) + // Encrypted Flag + (m_priority & 0x07U); // Priority + tsbkValue = (tsbkValue << 32) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_STS_UPDT: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 16) + m_statusValue; // Status Value + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_MSG_UPDT: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 16) + m_messageValue; // Message Value + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_CALL_ALRT: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 40) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_ACK_RSP: + tsbkValue = (m_service & 0x3F); // Service Type + tsbkValue |= (m_aivFlag) ? 0x80U : 0x00U; // Additional Info. Valid Flag + tsbkValue = (tsbkValue << 32) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_IOSP_EXT_FNCT: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 16) + m_extendedFunction; // Extended Function + tsbkValue = (tsbkValue << 24) + m_srcId; // Argument + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + break; + case TSBK_IOSP_GRP_AFF: + tsbkValue = 1U; // Local/Global Affiliation Flag (0 = Local, 1 = Global) + tsbkValue = (tsbkValue << 7) + (m_response & 0x3U); // Affiliation Response + tsbkValue = (tsbkValue << 16) + (m_patchSuperGroupId & 0xFFFFU); // Announcement Group Address + tsbkValue = (tsbkValue << 16) + (m_dstId & 0xFFFFU); // Talkgroup Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_SNDCP_CH_ANN: + tsbkValue = 0U; // + tsbkValue = (m_emergency ? 0x80U : 0x00U) + // Emergency Flag + (m_encrypted ? 0x40U : 0x00U); // Encrypted Flag + tsbkValue = (tsbkValue << 8) + + (m_sndcpAutoAccess ? 0x80U : 0x00U) + // Autonomous Access + (m_sndcpAutoAccess ? 0x40U : 0x00U); // Requested Access + tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (T) ID + tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel (T) Number + tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel (R) ID + tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel (R) Number + tsbkValue = (tsbkValue << 16) + m_sndcpDAC; // Data Access Control + break; + case TSBK_IOSP_U_REG: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 2) + (m_response & 0x3U); // Unit Registration Response + tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 24) + m_dstId; // Source ID + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_DENY_RSP: + case TSBK_OSP_QUE_RSP: + { + if (m_response == 0U) { + if (m_lco == TSBK_OSP_QUE_RSP) { + LogError(LOG_P25, "invalid values for TSBK_OSP_QUE_RSP, reason = %u", m_response); + } + else { + LogError(LOG_P25, "invalid values for TSBK_OSP_DENY_RSP, reason = %u", m_response); + } + return; // blatently ignore creating this TSBK + } + + tsbkValue = (m_aivFlag) ? 0x80U : 0x00U; // Additional Info Flag + tsbkValue = (tsbkValue << 6) + m_service; // Service Type + tsbkValue = (tsbkValue << 8) + m_response; // Deny/Queue Reason + + if (m_group) { + // group deny/queue + tsbkValue = (tsbkValue << 8) + 0U; // Call Options + tsbkValue = (tsbkValue << 16) + m_dstId; // Talkgroup Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + } + else { + // private/individual deny/queue + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + } + } + break; + case TSBK_OSP_GRP_AFF_Q: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_LOC_REG_RSP: + tsbkValue = 0U; + 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 << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_U_REG_CMD: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 24) + m_dstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_U_DEREG_ACK: + tsbkValue = 0U; + tsbkValue = (tsbkValue << 8) + m_siteData.netId(); // Network ID + tsbkValue = (tsbkValue << 12) + m_siteData.sysId(); // System ID + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + break; + case TSBK_OSP_IDEN_UP_VU: + { + if ((m_siteIdenEntry.chBandwidthKhz() != 0.0F) && (m_siteIdenEntry.chSpaceKhz() != 0.0F) && + (m_siteIdenEntry.txOffsetMhz() != 0.0F) && (m_siteIdenEntry.baseFrequency() != 0U)) { + uint32_t calcSpace = (uint32_t)(m_siteIdenEntry.chSpaceKhz() / 0.125); + uint32_t calcTxOffset = (uint32_t)((abs(m_siteIdenEntry.txOffsetMhz()) / m_siteIdenEntry.chSpaceKhz()) * 1000); + if (m_siteIdenEntry.txOffsetMhz() > 0.0F) + calcTxOffset |= 0x2000U; // this sets a positive offset ... + + uint32_t calcBaseFreq = (uint32_t)(m_siteIdenEntry.baseFrequency() / 5); + uint8_t chanBw = (m_siteIdenEntry.chBandwidthKhz() >= 12.5F) ? P25_IDEN_UP_VU_BW_125K : P25_IDEN_UP_VU_BW_625K; + + tsbkValue = m_siteIdenEntry.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + chanBw; // Channel Bandwidth + tsbkValue = (tsbkValue << 14) + calcTxOffset; // Transmit Offset + tsbkValue = (tsbkValue << 10) + calcSpace; // Channel Spacing + tsbkValue = (tsbkValue << 32) + calcBaseFreq; // Base Frequency + } + else { + LogError(LOG_P25, "invalid values for TSBK_OSP_IDEN_UP_VU, baseFrequency = %uHz, txOffsetMhz = %uMHz, chBandwidthKhz = %fKHz, chSpaceKhz = %fKHz", + m_siteIdenEntry.baseFrequency(), m_siteIdenEntry.txOffsetMhz(), m_siteIdenEntry.chBandwidthKhz(), + m_siteIdenEntry.chSpaceKhz()); + return; // blatently ignore creating this TSBK + } + } + break; + case TSBK_OSP_SYS_SRV_BCAST: + tsbkValue = 0U; // + tsbkValue = (tsbkValue << 16) + services; // System Services Available + tsbkValue = (tsbkValue << 24) + services; // System Services Supported + break; + case TSBK_OSP_RFSS_STS_BCAST: + tsbkValue = m_siteData.lra(); // Location Registration Area + tsbkValue = (tsbkValue << 4) + + (m_siteNetActive) ? P25_CFVA_NETWORK : 0U; // CFVA + 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) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + break; + case TSBK_OSP_NET_STS_BCAST: + 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) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + break; + case TSBK_OSP_ADJ_STS_BCAST: + { + if ((m_adjRfssId != 0U) && (m_adjSiteId != 0U) && (m_adjChannelNo != 0U)) { + if (m_adjSysId == 0U) { + m_adjSysId = m_siteData.sysId(); + } + + tsbkValue = m_siteData.lra(); // Location Registration Area + tsbkValue = (tsbkValue << 8) + m_adjCFVA; // CFVA + tsbkValue = (tsbkValue << 12) + m_adjSysId; // 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 + tsbkValue = (tsbkValue << 12) + m_adjChannelNo; // Channel Number + tsbkValue = (tsbkValue << 8) + // System Service Class + (P25_SVC_CLS_COMPOSITE | P25_SVC_CLS_VOICE | P25_SVC_CLS_DATA | P25_SVC_CLS_REG); + } + else { + LogError(LOG_P25, "invalid values for OSP_ADJ_STS_BCAST, adjRfssId = $%02X, adjSiteId = $%02X, adjChannelId = %u, adjChannelNo = $%02X", + m_adjRfssId, m_adjSiteId, m_adjChannelId, m_adjChannelNo); + return; // blatently ignore creating this TSBK + } + } + break; + case TSBK_OSP_IDEN_UP: + { + if ((m_siteIdenEntry.chBandwidthKhz() != 0.0F) && (m_siteIdenEntry.chSpaceKhz() != 0.0F) && + (m_siteIdenEntry.txOffsetMhz() != 0.0F) && (m_siteIdenEntry.baseFrequency() != 0U)) { + if (m_siteIdenEntry.baseFrequency() < 762000000U) { + LogError(LOG_P25, "invalid values for TSBK_OSP_IDEN_UP, baseFrequency = %uHz", + m_siteIdenEntry.baseFrequency()); + return; // blatently ignore creating this TSBK + } + + uint32_t calcSpace = (uint32_t)(m_siteIdenEntry.chSpaceKhz() / 0.125); + uint32_t calcTxOffset = (uint32_t)((abs(m_siteIdenEntry.txOffsetMhz()) * 1000000) / 250000); + if (m_siteIdenEntry.txOffsetMhz() > 0.0F) + calcTxOffset |= 0x100U; // this sets a positive offset ... + + uint32_t calcBaseFreq = (uint32_t)(m_siteIdenEntry.baseFrequency() / 5); + uint16_t chanBw = (uint16_t)((m_siteIdenEntry.chBandwidthKhz() * 1000) / 125); + + tsbkValue = m_siteIdenEntry.channelId(); // Channel ID + tsbkValue = (tsbkValue << 9) + chanBw; // Channel Bandwidth + tsbkValue = (tsbkValue << 9) + calcTxOffset; // Transmit Offset + tsbkValue = (tsbkValue << 10) + calcSpace; // Channel Spacing + tsbkValue = (tsbkValue << 32) + calcBaseFreq; // Base Frequency + } + else { + LogError(LOG_P25, "invalid values for TSBK_OSP_IDEN_UP, baseFrequency = %uHz, txOffsetMhz = %uMHz, chBandwidthKhz = %fKHz, chSpaceKhz = %fKHz", + m_siteIdenEntry.baseFrequency(), m_siteIdenEntry.txOffsetMhz(), m_siteIdenEntry.chBandwidthKhz(), + m_siteIdenEntry.chSpaceKhz()); + return; // blatently ignore creating this TSBK + } + } + break; + default: + if (m_mfId == P25_MFG_STANDARD) { + LogError(LOG_P25, "unknown TSBK LCO value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + } + break; + } + + if (!m_vendorSkip) { + // Motorola P25 vendor opcodes + /** + * The sequence of data in these opcodes was gleaned from the sdrtrunk project (https://github.com/DSheirer/sdrtrunk) + */ + if (m_mfId == P25_MFG_MOT) { + switch (m_lco) { + case TSBK_OSP_MOT_GRG_ADD: + { + if ((m_patchSuperGroupId != 0U)) { + tsbkValue = m_patchSuperGroupId; // Patch Super Group Address + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + + if (m_patchGroup2Id != 0U) { + tsbkValue = (tsbkValue << 16) + m_patchGroup2Id; // Patch Group 2 Address + } + else { + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + } + + if (m_patchGroup3Id != 0U) { + tsbkValue = (tsbkValue << 16) + m_patchGroup3Id; // Patch Group 3 Address + } + else { + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + } + } // otherwise handled as a TSBK_OSP_MOT_GRG_ADD + } + break; + case TSBK_OSP_MOT_GRG_DEL: + { + if ((m_patchSuperGroupId != 0U) && (m_patchGroup1Id != 0U)) { + tsbkValue = m_patchSuperGroupId; // Patch Super Group Address + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + + if (m_patchGroup2Id != 0U) { + tsbkValue = (tsbkValue << 16) + m_patchGroup2Id; // Patch Group 2 Address + } + else { + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + } + + if (m_patchGroup3Id != 0U) { + tsbkValue = (tsbkValue << 16) + m_patchGroup3Id; // Patch Group 3 Address + } + else { + tsbkValue = (tsbkValue << 16) + m_patchGroup1Id; // Patch Group 1 Address + } + } + else { + LogError(LOG_P25, "invalid values for TSBK_OSP_MOT_GRG_DEL, patchSuperGroupId = $%02X, patchGroup1Id = $%02X", + m_patchSuperGroupId, m_patchGroup1Id); + return; // blatently ignore creating this TSBK + } + } + break; + case TSBK_OSP_MOT_GRG_VCH_GRANT: + { + 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 << 16) + m_patchSuperGroupId; // Patch Supergroup Address + tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address + } + else { + LogError(LOG_P25, "invalid values for TSBK_OSP_MOT_GRG_VCH_GRANT, patchSuperGroupId = $%02X", m_patchSuperGroupId); + return; // blatently ignore creating this TSBK + } + } + break; + case TSBK_OSP_MOT_GRG_VCH_UPD: + tsbkValue = m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 4) + m_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 << 12) + m_patchGroup2Id; // Patch Group 2 + break; + break; + case TSBK_OSP_MOT_CC_BSI: + tsbkValue = (m_siteCallsign[0] - 43U) & 0x3F; // Character 0 + for (int i = 1; i < P25_MOT_CALLSIGN_LENGTH_BYTES; i++) { + tsbkValue = (tsbkValue << 6) + ((m_siteCallsign[i] - 43U) & 0x3F); // Character 1 - 7 + } + tsbkValue = (tsbkValue << 4) + m_siteData.channelId(); // Channel ID + tsbkValue = (tsbkValue << 12) + m_siteData.channelNo(); // Channel Number + break; + case TSBK_OSP_MOT_PSH_CCH: + tsbkValue = 0U; + break; + // because of how MFId is handled; we have to skip these opcodes + case TSBK_IOSP_UU_VCH: + case TSBK_IOSP_STS_UPDT: + case TSBK_IOSP_STS_Q: + case TSBK_IOSP_MSG_UPDT: + case TSBK_IOSP_CALL_ALRT: + case TSBK_IOSP_GRP_AFF: + case TSBK_IOSP_ACK_RSP: + case TSBK_IOSP_U_REG: + case TSBK_OSP_DENY_RSP: + case TSBK_OSP_QUE_RSP: + case TSBK_OSP_GRP_AFF_Q: + case TSBK_OSP_U_REG_CMD: + case TSBK_OSP_U_DEREG_ACK: + break; + default: + LogError(LOG_P25, "unknown TSBK LCO value, mfId = $%02X, lco = $%02X", m_mfId, m_lco); + break; + } + } + } + + // split rs value into bytes + tsbk[2U] = (uint8_t)((tsbkValue >> 56) & 0xFFU); + tsbk[3U] = (uint8_t)((tsbkValue >> 48) & 0xFFU); + tsbk[4U] = (uint8_t)((tsbkValue >> 40) & 0xFFU); + tsbk[5U] = (uint8_t)((tsbkValue >> 32) & 0xFFU); + tsbk[6U] = (uint8_t)((tsbkValue >> 24) & 0xFFU); + tsbk[7U] = (uint8_t)((tsbkValue >> 16) & 0xFFU); + tsbk[8U] = (uint8_t)((tsbkValue >> 8) & 0xFFU); + tsbk[9U] = (uint8_t)((tsbkValue >> 0) & 0xFFU); + + // compute CRC-CCITT 16 + edac::CRC::addCCITT162(tsbk, P25_TSBK_LENGTH_BYTES); + + // Utils::dump(2U, "TSBK", tsbk, P25_TSBK_LENGTH_BYTES); + + uint8_t raw[P25_TSBK_FEC_LENGTH_BYTES]; + ::memset(raw, 0x00U, P25_TSBK_FEC_LENGTH_BYTES); + + // encode 1/2 rate Trellis + m_trellis.encode12(tsbk, raw); + + // is this a single block TSBK? + if (singleBlock) { + // interleave + P25Utils::encode(raw, data, 114U, 318U); + + // Utils::dump(2U, "TSBK Interleave", data, P25_TSBK_FEC_LENGTH_BYTES + P25_PREAMBLE_LENGTH_BYTES); + } + else { + ::memcpy(data, raw, P25_TSBK_FEC_LENGTH_BYTES); + } +} + +/// +/// Helper to reset data values to defaults. +/// +void TSBK::reset() +{ + m_vendorSkip = false; + + m_protect = false; + m_lco = LC_GROUP; + m_mfId = P25_MFG_STANDARD; + + m_srcId = 0U; + m_dstId = 0U; + + m_lastBlock = true; + m_aivFlag = true; + + m_service = 0U; + m_response = P25_RSP_ACCEPT; + + m_netId = P25_WACN_STD_DEFAULT; + m_sysId = P25_SID_STD_DEFAULT; + + m_grpVchNo = m_siteData.channelNo(); + + m_messageValue = 0U; + m_statusValue = 0U; + + m_extendedFunction = P25_EXT_FNCT_CHECK; + + m_adjCFVA = P25_CFVA_CONV | P25_CFVA_FAILURE; + m_adjRfssId = 0U; + m_adjSiteId = 0U; + m_adjChannelId = 0U; + m_adjChannelNo = 0U; + + /* TSBK Patch Group data */ + m_patchSuperGroupId = 0U; + m_patchGroup1Id = 0U; + m_patchGroup2Id = 0U; + m_patchGroup3Id = 0U; + + /* Service Options */ + m_emergency = false; + m_encrypted = false; + m_priority = 4U; + m_group = true; +} + +/// +/// +void TSBK::setVendorSkip(bool skip) +{ + m_vendorSkip = skip; +} + +/** Local Site data */ +/// Sets local configured site data. +/// +void TSBK::setSiteData(SiteData siteData) +{ + m_siteData = siteData; +} + +/// Sets local configured site callsign. +/// +void TSBK::setCallsign(std::string callsign) +{ + uint32_t idLength = callsign.length(); + if (idLength > 0) { + ::memset(m_siteCallsign, 0x20U, P25_MOT_CALLSIGN_LENGTH_BYTES); + + if (idLength > P25_MOT_CALLSIGN_LENGTH_BYTES) + idLength = P25_MOT_CALLSIGN_LENGTH_BYTES; + for (uint32_t i = 0; i < idLength; i++) + m_siteCallsign[i] = callsign[i]; + } +} + +/// +/// +void TSBK::setIdenTable(lookups::IdenTable entry) +{ + m_siteIdenEntry = entry; +} + +/// Sets a flag indicating whether or not networking is active. +/// +void TSBK::setNetActive(bool netActive) +{ + m_siteNetActive = netActive; +} + +/// Sets the total number of channels at the site. +/// +void TSBK::setSiteChCnt(uint8_t chCnt) +{ + m_siteChCnt = chCnt; +} diff --git a/p25/lc/TSBK.h b/p25/lc/TSBK.h new file mode 100644 index 00000000..a70589ae --- /dev/null +++ b/p25/lc/TSBK.h @@ -0,0 +1,185 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* Copyright (C) 2017-2019 by Bryan Biedenkapp N2PLL +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__P25_LC__TSBK_H__) +#define __P25_LC__TSBK_H__ + +#include "Defines.h" +#include "p25/edac/Trellis.h" +#include "p25/lc/LC.h" +#include "p25/lc/TDULC.h" +#include "p25/SiteData.h" +#include "edac/RS634717.h" +#include "lookups/IdenTableLookup.h" + +#include + +namespace p25 +{ + namespace lc + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API LC; + class HOST_SW_API TDULC; + + // --------------------------------------------------------------------------- + // Class Declaration + // Represents link control data for TSDU packets. + // --------------------------------------------------------------------------- + + class HOST_SW_API TSBK { + public: + /// Initializes a new instance of the TSBK class. + TSBK(); + /// Finalizes a instance of the TSBK class. + ~TSBK(); + + /// Decode a trunking signalling block. + bool decode(const uint8_t* data); + /// Encode a trunking signalling block. + void encode(uint8_t* data, bool singleBlock); + + /// Helper to reset data values to defaults. + void reset(); + + /// + void setVendorSkip(bool skip); + + /** Local Site data */ + /// Sets local configured site data. + void setSiteData(SiteData siteData); + /// Sets local configured site callsign. + void setCallsign(std::string callsign); + /// + void setIdenTable(lookups::IdenTable entry); + /// Sets a flag indicating whether or not networking is active. + void setNetActive(bool netActive); + /// Sets the total number of channels at the site. + void setSiteChCnt(uint8_t chCnt); + + public: + /** Common Data */ + /// Flag indicating the link control data is protected. + __PROPERTY(bool, protect, Protect); + /// Link control opcode. + __PROPERTY(uint8_t, lco, LCO); + /// Manufacturer ID. + __PROPERTY(uint8_t, mfId, MFId); + + /// Source ID. + __PROPERTY(uint32_t, srcId, SrcId); + /// Destination ID. + __PROPERTY(uint32_t, dstId, DstId); + + /// Flag indicating this is the last TSBK in a sequence of TSBKs. + __PROPERTY(bool, lastBlock, LastBlock); + /// Flag indicating this TSBK contains additional information. + __PROPERTY(bool, aivFlag, AIV); + + /// Service type. + __PROPERTY(uint8_t, service, Service); + + /// Response type. + __PROPERTY(uint8_t, response, Response); + + /// Configured network ID. + __READONLY_PROPERTY(uint32_t, netId, NetId); + /// Configured system ID. + __READONLY_PROPERTY(uint32_t, sysId, SysId); + + /// Voice channel number. + __PROPERTY(uint32_t, grpVchNo, GrpVchNo); + + /// Message value. + __PROPERTY(uint32_t, messageValue, Message); + /// Status value. + __PROPERTY(uint8_t, statusValue, Status); + + /// Extended function opcode. + __PROPERTY(uint32_t, extendedFunction, ExtendedFunction); + + /** Adjacent Site Data */ + /// Adjacent site CFVA flags. + __PROPERTY(uint8_t, adjCFVA, AdjSiteCFVA); + /// Adjacent site system ID. + __PROPERTY(uint32_t, adjSysId, AdjSiteSysId); + /// Adjacent site RFSS ID. + __PROPERTY(uint8_t, adjRfssId, AdjSiteRFSSId); + /// Adjacent site ID. + __PROPERTY(uint8_t, adjSiteId, AdjSiteId); + /// Adjacent site channel ID. + __PROPERTY(uint8_t, adjChannelId, AdjSiteChnId); + /// Adjacent site channel number. + __PROPERTY(uint32_t, adjChannelNo, AdjSiteChnNo); + + /** Patch Group data */ + /// Patch super group ID. + __PROPERTY(uint32_t, patchSuperGroupId, PatchSuperGroupId); + /// 1st patch group ID. + __PROPERTY(uint32_t, patchGroup1Id, PatchGroup1Id); + /// 2nd patch group ID. + __PROPERTY(uint32_t, patchGroup2Id, PatchGroup2Id); + /// 3rd patch group ID. + __PROPERTY(uint32_t, patchGroup3Id, PatchGroup3Id); + + /** Service Options */ + /// Flag indicating the emergency bits are set. + __PROPERTY(bool, emergency, Emergency); + /// Flag indicating that encryption is enabled. + __PROPERTY(bool, encrypted, Encrypted); + /// Priority level for the traffic. + __PROPERTY(uint8_t, priority, Priority); + /// Flag indicating a group/talkgroup operation. + __PROPERTY(bool, group, Group); + + private: + friend class LC; + friend class TDULC; + edac::RS634717 m_rs; + edac::Trellis m_trellis; + bool m_vendorSkip; + + bool m_sndcpAutoAccess; + bool m_sndcpReqAccess; + uint16_t m_sndcpDAC; + + /** Local Site data */ + SiteData m_siteData; + uint8_t* m_siteCallsign; + lookups::IdenTable m_siteIdenEntry; + bool m_siteNetActive; + uint8_t m_siteChCnt; + }; + } // namespace lc +} // namespace p25 + +#endif // __P25_LC__TSBK_H__ diff --git a/rid_acl.dat b/rid_acl.dat new file mode 100644 index 00000000..09de49d1 --- /dev/null +++ b/rid_acl.dat @@ -0,0 +1,5 @@ +# +# This file sets the valid Radio IDs allowed on a repeater. +# +# RID,Enabled (1 = Enabled / 0 = Disabled), +#1234,1, diff --git a/tg_acl.dat b/tg_acl.dat new file mode 100644 index 00000000..36dd6744 --- /dev/null +++ b/tg_acl.dat @@ -0,0 +1,6 @@ +# +# This file sets the valid Talkgroup IDs allowed on a repeater. +# +# TGID,Enabled (1 = Enabled / 0 = Disabled),Slot (0 = Both, 1 = Slot 1, 2 = Slot 2), +16777215,1,0, +65535,1,0, diff --git a/yaml/Yaml.cpp b/yaml/Yaml.cpp new file mode 100644 index 00000000..c2c5bf41 --- /dev/null +++ b/yaml/Yaml.cpp @@ -0,0 +1,2753 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the mini-yaml project. (https://github.com/jimmiebergmann/mini-yaml) +// Licensed under the MIT License (https://opensource.org/licenses/MIT) +// +/* +* Copyright(c) 2018 Jimmie Bergmann +* Copyright (C) 2020 Bryan Biedenkapp N2PLL +* +* MIT License +* +* 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. +* +*/ + +#include "yaml/Yaml.h" + +#include +#include +#include +#include +#include +#include +#include + +// Implementation access definitions. +#define NODE_IMP static_cast(m_pImp) +#define NODE_IMP_EXT(node) static_cast(node.m_pImp) +#define TYPE_IMP static_cast(m_pImp)->m_pImp + +#define IT_IMP static_cast(m_pImp) + +namespace yaml +{ + class ReaderLine; + + // Exception message definitions. + static const std::string g_ErrorInvalidCharacter = "Invalid character found."; + static const std::string g_ErrorKeyMissing = "Missing key."; + static const std::string g_ErrorKeyIncorrect = "Incorrect key."; + static const std::string g_ErrorValueIncorrect = "Incorrect value."; + static const std::string g_ErrorTabInOffset = "Tab found in offset."; + static const std::string g_ErrorBlockSequenceNotAllowed = "Sequence entries are not allowed in this context."; + static const std::string g_ErrorUnexpectedDocumentEnd = "Unexpected document end."; + static const std::string g_ErrorDiffEntryNotAllowed = "Different entry is not allowed in this context."; + static const std::string g_ErrorIncorrectOffset = "Incorrect offset."; + static const std::string g_ErrorSequenceError = "Error in sequence node."; + static const std::string g_ErrorCannotOpenFile = "Cannot open file."; + static const std::string g_ErrorIndentation = "Space indentation is less than 2."; + static const std::string g_ErrorInvalidBlockScalar = "Invalid block scalar."; + static const std::string g_ErrorInvalidQuote = "Invalid quote."; + static const std::string g_EmptyString = ""; + + static yaml::Node g_NoneNode; + + // Global function definitions. Implemented at end of this source file. + static std::string ExceptionMessage(const std::string& message, ReaderLine& line); + static std::string ExceptionMessage(const std::string& message, ReaderLine& line, const size_t errorPos); + static std::string ExceptionMessage(const std::string& message, const size_t errorLine, const size_t errorPos); + static std::string ExceptionMessage(const std::string& message, const size_t errorLine, const std::string& data); + + static bool FindQuote(const std::string& input, size_t& start, size_t& end, size_t searchPos = 0); + static size_t FindNotCited(const std::string& input, char token, size_t& preQuoteCount); + static size_t FindNotCited(const std::string& input, char token); + static bool ValidateQuote(const std::string& input); + static void CopyNode(const Node& from, Node& to); + static bool ShouldBeCited(const std::string& key); + static void AddEscapeTokens(std::string& input, const std::string& tokens); + static void RemoveAllEscapeTokens(std::string& input); + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the Exception class. + /// + /// + /// + Exception::Exception(const std::string& message, const eType type) : + std::runtime_error(message), + m_Type(type) + { + /* stub */ + } + + /// + /// Get type of exception. + /// + /// + Exception::eType Exception::type() const + { + return m_Type; + } + + /// + /// Get message of exception. + /// + /// + const char* Exception::message() const + { + return what(); + } + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the InternalException class. + /// + /// + InternalException::InternalException(const std::string& message) : + Exception(message, InternalError) + { + /* stub */ + } + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the ParsingException class. + /// + /// + ParsingException::ParsingException(const std::string& message) : + Exception(message, ParsingError) + { + /* stub */ + } + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the OperationException class. + /// + /// + OperationException::OperationException(const std::string & message) : + Exception(message, OperationError) + { + /* stub */ + } + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class TypeImp { + public: + /// + /// Finalizes a new instance of the TypeImp class. + /// + virtual ~TypeImp() + { + } + + /// + /// + virtual const std::string& getData() const = 0; + /// + /// + /// + virtual bool setData(const std::string& data) = 0; + + /// + /// + virtual size_t size() const = 0; + + /// + /// + /// + virtual Node* getNode(const size_t index) = 0; + /// + /// + /// + virtual Node* getNode(const std::string& key) = 0; + /// + /// + /// + virtual Node* insert(const size_t index) = 0; + /// + /// + virtual Node* push_front() = 0; + /// + /// + virtual Node* push_back() = 0; + /// + /// + /// + virtual void erase(const size_t index) = 0; + /// + /// + /// + virtual void erase(const std::string& key) = 0; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class SequenceImp : public TypeImp { + public: + /// + /// Finalizes a new instance of the SequenceImp class. + /// + ~SequenceImp() + { + for (auto it = m_Sequence.begin(); it != m_Sequence.end(); it++) { + delete it->second; + } + } + + /// + /// + virtual const std::string & getData() const + { + return g_EmptyString; + } + /// + /// + /// + virtual bool setData(const std::string & data) + { + return false; + } + + /// + /// + virtual size_t size() const + { + return m_Sequence.size(); + } + + /// + /// + /// + virtual Node* getNode(const size_t index) + { + auto it = m_Sequence.find(index); + if (it != m_Sequence.end()) { + return it->second; + } + return nullptr; + } + /// + /// + /// + virtual Node* getNode(const std::string& key) + { + return nullptr; + } + /// + /// + /// + virtual Node* insert(const size_t index) + { + if (m_Sequence.size() == 0) { + Node* pNode = new Node; + m_Sequence.insert({ 0, pNode }); + return pNode; + } + + if (index >= m_Sequence.size()) { + auto it = m_Sequence.end(); + --it; + Node* pNode = new Node; + m_Sequence.insert({ it->first, pNode }); + return pNode; + } + + auto it = m_Sequence.cbegin(); + while (it != m_Sequence.cend()) { + m_Sequence[it->first + 1] = it->second; + if (it->first == index) { + break; + } + } + + Node* pNode = new Node; + m_Sequence.insert({ index, pNode }); + return pNode; + } + /// + /// + virtual Node* push_front() + { + for (auto it = m_Sequence.cbegin(); it != m_Sequence.cend(); it++) { + m_Sequence[it->first + 1] = it->second; + } + + Node* pNode = new Node; + m_Sequence.insert({ 0, pNode }); + return pNode; + } + /// + /// + virtual Node* push_back() + { + size_t index = 0; + if (m_Sequence.size()) { + auto it = m_Sequence.end(); + --it; + index = it->first + 1; + } + + Node* pNode = new Node; + m_Sequence.insert({ index, pNode }); + return pNode; + } + /// + /// + /// + virtual void erase(const size_t index) + { + auto it = m_Sequence.find(index); + if (it == m_Sequence.end()) { + return; + } + delete it->second; + m_Sequence.erase(index); + } + /// + /// + /// + virtual void erase(const std::string& key) + { + /* stub */ + } + + std::map m_Sequence; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class MapImp : public TypeImp { + public: + /// + /// Finalizes a new instance of the SequenceImp class. + /// + ~MapImp() + { + for (auto it = m_Map.begin(); it != m_Map.end(); it++) { + delete it->second; + } + } + + /// + /// + virtual const std::string& getData() const + { + return g_EmptyString; + } + /// + /// + /// + virtual bool setData(const std::string& data) + { + return false; + } + + /// + /// + virtual size_t size() const + { + return m_Map.size(); + } + + /// + /// + /// + virtual Node* getNode(const size_t index) + { + return nullptr; + } + /// + /// + /// + virtual Node* getNode(const std::string& key) + { + auto it = m_Map.find(key); + if (it == m_Map.end()) { + Node* pNode = new Node; + m_Map.insert({ key, pNode }); + return pNode; + } + return it->second; + } + /// + /// + /// + virtual Node* insert(const size_t index) + { + return nullptr; + } + /// + /// + virtual Node* push_front() + { + return nullptr; + } + /// + /// + virtual Node* push_back() + { + return nullptr; + } + /// + /// + /// + virtual void erase(const size_t index) + { + /* stub */ + } + /// + /// + /// + virtual void erase(const std::string& key) + { + auto it = m_Map.find(key); + if (it == m_Map.end()) { + return; + } + delete it->second; + m_Map.erase(key); + } + + std::map m_Map; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class ScalarImp : public TypeImp { + public: + /// + /// Finalizes a new instance of the ScalarImp class. + /// + ~ScalarImp() + { + /* stub */ + } + + /// + /// + virtual const std::string& getData() const + { + return m_Value; + } + /// + /// + /// + virtual bool setData(const std::string& data) + { + m_Value = data; + return true; + } + + /// + /// + virtual size_t size() const + { + return 0; + } + + /// + /// + /// + virtual Node* getNode(const size_t index) + { + return nullptr; + } + /// + /// + /// + virtual Node* getNode(const std::string& key) + { + return nullptr; + } + /// + /// + /// + virtual Node* insert(const size_t index) + { + return nullptr; + } + /// + /// + virtual Node* push_front() + { + return nullptr; + } + /// + /// + virtual Node* push_back() + { + return nullptr; + } + /// + /// + /// + virtual void erase(const size_t index) + { + /* stub */ + } + /// + /// + /// + virtual void erase(const std::string& key) + { + /* stub */ + } + + std::string m_Value; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class NodeImp { + public: + /// + /// Initializes a new instance of the NodeImp class. + /// + NodeImp() : + m_Type(Node::None), + m_pImp(nullptr) + { + /* stub */ + } + /// + /// Finalizes a new instance of the NodeImp class. + /// + ~NodeImp() + { + clear(); + } + + /// Completely clear node. + void clear() + { + if (m_pImp != nullptr) { + delete m_pImp; + m_pImp = nullptr; + } + m_Type = Node::None; + } + + /// + void initSequence() + { + if (m_Type != Node::SequenceType || m_pImp == nullptr) { + if (m_pImp) { + delete m_pImp; + } + m_pImp = new SequenceImp; + m_Type = Node::SequenceType; + } + } + /// + void initMap() + { + if (m_Type != Node::MapType || m_pImp == nullptr) { + if (m_pImp) { + delete m_pImp; + } + m_pImp = new MapImp; + m_Type = Node::MapType; + } + } + /// + void initScalar() + { + if (m_Type != Node::ScalarType || m_pImp == nullptr) { + if (m_pImp) { + delete m_pImp; + } + m_pImp = new ScalarImp; + m_Type = Node::ScalarType; + } + + } + + Node::eType m_Type; + TypeImp* m_pImp; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class IteratorImp { + public: + /// + /// Finalizes a new instance of the IteratorImp class. + /// + virtual ~IteratorImp() + { + /* stub */ + } + + /// + /// + virtual Node::eType type() const = 0; + /// + /// + virtual void initBegin(SequenceImp* pSequenceImp) = 0; + /// + /// + virtual void initEnd(SequenceImp* pSequenceImp) = 0; + /// + /// + virtual void initBegin(MapImp* pMapImp) = 0; + /// + /// + virtual void initEnd(MapImp* pMapImp) = 0; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class SequenceIteratorImp : public IteratorImp { + public: + /// + /// + virtual Node::eType type() const + { + return Node::SequenceType; + } + /// + /// + virtual void initBegin(SequenceImp* pSequenceImp) + { + m_Iterator = pSequenceImp->m_Sequence.begin(); + } + /// + /// + virtual void initEnd(SequenceImp* pSequenceImp) + { + m_Iterator = pSequenceImp->m_Sequence.end(); + } + /// + /// + virtual void initBegin(MapImp* pMapImp) + { + /* stub */ + } + /// + /// + virtual void initEnd(MapImp* pMapImp) + { + /* stub */ + } + + /// + /// + void copy(const SequenceIteratorImp& it) + { + m_Iterator = it.m_Iterator; + } + + std::map::iterator m_Iterator; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class MapIteratorImp : public IteratorImp { + public: + /// + /// + virtual Node::eType type() const + { + return Node::MapType; + } + /// + /// + virtual void initBegin(SequenceImp* pSequenceImp) + { + /* stub */ + } + /// + /// + virtual void initEnd(SequenceImp* pSequenceImp) + { + /* stub */ + } + /// + /// + virtual void initBegin(MapImp* pMapImp) + { + m_Iterator = pMapImp->m_Map.begin(); + } + /// + /// + virtual void initEnd(MapImp* pMapImp) + { + m_Iterator = pMapImp->m_Map.end(); + } + + /// + /// + void copy(const MapIteratorImp& it) + { + m_Iterator = it.m_Iterator; + } + + std::map::iterator m_Iterator; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class SequenceConstIteratorImp : public IteratorImp { + public: + /// + /// + virtual Node::eType type() const + { + return Node::SequenceType; + } + /// + /// + virtual void initBegin(SequenceImp* pSequenceImp) + { + m_Iterator = pSequenceImp->m_Sequence.begin(); + } + /// + /// + virtual void initEnd(SequenceImp* pSequenceImp) + { + m_Iterator = pSequenceImp->m_Sequence.end(); + } + /// + /// + virtual void initBegin(MapImp* pMapImp) + { + /* stub */ + } + /// + /// + virtual void initEnd(MapImp* pMapImp) + { + /* stub */ + } + + /// + /// + void copy(const SequenceConstIteratorImp & it) + { + m_Iterator = it.m_Iterator; + } + + std::map::const_iterator m_Iterator; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class MapConstIteratorImp : public IteratorImp { + public: + /// + /// + virtual Node::eType type() const + { + return Node::MapType; + } + /// + /// + virtual void initBegin(SequenceImp* pSequenceImp) + { + /* stub */ + } + /// + /// + virtual void initEnd(SequenceImp* pSequenceImp) + { + /* stub */ + } + /// + /// + virtual void initBegin(MapImp* pMapImp) + { + m_Iterator = pMapImp->m_Map.begin(); + } + /// + /// + virtual void initEnd(MapImp* pMapImp) + { + m_Iterator = pMapImp->m_Map.end(); + } + + /// + /// + void copy(const MapConstIteratorImp & it) + { + m_Iterator = it.m_Iterator; + } + + std::map::const_iterator m_Iterator; + }; + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the Iterator class. + /// + Iterator::Iterator() : + m_Type(None), + m_pImp(nullptr) + { + /* stub */ + } + /// + /// Copies an instance of the Iterator class to a new instance of the Iterator class. + /// + /// + Iterator::Iterator(const Iterator& it) : + m_Type(None), + m_pImp(nullptr) + { + *this = it; + } + /// + /// Finalizes a instance of the Iterator class. + /// + Iterator::~Iterator() + { + if (m_pImp) { + switch (m_Type) { + case SequenceType: + delete static_cast(m_pImp); + break; + case MapType: + delete static_cast(m_pImp); + break; + default: + break; + } + + } + } + + /// Assignment operator. + Iterator& Iterator::operator = (const Iterator& it) + { + if (m_pImp) { + switch (m_Type) { + case SequenceType: + delete static_cast(m_pImp); + break; + case MapType: + delete static_cast(m_pImp); + break; + default: + break; + } + + m_pImp = nullptr; + m_Type = None; + } + + IteratorImp* pNewImp = nullptr; + switch (it.m_Type) { + case SequenceType: + m_Type = SequenceType; + pNewImp = new SequenceIteratorImp; + static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; + break; + case MapType: + m_Type = MapType; + pNewImp = new MapIteratorImp; + static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; + break; + default: + break; + } + + m_pImp = pNewImp; + return *this; + } + + /// Get node of iterator. First pair item is the key of map value, empty if type is sequence. + std::pair Iterator::operator *() + { + switch (m_Type) { + case SequenceType: + return { g_EmptyString, *(static_cast(m_pImp)->m_Iterator->second) }; + break; + case MapType: + return { static_cast(m_pImp)->m_Iterator->first, + *(static_cast(m_pImp)->m_Iterator->second) }; + break; + default: + break; + } + + g_NoneNode.clear(); + return { g_EmptyString, g_NoneNode }; + } + + /// Post-increment operator. + Iterator& Iterator::operator ++ (int dummy) + { + switch (m_Type) { + case SequenceType: + static_cast(m_pImp)->m_Iterator++; + break; + case MapType: + static_cast(m_pImp)->m_Iterator++; + break; + default: + break; + } + return *this; + } + + /// Post-decrement operator. + Iterator& Iterator::operator -- (int dummy) + { + switch(m_Type) { + case SequenceType: + static_cast(m_pImp)->m_Iterator--; + break; + case MapType: + static_cast(m_pImp)->m_Iterator--; + break; + default: + break; + } + return *this; + } + + /// Check if iterator is equal to other iterator. + bool Iterator::operator == (const Iterator& it) + { + if (m_Type != it.m_Type) { + return false; + } + + switch (m_Type) { + case SequenceType: + return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; + break; + case MapType: + return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; + break; + default: + break; + } + + return false; + } + + /// Check if iterator is not equal to other iterator. + bool Iterator::operator != (const Iterator& it) + { + return !(*this == it); + } + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the ConstIterator class. + /// + ConstIterator::ConstIterator() : + m_Type(None), + m_pImp(nullptr) + { + /* stub */ + } + /// + /// Copies an instance of the ConstIterator class to a new instance of the ConstIterator class. + /// + /// + ConstIterator::ConstIterator(const ConstIterator& it) : + m_Type(None), + m_pImp(nullptr) + { + *this = it; + } + /// + /// Finalizes a instance of the ConstIterator class. + /// + ConstIterator::~ConstIterator() + { + if (m_pImp) { + switch (m_Type) { + case SequenceType: + delete static_cast(m_pImp); + break; + case MapType: + delete static_cast(m_pImp); + break; + default: + break; + } + + } + } + + /// Assignment operator. + ConstIterator& ConstIterator::operator = (const ConstIterator& it) + { + if (m_pImp) { + switch (m_Type) { + case SequenceType: + delete static_cast(m_pImp); + break; + case MapType: + delete static_cast(m_pImp); + break; + default: + break; + } + m_pImp = nullptr; + m_Type = None; + } + + IteratorImp* pNewImp = nullptr; + switch (it.m_Type) { + case SequenceType: + m_Type = SequenceType; + pNewImp = new SequenceConstIteratorImp; + static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; + break; + case MapType: + m_Type = MapType; + pNewImp = new MapConstIteratorImp; + static_cast(pNewImp)->m_Iterator = static_cast(it.m_pImp)->m_Iterator; + break; + default: + break; + } + + m_pImp = pNewImp; + return *this; + } + + /// Get node of iterator. First pair item is the key of map value, empty if type is sequence. + std::pair ConstIterator::operator *() + { + switch (m_Type) { + case SequenceType: + return { g_EmptyString, *(static_cast(m_pImp)->m_Iterator->second) }; + break; + case MapType: + return { static_cast(m_pImp)->m_Iterator->first, + *(static_cast(m_pImp)->m_Iterator->second) }; + break; + default: + break; + } + + g_NoneNode.clear(); + return { g_EmptyString, g_NoneNode }; + } + + /// Post-increment operator. + ConstIterator& ConstIterator::operator ++ (int dummy) + { + switch (m_Type) { + case SequenceType: + static_cast(m_pImp)->m_Iterator++; + break; + case MapType: + static_cast(m_pImp)->m_Iterator++; + break; + default: + break; + } + return *this; + } + + /// Post-decrement operator. + ConstIterator& ConstIterator::operator -- (int dummy) + { + switch (m_Type) { + case SequenceType: + static_cast(m_pImp)->m_Iterator--; + break; + case MapType: + static_cast(m_pImp)->m_Iterator--; + break; + default: + break; + } + return *this; + } + + /// Check if iterator is equal to other iterator. + bool ConstIterator::operator == (const ConstIterator& it) + { + if (m_Type != it.m_Type) { + return false; + } + + switch (m_Type) { + case SequenceType: + return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; + break; + case MapType: + return static_cast(m_pImp)->m_Iterator == static_cast(it.m_pImp)->m_Iterator; + break; + default: + break; + } + + return false; + } + + /// Check if iterator is not equal to other iterator. + bool ConstIterator::operator != (const ConstIterator & it) + { + return !(*this == it); + } + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the Node class. + /// + Node::Node() : + m_pImp(new NodeImp) + { + /* stub */ + } + /// + /// Copies an instance of the Node class to a new instance of the Node class. + /// + /// + Node::Node(const Node& node) : + Node() + { + *this = node; + } + /// + /// Initializes a new instance of the Node class. + /// + /// + Node::Node(const std::string& value) : + Node() + { + *this = value; + } + /// + /// Initializes a new instance of the Node class. + /// + Node::Node(const char* value) : + Node() + { + *this = value; + } + /// + /// Finalizes a instance of the Node class. + /// + Node::~Node() + { + delete static_cast(m_pImp); + } + + /// Gets the type of node. + /// + Node::eType Node::type() const + { + return NODE_IMP->m_Type; + } + + /// Checks if the node contains nothing. + /// + bool Node::isNone() const + { + return NODE_IMP->m_Type == Node::None; + } + + /// Checks if the node is a sequence node. + /// + bool Node::isSequence() const + { + return NODE_IMP->m_Type == Node::SequenceType; + } + + /// Checks if the node is a map node. + /// + bool Node::isMap() const + { + return NODE_IMP->m_Type == Node::MapType; + } + + /// Checks if the node is a scalar node. + /// + bool Node::isScalar() const + { + return NODE_IMP->m_Type == Node::ScalarType; + } + + /// Completely clear node. + void Node::clear() + { + NODE_IMP->clear(); + } + + /// Get size of node. Nodes of type None or Scalar will return 0. + /// + size_t Node::size() const + { + if (TYPE_IMP == nullptr) { + return 0; + } + + return TYPE_IMP->size(); + } + + /// + /// Insert sequence item at given index. Converts node to sequence type if needed. + /// Adding new item to end of sequence if index is larger than sequence size. + /// + /// + /// + Node& Node::insert(const size_t index) + { + NODE_IMP->initSequence(); + return *TYPE_IMP->insert(index); + } + + /// Add new sequence index to back. Converts node to sequence type if needed. + /// + Node& Node::push_front() + { + NODE_IMP->initSequence(); + return *TYPE_IMP->push_front(); + } + /// Add new sequence index to front. Converts node to sequence type if needed. + /// + Node& Node::push_back() + { + NODE_IMP->initSequence(); + return *TYPE_IMP->push_back(); + } + + /// Get sequence/map item. Converts node to sequence/map type if needed. + /// + /// + Node& Node::operator[](const size_t index) + { + NODE_IMP->initSequence(); + Node* pNode = TYPE_IMP->getNode(index); + if (pNode == nullptr) { + g_NoneNode.clear(); + return g_NoneNode; + } + return *pNode; + } + /// Get sequence/map item. Converts node to sequence/map type if needed. + /// + /// + Node& Node::operator[](const std::string& key) + { + NODE_IMP->initMap(); + return *TYPE_IMP->getNode(key); + } + + /// Erase item. No action if node is not a sequence or map. + /// + void Node::erase(const size_t index) + { + if (TYPE_IMP == nullptr || NODE_IMP->m_Type != Node::SequenceType) { + return; + } + + return TYPE_IMP->erase(index); + } + /// Erase item. No action if node is not a sequence or map. + /// + void Node::erase(const std::string& key) + { + if (TYPE_IMP == nullptr || NODE_IMP->m_Type != Node::MapType) { + return; + } + + return TYPE_IMP->erase(key); + } + + /// Assignment operator. + Node& Node::operator = (const Node& node) + { + NODE_IMP->clear(); + CopyNode(node, *this); + return *this; + } + /// Assignment operator. + Node& Node::operator = (const std::string& value) + { + NODE_IMP->initScalar(); + TYPE_IMP->setData(value); + return *this; + } + /// Assignment operator. + Node& Node::operator = (const char* value) + { + NODE_IMP->initScalar(); + TYPE_IMP->setData(value ? std::string(value) : ""); + return *this; + } + + /// Get start iterator. + /// + Iterator Node::begin() + { + Iterator it; + if (TYPE_IMP != nullptr) { + IteratorImp* pItImp = nullptr; + switch (NODE_IMP->m_Type) { + case Node::SequenceType: + it.m_Type = Iterator::SequenceType; + pItImp = new SequenceIteratorImp; + pItImp->initBegin(static_cast(TYPE_IMP)); + break; + case Node::MapType: + it.m_Type = Iterator::MapType; + pItImp = new MapIteratorImp; + pItImp->initBegin(static_cast(TYPE_IMP)); + break; + default: + break; + } + + it.m_pImp = pItImp; + } + + return it; + } + /// Get start constant iterator. + /// + ConstIterator Node::begin() const + { + ConstIterator it; + if (TYPE_IMP != nullptr) { + IteratorImp* pItImp = nullptr; + switch (NODE_IMP->m_Type) { + case Node::SequenceType: + it.m_Type = ConstIterator::SequenceType; + pItImp = new SequenceConstIteratorImp; + pItImp->initBegin(static_cast(TYPE_IMP)); + break; + case Node::MapType: + it.m_Type = ConstIterator::MapType; + pItImp = new MapConstIteratorImp; + pItImp->initBegin(static_cast(TYPE_IMP)); + break; + default: + break; + } + + it.m_pImp = pItImp; + } + + return it; + } + + /// Get end iterator. + /// + Iterator Node::end() + { + Iterator it; + if (TYPE_IMP != nullptr) { + IteratorImp* pItImp = nullptr; + switch (NODE_IMP->m_Type) { + case Node::SequenceType: + it.m_Type = Iterator::SequenceType; + pItImp = new SequenceIteratorImp; + pItImp->initEnd(static_cast(TYPE_IMP)); + break; + case Node::MapType: + it.m_Type = Iterator::MapType; + pItImp = new MapIteratorImp; + pItImp->initEnd(static_cast(TYPE_IMP)); + break; + default: + break; + } + + it.m_pImp = pItImp; + } + + return it; + } + /// Get end constant iterator. + /// + ConstIterator Node::end() const + { + ConstIterator it; + if (TYPE_IMP != nullptr) { + IteratorImp* pItImp = nullptr; + switch (NODE_IMP->m_Type) { + case Node::SequenceType: + it.m_Type = ConstIterator::SequenceType; + pItImp = new SequenceConstIteratorImp; + pItImp->initEnd(static_cast(TYPE_IMP)); + break; + case Node::MapType: + it.m_Type = ConstIterator::MapType; + pItImp = new MapConstIteratorImp; + pItImp->initEnd(static_cast(TYPE_IMP)); + break; + default: + break; + } + + it.m_pImp = pItImp; + } + + return it; + } + + // --------------------------------------------------------------------------- + // Private Class Members + // --------------------------------------------------------------------------- + /// + /// + const std::string& Node::asString() const + { + if (TYPE_IMP == nullptr) { + return g_EmptyString; + } + + return TYPE_IMP->getData(); + } + + // Reader implementations + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + class ReaderLine { + public: + /// + /// Initializes a new instance of the ReaderLine class. + /// + /// + /// + /// + /// + /// + ReaderLine(const std::string& data = "", const size_t no = 0, const size_t offset = 0, + const Node::eType type = Node::None, const unsigned char flags = 0) : + Data(data), + No(no), + Offset(offset), + Type(type), + Flags(flags), + NextLine(nullptr) + { + /* stub */ + } + + enum eFlag + { + LiteralScalarFlag, // Literal scalar type, defined as "|". + FoldedScalarFlag, // Folded scalar type, defined as "<". + ScalarNewlineFlag // Scalar ends with a newline. + }; + + /// Set flag. + /// + void setFlag(const eFlag flag) + { + Flags |= FlagMask[static_cast(flag)]; + } + /// Set flags by mask value. + /// + void setFlags(const unsigned char flags) + { + Flags |= flags; + } + + /// Unset flag. + /// + void unsetFlag(const eFlag flag) + { + Flags &= ~FlagMask[static_cast(flag)]; + } + /// Unset flags by mask value. + /// + void unsetFlags(const unsigned char flags) + { + Flags &= ~flags; + } + + /// Get flag value. + /// + /// + bool getFlag(const eFlag flag) const + { + return Flags & FlagMask[static_cast(flag)]; + } + + /// Copy and replace scalar flags from another ReaderLine. + /// + void copyScalarFlags(ReaderLine * from) + { + if (from == nullptr) { + return; + } + + unsigned char newFlags = from->Flags & (FlagMask[0] | FlagMask[1] | FlagMask[2]); + Flags |= newFlags; + } + + static const unsigned char FlagMask[3]; + + std::string Data; // Data of line. + size_t No; // Line number. + size_t Offset; // Offset to first character in data. + Node::eType Type; // Type of line. + unsigned char Flags; // Flags of line. + ReaderLine* NextLine; // Pointer to next line. + }; + + const unsigned char ReaderLine::FlagMask[3] = { 0x01, 0x02, 0x04 }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implementation class of Yaml parsing. + // Parsing incoming streamand outputs a root node. + // --------------------------------------------------------------------------- + + class ParseImp { + public: + /// + /// Initializes a new instance of the ParseImp class. + /// + ParseImp() + { + /* stub */ + } + /// + /// Finalizes a new instance of the ParseImp class. + /// + ~ParseImp() + { + clearLines(); + } + + /// Run full parsing procedure. + /// + /// + void parse(Node& root, std::iostream& stream) + { + try + { + root.clear(); + readLines(stream); + postProcessLines(); + parseRoot(root); + } + catch (Exception e) + { + root.clear(); + throw; + } + } + + private: + /// + /// Copies a instance of the ParseImp class to new instance of the ParseImp class. + /// + /// + ParseImp(const ParseImp& copy) + { + /* stub */ + } + + /// Read all lines. + /// + void readLines(std::iostream& stream) + { + std::string line = ""; + size_t lineNo = 0; + bool documentStartFound = false; + bool foundFirstNotEmpty = false; + std::streampos streamPos = 0; + + // read all lines, as long as the stream is ok + while (!stream.eof() && !stream.fail()) { + // read line + streamPos = stream.tellg(); + std::getline(stream, line); + lineNo++; + + // remove comment + const size_t commentPos = FindNotCited(line, '#'); + if (commentPos != std::string::npos) { + line.resize(commentPos); + } + + // start of document + if (documentStartFound == false && line == "---") { + // erase all lines before this line + clearLines(); + documentStartFound = true; + continue; + } + + // end of document + if (line == "...") { + break; + } + else if (line == "---") { + stream.seekg(streamPos); + break; + } + + // remove trailing return + if (line.size()) { + if (line[line.size() - 1] == '\r') { + line.resize(line.size() - 1); + } + } + + // validate characters + for (size_t i = 0; i < line.size(); i++) { + if (line[i] != '\t' && (line[i] < 32 || line[i] > 125)) { + throw ParsingException(ExceptionMessage(g_ErrorInvalidCharacter, lineNo, i + 1)); + } + } + + // validate tabs + const size_t firstTabPos = line.find_first_of('\t'); + size_t startOffset = line.find_first_not_of(" \t"); + + // make sure no tabs are in the very front + if (startOffset != std::string::npos) { + if (firstTabPos < startOffset) { + throw ParsingException(ExceptionMessage(g_ErrorTabInOffset, lineNo, firstTabPos)); + } + + // remove front spaces + line = line.substr(startOffset); + } + else { + startOffset = 0; + line = ""; + } + + // add line + if (foundFirstNotEmpty == false) { + if (line.size()) { + foundFirstNotEmpty = true; + } + else { + continue; + } + } + + ReaderLine* pLine = new ReaderLine(line, lineNo, startOffset); + m_Lines.push_back(pLine); + } + } + + /// Run post-processing on all lines. Basically split lines into multiple lines if needed, to follow the parsing algorithm. + void postProcessLines() + { + for (auto it = m_Lines.begin(); it != m_Lines.end();) { + // sequence + if (postProcessSequenceLine(it) == true) { + continue; + } + + // mapping + if (postProcessMappingLine(it) == true) { + continue; + } + + // scalar + postProcessScalarLine(it); + } + + // set next line of all lines + if (m_Lines.size()) { + if (m_Lines.back()->Type != Node::ScalarType) { + throw ParsingException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *m_Lines.back())); + } + + if (m_Lines.size() > 1) { + auto prevEnd = m_Lines.end(); + --prevEnd; + + for (auto it = m_Lines.begin(); it != prevEnd; it++) { + auto nextIt = it; + ++nextIt; + + (*it)->NextLine = *nextIt; + } + } + } + } + + /// Run post-processing and check for sequence. Split line into two lines if sequence token is not on it's own line. + /// + /// True if line is sequence, else false. + bool postProcessSequenceLine(std::list::iterator & it) + { + ReaderLine* pLine = *it; + + // sequence split + if (isSequenceStart(pLine->Data) == false) { + return false; + } + + pLine->Type = Node::SequenceType; + clearTrailingEmptyLines(++it); + + const size_t valueStart = pLine->Data.find_first_not_of(" \t", 1); + if (valueStart == std::string::npos) { + return true; + } + + // create new line and insert + std::string newLine = pLine->Data.substr(valueStart); + it = m_Lines.insert(it, new ReaderLine(newLine, pLine->No, pLine->Offset + valueStart)); + pLine->Data = ""; + + return false; + } + + /// Run post-processing and check for mapping. Split line into two lines if mapping value is not on it's own line. + /// + /// True if line is mapping, else move on to scalar parsing. + bool postProcessMappingLine(std::list::iterator & it) + { + ReaderLine* pLine = *it; + + // find map key + size_t preKeyQuotes = 0; + size_t tokenPos = FindNotCited(pLine->Data, ':', preKeyQuotes); + if (tokenPos == std::string::npos) { + return false; + } + + if (preKeyQuotes > 1) { + throw ParsingException(ExceptionMessage(g_ErrorKeyIncorrect, *pLine)); + } + + pLine->Type = Node::MapType; + + // get key + std::string key = pLine->Data.substr(0, tokenPos); + const size_t keyEnd = key.find_last_not_of(" \t"); + if (keyEnd == std::string::npos) { + throw ParsingException(ExceptionMessage(g_ErrorKeyMissing, *pLine)); + } + key.resize(keyEnd + 1); + + // handle cited key + if (preKeyQuotes == 1) { + if (key.front() != '"' || key.back() != '"') { + throw ParsingException(ExceptionMessage(g_ErrorKeyIncorrect, *pLine)); + } + + key = key.substr(1, key.size() - 2); + } + RemoveAllEscapeTokens(key); + + // get value + std::string value = ""; + size_t valueStart = std::string::npos; + if (tokenPos + 1 != pLine->Data.size()) { + valueStart = pLine->Data.find_first_not_of(" \t", tokenPos + 1); + if (valueStart != std::string::npos) { + value = pLine->Data.substr(valueStart); + } + } + + // make sure the value is not a sequence start + if (isSequenceStart(value) == true) { + throw ParsingException(ExceptionMessage(g_ErrorBlockSequenceNotAllowed, *pLine, valueStart)); + } + + pLine->Data = key; + + // remove all empty lines after map key + clearTrailingEmptyLines(++it); + + // add new empty line? + size_t newLineOffset = valueStart; + if (newLineOffset == std::string::npos) { + if (it != m_Lines.end() && (*it)->Offset > pLine->Offset) { + return true; + } + + newLineOffset = tokenPos + 2; + } + else { + newLineOffset += pLine->Offset; + } + + // add new line with value + unsigned char dummyBlockFlags = 0; + if (isBlockScalar(value, pLine->No, dummyBlockFlags) == true) { + newLineOffset = pLine->Offset; + } + + ReaderLine* pNewLine = new ReaderLine(value, pLine->No, newLineOffset, Node::ScalarType); + it = m_Lines.insert(it, pNewLine); + + // return false in order to handle next line(scalar value) + return false; + } + + /// Run post-processing and check for scalar. Checking for multi-line scalars. + /// + void postProcessScalarLine(std::list::iterator & it) + { + ReaderLine* pLine = *it; + pLine->Type = Node::ScalarType; + + size_t parentOffset = pLine->Offset; + if (pLine != m_Lines.front()) { + std::list::iterator lastIt = it; + --lastIt; + parentOffset = (*lastIt)->Offset; + } + + std::list::iterator lastNotEmpty = it++; + + // find last empty lines + while (it != m_Lines.end()) { + pLine = *it; + pLine->Type = Node::ScalarType; + if (pLine->Data.size()) { + if (pLine->Offset <= parentOffset) { + break; + } + else { + lastNotEmpty = it; + } + } + ++it; + } + + clearTrailingEmptyLines(++lastNotEmpty); + } + + /// Process root node and start of document. + /// + void parseRoot(Node & root) + { + // get first line and start type + auto it = m_Lines.begin(); + if (it == m_Lines.end()) { + return; + } + + Node::eType type = (*it)->Type; + ReaderLine* pLine = *it; + + // handle next line + switch (type) { + case Node::SequenceType: + parseSequence(root, it); + break; + case Node::MapType: + parseMap(root, it); + break; + case Node::ScalarType: + parseScalar(root, it); + break; + default: + break; + } + + if (it != m_Lines.end()) { + throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); + } + + } + + /// Process sequence node. + /// + /// + void parseSequence(Node & node, std::list::iterator & it) + { + ReaderLine* pNextLine = nullptr; + while (it != m_Lines.end()) { + ReaderLine* pLine = *it; + Node& childNode = node.push_back(); + + // move to next line, error check + ++it; + if (it == m_Lines.end()) { + throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); + } + + // handle value of map + Node::eType valueType = (*it)->Type; + switch (valueType) { + case Node::SequenceType: + parseSequence(childNode, it); + break; + case Node::MapType: + parseMap(childNode, it); + break; + case Node::ScalarType: + parseScalar(childNode, it); + break; + default: + break; + } + + // check next line; if sequence and correct level, go on, else exit + // if same level but but of type map = error + if (it == m_Lines.end() || ((pNextLine = *it)->Offset < pLine->Offset)) { + break; + } + + if (pNextLine->Offset > pLine->Offset) { + throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pNextLine)); + } + + if (pNextLine->Type != Node::SequenceType) { + throw InternalException(ExceptionMessage(g_ErrorDiffEntryNotAllowed, *pNextLine)); + } + } + } + + /// Process map node. + /// + /// + void parseMap(Node & node, std::list::iterator & it) + { + ReaderLine* pNextLine = nullptr; + while (it != m_Lines.end()) { + ReaderLine* pLine = *it; + Node& childNode = node[pLine->Data]; + + // move to next line, error check + ++it; + if (it == m_Lines.end()) { + throw InternalException(ExceptionMessage(g_ErrorUnexpectedDocumentEnd, *pLine)); + } + + // handle value of map + Node::eType valueType = (*it)->Type; + switch (valueType) { + case Node::SequenceType: + parseSequence(childNode, it); + break; + case Node::MapType: + parseMap(childNode, it); + break; + case Node::ScalarType: + parseScalar(childNode, it); + break; + default: + break; + } + + // check next line; if map and correct level, go on, else exit + // if same level but but of type map = error + if (it == m_Lines.end() || ((pNextLine = *it)->Offset < pLine->Offset)) { + break; + } + + if (pNextLine->Offset > pLine->Offset) { + throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pNextLine)); + } + + if (pNextLine->Type != pLine->Type) { + throw InternalException(ExceptionMessage(g_ErrorDiffEntryNotAllowed, *pNextLine)); + } + } + } + + /// Process scalar node. + /// + /// + void parseScalar(Node & node, std::list::iterator & it) + { + std::string data = ""; + ReaderLine* pFirstLine = *it; + ReaderLine* pLine = *it; + + // check if current line is a block scalar + unsigned char blockFlags = 0; + bool blockScalar = isBlockScalar(pLine->Data, pLine->No, blockFlags); + const bool newLineFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]); + const bool foldedFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::FoldedScalarFlag)]); + const bool literalFlag = static_cast(blockFlags & ReaderLine::FlagMask[static_cast(ReaderLine::LiteralScalarFlag)]); + size_t parentOffset = 0; + + // find parent offset + if (it != m_Lines.begin()) { + std::list::iterator parentIt = it; + --parentIt; + parentOffset = (*parentIt)->Offset; + } + + // move to next iterator/line if current line is a block scalar + if (blockScalar) { + ++it; + if (it == m_Lines.end() || (pLine = *it)->Type != Node::ScalarType) { + return; + } + } + + // not a block scalar, cut end spaces/tabs + if (blockScalar == false) { + while (true) { + pLine = *it; + if (parentOffset != 0 && pLine->Offset <= parentOffset) { + throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); + } + + const size_t endOffset = pLine->Data.find_last_not_of(" \t"); + if (endOffset == std::string::npos) { + data += "\n"; + } + else { + data += pLine->Data.substr(0, endOffset + 1); + } + + // Move to next line + ++it; + if (it == m_Lines.end() || (*it)->Type != Node::ScalarType) { + break; + } + + data += " "; + } + + if (ValidateQuote(data) == false) { + throw ParsingException(ExceptionMessage(g_ErrorInvalidQuote, *pFirstLine)); + } + } + else { + // block scalar + pLine = *it; + size_t blockOffset = pLine->Offset; + if (blockOffset <= parentOffset) { + throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); + } + + bool addedSpace = false; + while (it != m_Lines.end() && (*it)->Type == Node::ScalarType) { + pLine = *it; + + const size_t endOffset = pLine->Data.find_last_not_of(" \t"); + if (endOffset != std::string::npos && pLine->Offset < blockOffset) { + throw ParsingException(ExceptionMessage(g_ErrorIncorrectOffset, *pLine)); + } + + if (endOffset == std::string::npos) { + if (addedSpace) { + data[data.size() - 1] = '\n'; + addedSpace = false; + } + else { + data += "\n"; + } + + ++it; + continue; + } + else + { + if (blockOffset != pLine->Offset && foldedFlag) { + if (addedSpace) { + data[data.size() - 1] = '\n'; + addedSpace = false; + } + else { + data += "\n"; + } + } + data += std::string(pLine->Offset - blockOffset, ' '); + data += pLine->Data; + } + + // move to next line + ++it; + if (it == m_Lines.end() || (*it)->Type != Node::ScalarType) { + if (newLineFlag) { + data += "\n"; + } + break; + } + + if (foldedFlag) { + data += " "; + addedSpace = true; + } + else if (literalFlag && endOffset != std::string::npos) { + data += "\n"; + } + } + } + + if (data.size() && (data[0] == '"' || data[0] == '\'')) { + data = data.substr(1, data.size() - 2); + } + + node = data; + } + + /// Clear all read lines. + /// + void clearTrailingEmptyLines(std::list::iterator & it) + { + while (it != m_Lines.end()) { + ReaderLine* pLine = *it; + if (pLine->Data.size() == 0) { + delete* it; + it = m_Lines.erase(it); + } + else { + return; + } + + } + } + + /// + /// + /// + static bool isSequenceStart(const std::string & data) + { + if (data.size() == 0 || data[0] != '-') { + return false; + } + + if (data.size() >= 2 && data[1] != ' ') { + return false; + } + + return true; + } + + /// + /// + /// + /// + static bool isBlockScalar(const std::string & data, const size_t line, unsigned char& flags) + { + flags = 0; + if (data.size() == 0) { + return false; + } + + if (data[0] == '|') { + if (data.size() >= 2) { + if (data[1] != '-' && data[1] != ' ' && data[1] != '\t') { + throw ParsingException(ExceptionMessage(g_ErrorInvalidBlockScalar, line, data)); + } + } + else { + flags |= ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]; + } + flags |= ReaderLine::FlagMask[static_cast(ReaderLine::LiteralScalarFlag)]; + return true; + } + + if (data[0] == '>') { + if (data.size() >= 2) { + if (data[1] != '-' && data[1] != ' ' && data[1] != '\t') { + throw ParsingException(ExceptionMessage(g_ErrorInvalidBlockScalar, line, data)); + } + } + else { + flags |= ReaderLine::FlagMask[static_cast(ReaderLine::ScalarNewlineFlag)]; + } + flags |= ReaderLine::FlagMask[static_cast(ReaderLine::FoldedScalarFlag)]; + return true; + } + + return false; + } + + std::list m_Lines; // List of lines. + }; + + // --------------------------------------------------------------------------- + // Parsing Functions + // --------------------------------------------------------------------------- + /// Populate given root node with deserialized data. + /// Root node to populate. + /// Path of input file. + bool Parse(Node& root, const char* filename) + { + std::ifstream f(filename, std::ifstream::binary); + if (f.is_open() == false) { + throw OperationException(g_ErrorCannotOpenFile); + } + + f.seekg(0, f.end); + size_t fileSize = static_cast(f.tellg()); + f.seekg(0, f.beg); + + std::unique_ptr data(new char[fileSize]); + f.read(data.get(), fileSize); + f.close(); + + return Parse(root, data.get(), fileSize); + } + /// Populate given root node with deserialized data. + /// Root node to populate. + /// Input stream. + bool Parse(Node& root, std::iostream& stream) + { + ParseImp* pImp = nullptr; + + try + { + pImp = new ParseImp; + pImp->parse(root, stream); + delete pImp; + return true; + } + catch (const Exception e) + { + delete pImp; + return false; + } + } + /// Populate given root node with deserialized data. + /// Root node to populate. + /// String of input data. + bool Parse(Node& root, const std::string& string) + { + std::stringstream ss(string); + return Parse(root, ss); + } + /// Populate given root node with deserialized data. + /// Character array of input data. + /// Buffer size. + bool Parse(Node& root, const char* buffer, const size_t size) + { + std::stringstream ss(std::string(buffer, size)); + return Parse(root, ss); + } + + + // --------------------------------------------------------------------------- + // Public Class Members + // --------------------------------------------------------------------------- + /// + /// Initializes a new instance of the SerializeConfig struct. + /// + /// Number of spaces per indentation. + /// Maximum length of scalars. Serialized as folder scalars if exceeded. Ignored if equal to 0. + /// Put maps on a new line if parent node is a sequence. + /// Put scalars on a new line if parent node is a map. + SerializeConfig::SerializeConfig(const size_t spaceIndentation, const size_t scalarMaxLength, + const bool sequenceMapNewline, const bool mapScalarNewline) : + SpaceIndentation(spaceIndentation), + ScalarMaxLength(scalarMaxLength), + SequenceMapNewline(sequenceMapNewline), + MapScalarNewline(mapScalarNewline) + { + /* stub */ + } + + // --------------------------------------------------------------------------- + // Serialization Functions + // --------------------------------------------------------------------------- + /// Serialize node data. + /// Root node to serialize. + /// Path of output file. + /// Serialization configuration. + void Serialize(const Node& root, const char* filename, const SerializeConfig& config) + { + std::stringstream stream; + Serialize(root, stream, config); + + std::ofstream f(filename); + if (f.is_open() == false) { + throw OperationException(g_ErrorCannotOpenFile); + } + + f.write(stream.str().c_str(), stream.str().size()); + f.close(); + } + + /// + /// + /// + /// + /// + size_t LineFolding(const std::string& input, std::vector& folded, const size_t maxLength) + { + folded.clear(); + if (input.size() == 0) { + return 0; + } + + size_t currentPos = 0; + size_t lastPos = 0; + size_t spacePos = std::string::npos; + while (currentPos < input.size()) { + currentPos = lastPos + maxLength; + + if (currentPos < input.size()) { + spacePos = input.find_first_of(' ', currentPos); + } + + if (spacePos == std::string::npos || currentPos >= input.size()) { + const std::string endLine = input.substr(lastPos); + if (endLine.size()) { + folded.push_back(endLine); + } + + return folded.size(); + } + + folded.push_back(input.substr(lastPos, spacePos - lastPos)); + + lastPos = spacePos + 1; + } + + return folded.size(); + } + + /// + /// + /// + /// + /// + /// + static void SerializeLoop(const Node& node, std::iostream& stream, bool useLevel, const size_t level, const SerializeConfig& config) + { + const size_t indention = config.SpaceIndentation; + switch (node.type()) { + case Node::SequenceType: + { + for (auto it = node.begin(); it != node.end(); it++) { + const Node& value = (*it).second; + if (value.isNone()) { + continue; + } + stream << std::string(level, ' ') << "- "; + useLevel = false; + if (value.isSequence() || (value.isMap() && config.SequenceMapNewline == true)) { + useLevel = true; + stream << "\n"; + } + + SerializeLoop(value, stream, useLevel, level + 2, config); + } + + } + break; + case Node::MapType: + { + size_t count = 0; + for (auto it = node.begin(); it != node.end(); it++) { + const Node& value = (*it).second; + if (value.isNone()) { + continue; + } + + if (useLevel || count > 0) { + stream << std::string(level, ' '); + } + + std::string key = (*it).first; + AddEscapeTokens(key, "\\\""); + if (ShouldBeCited(key)) { + stream << "\"" << key << "\"" << ": "; + } + else { + stream << key << ": "; + } + + + useLevel = false; + if (value.isScalar() == false || (value.isScalar() && config.MapScalarNewline)) { + useLevel = true; + stream << "\n"; + } + + SerializeLoop(value, stream, useLevel, level + indention, config); + + useLevel = true; + count++; + } + + } + break; + case Node::ScalarType: + { + const std::string value = node.as(); + + // empty scalar + if (value.size() == 0) { + stream << "\n"; + break; + } + + // get lines of scalar + std::string line = ""; + std::vector lines; + std::istringstream iss(value); + while (iss.eof() == false) { + std::getline(iss, line); + lines.push_back(line); + } + + // block scalar + const std::string& lastLine = lines.back(); + const bool endNewline = lastLine.size() == 0; + if (endNewline) { + lines.pop_back(); + } + + // literal + if (lines.size() > 1) { + stream << "|"; + } + // folded/plain + else { + const std::string frontLine = lines.front(); + if (config.ScalarMaxLength == 0 || lines.front().size() <= config.ScalarMaxLength || + LineFolding(frontLine, lines, config.ScalarMaxLength) == 1) { + if (useLevel) { + stream << std::string(level, ' '); + } + + if (ShouldBeCited(value)) { + stream << "\"" << value << "\"\n"; + break; + } + stream << value << "\n"; + break; + } + else { + stream << ">"; + } + } + + if (endNewline == false) { + stream << "-"; + } + stream << "\n"; + + for (auto it = lines.begin(); it != lines.end(); it++) { + stream << std::string(level, ' ') << (*it) << "\n"; + } + } + break; + + default: + break; + } + } + + /// Serialize node data. + /// Root node to serialize. + /// Output stream. + /// Serialization configuration. + void Serialize(const Node& root, std::iostream& stream, const SerializeConfig& config) + { + if (config.SpaceIndentation < 2) { + throw OperationException(g_ErrorIndentation); + } + + SerializeLoop(root, stream, false, 0, config); + } + + /// Serialize node data. + /// Root node to serialize. + /// String of output data. + /// Serialization configuration. + void Serialize(const Node& root, std::string& string, const SerializeConfig& config) + { + std::stringstream stream; + Serialize(root, stream, config); + string = stream.str(); + } + + // --------------------------------------------------------------------------- + // Global Functions + // --------------------------------------------------------------------------- + std::string ExceptionMessage(const std::string& message, ReaderLine& line) + { + return message + std::string(" Line ") + std::to_string(line.No) + std::string(": ") + line.Data; + } + + std::string ExceptionMessage(const std::string& message, ReaderLine& line, const size_t errorPos) + { + return message + std::string(" Line ") + std::to_string(line.No) + std::string(" column ") + std::to_string(errorPos + 1) + std::string(": ") + line.Data; + } + + std::string ExceptionMessage(const std::string& message, const size_t errorLine, const size_t errorPos) + { + return message + std::string(" Line ") + std::to_string(errorLine) + std::string(" column ") + std::to_string(errorPos); + } + + std::string ExceptionMessage(const std::string& message, const size_t errorLine, const std::string& data) + { + return message + std::string(" Line ") + std::to_string(errorLine) + std::string(": ") + data; + } + + bool FindQuote(const std::string& input, size_t& start, size_t& end, size_t searchPos) + { + start = end = std::string::npos; + size_t qPos = searchPos; + bool foundStart = false; + + while (qPos != std::string::npos) { + // find first quote + qPos = input.find_first_of("\"'", qPos); + if(qPos == std::string::npos) { + return false; + } + + const char token = input[qPos]; + if (token == '"' && (qPos == 0 || input[qPos-1] != '\\')) { + // found start quote + if (foundStart == false) { + start = qPos; + foundStart = true; + } + // found end quote + else { + end = qPos; + return true; + } + } + + // check if it's possible for another loop + if (qPos + 1 == input.size()) { + return false; + } + qPos++; + } + + return false; + } + + size_t FindNotCited(const std::string& input, char token, size_t& preQuoteCount) + { + preQuoteCount = 0; + size_t tokenPos = input.find_first_of(token); + if (tokenPos == std::string::npos) { + return std::string::npos; + } + + // find all quotes + std::vector> quotes; + + size_t quoteStart = 0; + size_t quoteEnd = 0; + while (FindQuote(input, quoteStart, quoteEnd, quoteEnd)) { + quotes.push_back({quoteStart, quoteEnd}); + + if (quoteEnd + 1 == input.size()) { + break; + } + quoteEnd++; + } + + if (quotes.size() == 0) { + return tokenPos; + } + + size_t currentQuoteIndex = 0; + std::pair currentQuote = {0, 0}; + while (currentQuoteIndex < quotes.size()) { + currentQuote = quotes[currentQuoteIndex]; + + if (tokenPos < currentQuote.first) { + return tokenPos; + } + + preQuoteCount++; + if (tokenPos <= currentQuote.second) { + // find next token + if (tokenPos + 1 == input.size()) { + return std::string::npos; + } + + tokenPos = input.find_first_of(token, tokenPos + 1); + if (tokenPos == std::string::npos) { + return std::string::npos; + } + } + + currentQuoteIndex++; + } + + return tokenPos; + } + + size_t FindNotCited(const std::string& input, char token) + { + size_t dummy = 0; + return FindNotCited(input, token, dummy); + } + + bool ValidateQuote(const std::string& input) + { + if (input.size() == 0) { + return true; + } + + char token = 0; + size_t searchPos = 0; + if (input[0] == '\"' || input[0] == '\'') { + if (input.size() == 1) { + return false; + } + token = input[0]; + searchPos = 1; + } + + while (searchPos != std::string::npos && searchPos < input.size() - 1) { + searchPos = input.find_first_of("\"'", searchPos + 1); + if(searchPos == std::string::npos) { + break; + } + + const char foundToken = input[searchPos]; + + if (input[searchPos] == '\"' || input[searchPos] == '\'') { + if (token == 0 && input[searchPos-1] != '\\') { + return false; + } + + if (foundToken == token && input[searchPos-1] != '\\') { + if(searchPos == input.size() - 1) + { + return true; + } + return false; + } + } + } + + return token == 0; + } + + void CopyNode(const Node& from, Node& to) + { + const Node::eType type = from.type(); + + switch(type) { + case Node::SequenceType: + for (auto it = from.begin(); it != from.end(); it++) { + const Node & currentNode = (*it).second; + Node & newNode = to.push_back(); + CopyNode(currentNode, newNode); + } + break; + case Node::MapType: + for (auto it = from.begin(); it != from.end(); it++) { + const Node & currentNode = (*it).second; + Node & newNode = to[(*it).first]; + CopyNode(currentNode, newNode); + } + break; + case Node::ScalarType: + to = from.as(); + break; + case Node::None: + break; + } + } + + bool ShouldBeCited(const std::string& key) + { + return key.find_first_of("\":{}[],&*#?|-<>=!%@") != std::string::npos; + } + + void AddEscapeTokens(std::string& input, const std::string& tokens) + { + for (auto it = tokens.begin(); it != tokens.end(); it++) { + const char token = *it; + const std::string replace = std::string("\\") + std::string(1, token); + size_t found = input.find_first_of(token); + while (found != std::string::npos) { + input.replace(found, 1, replace); + found = input.find_first_of(token, found + 2); + } + } + } + + void RemoveAllEscapeTokens(std::string & input) + { + size_t found = input.find_first_of("\\"); + while (found != std::string::npos) { + if (found + 1 == input.size()) { + return; + } + + std::string replace(1, input[found + 1]); + input.replace(found, 2, replace); + found = input.find_first_of("\\", found + 1); + } + } +} // namespace yaml diff --git a/yaml/Yaml.h b/yaml/Yaml.h new file mode 100644 index 00000000..5f3a2849 --- /dev/null +++ b/yaml/Yaml.h @@ -0,0 +1,478 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the mini-yaml project. (https://github.com/jimmiebergmann/mini-yaml) +// Licensed under the MIT License (https://opensource.org/licenses/MIT) +// +/* +* Copyright(c) 2018 Jimmie Bergmann +* Copyright (C) 2020 Bryan Biedenkapp N2PLL +* +* MIT License +* +* 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. +* +*/ +/* +* YAML documentation: +* http://yaml.org/spec/1.0/index.html +* https://www.codeproject.com/Articles/28720/YAML-Parser-in-C +*/ +#if !defined(__YAML_H__) +#define __YAML_H__ + +#include "Defines.h" + +#include +#include +#include +#include +#include +#include + +namespace yaml +{ + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + class HOST_SW_API Node; + + // --------------------------------------------------------------------------- + // Helper classes and functions + // --------------------------------------------------------------------------- + namespace impl + { + // --------------------------------------------------------------------------- + // Class Declaration + // Helper functionality, converting string to any data type. + // Strings are left untouched. + // --------------------------------------------------------------------------- + + template + struct StringConverter { + /// + /// + /// + static T get(const std::string& data) + { + T type; + std::stringstream ss(data); + ss >> type; + return type; + } + + /// + /// + /// + /// + static T get(const std::string& data, const T& defaultValue) + { + T type; + std::stringstream ss(data); + ss >> type; + + if (ss.fail()) { + return defaultValue; + } + + return type; + } + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + template<> + struct StringConverter { + /// + /// + /// + static std::string get(const std::string& data) + { + return data; + } + + /// + /// + /// + /// + static std::string get(const std::string& data, const std::string& defaultValue) + { + if (data.size() == 0) { + return defaultValue; + } + return data; + } + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // + // --------------------------------------------------------------------------- + + template<> + struct StringConverter { + /// + /// + /// + static bool get(const std::string& data) + { + std::string tmpData = data; + std::transform(tmpData.begin(), tmpData.end(), tmpData.begin(), ::tolower); + if (tmpData == "true" || tmpData == "yes" || tmpData == "1") { + return true; + } + + return false; + } + + /// + /// + /// + /// + static bool get(const std::string& data, const bool& defaultValue) + { + if (data.size() == 0) { + return defaultValue; + } + + return get(data); + } + }; + } // namespace impl + + // --------------------------------------------------------------------------- + // Class Declaration + // Exception class. + // --------------------------------------------------------------------------- + + class HOST_SW_API Exception : public std::runtime_error { + public: + + /// + /// Enumeration of exception types. + /// + enum eType + { + InternalError, // Internal error. + ParsingError, // Invalid parsing data. + OperationError // User operation error. + }; + + /// Initializes a new instance of the Exception class. + Exception(const std::string & message, const eType type); + + /// Get type of exception. + eType type() const; + + /// Get message of exception. + const char* message() const; + + private: + eType m_Type; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Internal exception class. + // --------------------------------------------------------------------------- + + class HOST_SW_API InternalException : public Exception { + public: + /// Initializes a new instance of the InternalException class. + InternalException(const std::string& message); + + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Parsing exception class. + // --------------------------------------------------------------------------- + + class HOST_SW_API ParsingException : public Exception { + public: + /// Initializes a new instance of the ParsingException class. + ParsingException(const std::string & message); + + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Parsing exception class. + // --------------------------------------------------------------------------- + + class HOST_SW_API OperationException : public Exception { + public: + /// Initializes a new instance of the OperationException class. + OperationException(const std::string & message); + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Iterator class. + // --------------------------------------------------------------------------- + + class HOST_SW_API Iterator { + public: + friend class Node; + + /// Initializes a new instance of the Iterator class. + Iterator(); + /// Copies an instance of the Iterator class to a new instance of the Iterator class. + Iterator(const Iterator & it); + /// Finalizes a instance of the Iterator class. + ~Iterator(); + + /// Assignment operator. + Iterator& operator = (const Iterator& it); + + /// Get node of iterator. First pair item is the key of map value, empty if type is sequence. + std::pair operator *(); + + /// Post-increment operator. + Iterator& operator ++ (int); + /// Post-decrement operator. + Iterator& operator -- (int); + + /// Check if iterator is equal to other iterator. + bool operator == (const Iterator& it); + + /// Check if iterator is not equal to other iterator. + bool operator != (const Iterator& it); + + private: + enum eType + { + None, + SequenceType, + MapType + }; + + eType m_Type; + void* m_pImp; + + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Constant iterator class. + // --------------------------------------------------------------------------- + + class HOST_SW_API ConstIterator { + public: + friend class Node; + + /// Initializes a new instance of the ConstIterator class. + ConstIterator(); + /// Copies an instance of the ConstIterator class to a new instance of the ConstIterator class. + ConstIterator(const ConstIterator & it); + /// Finalizes a instance of the ConstIterator class. + ~ConstIterator(); + + /// Assignment operator. + ConstIterator& operator = (const ConstIterator& it); + + /// Get node of iterator. First pair item is the key of map value, empty if type is sequence. + std::pair operator *(); + + /// Post-increment operator. + ConstIterator& operator ++ (int); + /// Post-decrement operator. + ConstIterator& operator -- (int); + + /// Check if iterator is equal to other iterator. + bool operator == (const ConstIterator& it); + + /// Check if iterator is not equal to other iterator. + bool operator != (const ConstIterator& it); + + private: + enum eType + { + None, + SequenceType, + MapType + }; + + eType m_Type; + void* m_pImp; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Node class. + // --------------------------------------------------------------------------- + + class HOST_SW_API Node { + public: + friend class Iterator; + + /// Enumeration of node types. + enum eType + { + None, + SequenceType, + MapType, + ScalarType + }; + + /// Initializes a new instance of the Node class. + Node(); + /// Copies an instance of the Node class to a new instance of the Node class. + Node(const Node& node); + /// Initializes a new instance of the Node class. + Node(const std::string& value); + /// Initializes a new instance of the Node class. + Node(const char* value); + /// Finalizes a instance of the Node class. + ~Node(); + + /// Gets the type of node. + eType type() const; + /// Checks if the node contains nothing. + bool isNone() const; + /// Checks if the node is a sequence node. + bool isSequence() const; + /// Checks if the node is a map node. + bool isMap() const; + /// Checks if the node is a scalar node. + bool isScalar() const; + + /// Completely clear node. + void clear(); + + /// Get node as given template type. + template + T as() const + { + return impl::StringConverter::get(asString()); + } + /// Get node as given template type with a default value if no value is found. + template + T as(const T& defaultValue) const + { + return impl::StringConverter::get(asString(), defaultValue); + } + + /// Get size of node. Nodes of type None or Scalar will return 0. + size_t size() const; + + // Sequence operators + /// Insert sequence item at given index. Converts node to sequence type if needed. + /// Adding new item to end of sequence if index is larger than sequence size. + Node& insert(const size_t index); + /// Add new sequence index to back. Converts node to sequence type if needed. + Node& push_front(); + /// Add new sequence index to front. Converts node to sequence type if needed. + Node& push_back(); + /// Get sequence/map item. Converts node to sequence/map type if needed. + Node& operator [] (const size_t index); + /// Get sequence/map item. Converts node to sequence/map type if needed. + Node& operator [] (const std::string& key); + + /// Erase item. No action if node is not a sequence or map. + void erase(const size_t index); + /// Erase item. No action if node is not a sequence or map. + void erase(const std::string& key); + + /// Assignment operator. + Node& operator = (const Node& node); + /// Assignment operator. + Node& operator = (const std::string& value); + /// Assignment operator. + Node& operator = (const char* value); + + /// Get start iterator. + Iterator begin(); + /// Get start constant iterator. + ConstIterator begin() const; + + /// Get end iterator. + Iterator end(); + /// Get end constant iterator. + ConstIterator end() const; + + private: + const std::string& asString() const; + void* m_pImp; + }; + + /// Populate given root node with deserialized data. + /// Root node to populate. + /// Path of input file. + bool Parse(Node& root, const char* filename); + /// Populate given root node with deserialized data. + /// Root node to populate. + /// Input stream. + bool Parse(Node& root, std::iostream& stream); + /// Populate given root node with deserialized data. + /// Root node to populate. + /// String of input data. + bool Parse(Node& root, const std::string& string); + /// Populate given root node with deserialized data. + /// Character array of input data. + /// Buffer size. + bool Parse(Node& root, const char* buffer, const size_t size); + + // --------------------------------------------------------------------------- + // Structure Declaration + // Serialization configuration structure, describing output behavior. + // --------------------------------------------------------------------------- + + struct SerializeConfig { + /// Initializes a new instance of the SerializeConfig struct. + /// Number of spaces per indentation. + /// Maximum length of scalars. Serialized as folder scalars if exceeded. Ignored if equal to 0. + /// Put maps on a new line if parent node is a sequence. + /// Put scalars on a new line if parent node is a map. + SerializeConfig(const size_t spaceIndentation = 2, const size_t scalarMaxLength = 64, const bool sequenceMapNewline = false, + const bool mapScalarNewline = false); + + size_t SpaceIndentation; // Number of spaces per indentation. + size_t ScalarMaxLength; // Maximum length of scalars. Serialized as folder scalars if exceeded. + bool SequenceMapNewline; // Put maps on a new line if parent node is a sequence. + bool MapScalarNewline; // Put scalars on a new line if parent node is a map. + }; + + /// Serialize node data. + /// Root node to serialize. + /// Path of output file. + /// Serialization configuration. + void Serialize(const Node& root, const char* filename, const SerializeConfig& config = {2, 64, false, false}); + /// Serialize node data. + /// Root node to serialize. + /// Output stream. + /// Serialization configuration. + void Serialize(const Node& root, std::iostream& stream, const SerializeConfig& config = {2, 64, false, false}); + /// Serialize node data. + /// Root node to serialize. + /// String of output data. + /// Serialization configuration. + void Serialize(const Node& root, std::string& string, const SerializeConfig& config = {2, 64, false, false}); +} // namespace yaml + +#endif // __YAML_H__