From 9cc117d30cf4605c11f0fc70e00a081bc7969160 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 17 Apr 2020 12:16:46 -0400 Subject: [PATCH] inital commit from private repo to public; --- .gitattributes | 1 + .gitignore | 64 + DVMHost.sln | 38 + DVMHost.vcxproj | 320 ++++ DVMHost.vcxproj.filters | 540 +++++++ Defines.h | 239 +++ HostMain.cpp | 236 +++ HostMain.h | 56 + LICENSE.md | 264 ++++ Log.cpp | 327 ++++ Log.h | 82 + Makefile | 91 ++ Makefile.arm | 91 ++ Makefile.rpi-arm | 91 ++ Mutex.cpp | 102 ++ Mutex.h | 66 + README.md | 21 + RSSI.dat | 11 + RingBuffer.h | 223 +++ StopWatch.cpp | 157 ++ StopWatch.h | 71 + Thread.cpp | 164 ++ Thread.h | 81 + Timer.cpp | 121 ++ Timer.h | 153 ++ Utils.cpp | 341 ++++ Utils.h | 86 + config.yml | 128 ++ cpp.hint | 13 + dmr/Control.cpp | 254 +++ dmr/Control.h | 103 ++ dmr/DMRDefines.h | 192 +++ dmr/DataPacket.cpp | 1191 ++++++++++++++ dmr/DataPacket.h | 99 ++ dmr/Slot.cpp | 715 +++++++++ dmr/Slot.h | 186 +++ dmr/SlotType.cpp | 104 ++ dmr/SlotType.h | 63 + dmr/Sync.cpp | 79 + dmr/Sync.h | 51 + dmr/VoicePacket.cpp | 849 ++++++++++ dmr/VoicePacket.h | 114 ++ dmr/acl/AccessControl.cpp | 108 ++ dmr/acl/AccessControl.h | 66 + dmr/data/Data.cpp | 128 ++ dmr/data/Data.h | 90 ++ dmr/data/DataHeader.cpp | 199 +++ dmr/data/DataHeader.h | 83 + dmr/data/EMB.cpp | 105 ++ dmr/data/EMB.h | 69 + dmr/data/EmbeddedData.cpp | 367 +++++ dmr/data/EmbeddedData.h | 100 ++ dmr/edac/Trellis.cpp | 453 ++++++ dmr/edac/Trellis.h | 73 + dmr/lc/CSBK.cpp | 247 +++ dmr/lc/CSBK.h | 90 ++ dmr/lc/FullLC.cpp | 140 ++ dmr/lc/FullLC.h | 65 + dmr/lc/LC.cpp | 221 +++ dmr/lc/LC.h | 90 ++ dmr/lc/ShortLC.cpp | 287 ++++ dmr/lc/ShortLC.h | 81 + edac/AMBEFEC.cpp | 554 +++++++ edac/AMBEFEC.h | 500 ++++++ edac/BCH.cpp | 178 +++ edac/BCH.h | 59 + edac/BPTC19696.cpp | 401 +++++ edac/BPTC19696.h | 78 + edac/CRC.cpp | 442 ++++++ edac/CRC.h | 72 + edac/Golay2087.cpp | 300 ++++ edac/Golay2087.h | 55 + edac/Golay24128.cpp | 1303 ++++++++++++++++ edac/Golay24128.h | 68 + edac/Hamming.cpp | 412 +++++ edac/Hamming.h | 77 + edac/QR1676.cpp | 152 ++ edac/QR1676.h | 55 + edac/RS129.cpp | 170 ++ edac/RS129.h | 56 + edac/RS634717.cpp | 576 +++++++ edac/RS634717.h | 80 + edac/SHA256.cpp | 438 ++++++ edac/SHA256.h | 99 ++ host/Host.cpp | 1465 ++++++++++++++++++ host/Host.h | 142 ++ host/calibrate/Console.cpp | 176 +++ host/calibrate/Console.h | 67 + host/calibrate/HostCal.cpp | 1656 ++++++++++++++++++++ host/calibrate/HostCal.h | 157 ++ iden_table.dat | 8 + lookups/IdenTableLookup.cpp | 157 ++ lookups/IdenTableLookup.h | 130 ++ lookups/LookupTable.h | 212 +++ lookups/RSSIInterpolator.cpp | 123 ++ lookups/RSSIInterpolator.h | 64 + lookups/RadioIdLookup.cpp | 192 +++ lookups/RadioIdLookup.h | 127 ++ lookups/TalkgroupIdLookup.cpp | 156 ++ lookups/TalkgroupIdLookup.h | 132 ++ modem/Modem.cpp | 1323 ++++++++++++++++ modem/Modem.h | 323 ++++ modem/NullModem.cpp | 78 + modem/NullModem.h | 104 ++ modem/SerialController.cpp | 530 +++++++ modem/SerialController.h | 104 ++ network/BaseNetwork.cpp | 927 +++++++++++ network/BaseNetwork.h | 262 ++++ network/Network.cpp | 547 +++++++ network/Network.h | 117 ++ network/RemoteControl.cpp | 693 +++++++++ network/RemoteControl.h | 102 ++ network/UDPSocket.cpp | 318 ++++ network/UDPSocket.h | 88 ++ p25/Audio.cpp | 368 +++++ p25/Audio.h | 63 + p25/Control.cpp | 821 ++++++++++ p25/Control.h | 193 +++ p25/DataPacket.cpp | 942 +++++++++++ p25/DataPacket.h | 128 ++ p25/NID.cpp | 222 +++ p25/NID.h | 70 + p25/P25Defines.h | 309 ++++ p25/P25Utils.cpp | 184 +++ p25/P25Utils.h | 57 + p25/SiteData.h | 219 +++ p25/Sync.cpp | 52 + p25/Sync.h | 49 + p25/TrunkPacket.cpp | 2321 +++++++++++++++++++++++++++ p25/TrunkPacket.h | 251 +++ p25/VoicePacket.cpp | 1440 +++++++++++++++++ p25/VoicePacket.h | 138 ++ p25/acl/AccessControl.cpp | 102 ++ p25/acl/AccessControl.h | 66 + p25/data/DataBlock.cpp | 230 +++ p25/data/DataBlock.h | 87 ++ p25/data/DataHeader.cpp | 250 +++ p25/data/DataHeader.h | 109 ++ p25/data/LowSpeedData.cpp | 155 ++ p25/data/LowSpeedData.h | 69 + p25/edac/Trellis.cpp | 641 ++++++++ p25/edac/Trellis.h | 87 ++ p25/lc/LC.cpp | 768 +++++++++ p25/lc/LC.h | 162 ++ p25/lc/TDULC.cpp | 413 +++++ p25/lc/TDULC.h | 143 ++ p25/lc/TSBK.cpp | 799 ++++++++++ p25/lc/TSBK.h | 185 +++ rid_acl.dat | 5 + tg_acl.dat | 6 + yaml/Yaml.cpp | 2753 +++++++++++++++++++++++++++++++++ yaml/Yaml.h | 478 ++++++ 152 files changed, 43180 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 DVMHost.sln create mode 100644 DVMHost.vcxproj create mode 100644 DVMHost.vcxproj.filters create mode 100644 Defines.h create mode 100644 HostMain.cpp create mode 100644 HostMain.h create mode 100644 LICENSE.md create mode 100644 Log.cpp create mode 100644 Log.h create mode 100644 Makefile create mode 100644 Makefile.arm create mode 100644 Makefile.rpi-arm create mode 100644 Mutex.cpp create mode 100644 Mutex.h create mode 100644 README.md create mode 100644 RSSI.dat create mode 100644 RingBuffer.h create mode 100644 StopWatch.cpp create mode 100644 StopWatch.h create mode 100644 Thread.cpp create mode 100644 Thread.h create mode 100644 Timer.cpp create mode 100644 Timer.h create mode 100644 Utils.cpp create mode 100644 Utils.h create mode 100644 config.yml create mode 100644 cpp.hint create mode 100644 dmr/Control.cpp create mode 100644 dmr/Control.h create mode 100644 dmr/DMRDefines.h create mode 100644 dmr/DataPacket.cpp create mode 100644 dmr/DataPacket.h create mode 100644 dmr/Slot.cpp create mode 100644 dmr/Slot.h create mode 100644 dmr/SlotType.cpp create mode 100644 dmr/SlotType.h create mode 100644 dmr/Sync.cpp create mode 100644 dmr/Sync.h create mode 100644 dmr/VoicePacket.cpp create mode 100644 dmr/VoicePacket.h create mode 100644 dmr/acl/AccessControl.cpp create mode 100644 dmr/acl/AccessControl.h create mode 100644 dmr/data/Data.cpp create mode 100644 dmr/data/Data.h create mode 100644 dmr/data/DataHeader.cpp create mode 100644 dmr/data/DataHeader.h create mode 100644 dmr/data/EMB.cpp create mode 100644 dmr/data/EMB.h create mode 100644 dmr/data/EmbeddedData.cpp create mode 100644 dmr/data/EmbeddedData.h create mode 100644 dmr/edac/Trellis.cpp create mode 100644 dmr/edac/Trellis.h create mode 100644 dmr/lc/CSBK.cpp create mode 100644 dmr/lc/CSBK.h create mode 100644 dmr/lc/FullLC.cpp create mode 100644 dmr/lc/FullLC.h create mode 100644 dmr/lc/LC.cpp create mode 100644 dmr/lc/LC.h create mode 100644 dmr/lc/ShortLC.cpp create mode 100644 dmr/lc/ShortLC.h create mode 100644 edac/AMBEFEC.cpp create mode 100644 edac/AMBEFEC.h create mode 100644 edac/BCH.cpp create mode 100644 edac/BCH.h create mode 100644 edac/BPTC19696.cpp create mode 100644 edac/BPTC19696.h create mode 100644 edac/CRC.cpp create mode 100644 edac/CRC.h create mode 100644 edac/Golay2087.cpp create mode 100644 edac/Golay2087.h create mode 100644 edac/Golay24128.cpp create mode 100644 edac/Golay24128.h create mode 100644 edac/Hamming.cpp create mode 100644 edac/Hamming.h create mode 100644 edac/QR1676.cpp create mode 100644 edac/QR1676.h create mode 100644 edac/RS129.cpp create mode 100644 edac/RS129.h create mode 100644 edac/RS634717.cpp create mode 100644 edac/RS634717.h create mode 100644 edac/SHA256.cpp create mode 100644 edac/SHA256.h create mode 100644 host/Host.cpp create mode 100644 host/Host.h create mode 100644 host/calibrate/Console.cpp create mode 100644 host/calibrate/Console.h create mode 100644 host/calibrate/HostCal.cpp create mode 100644 host/calibrate/HostCal.h create mode 100644 iden_table.dat create mode 100644 lookups/IdenTableLookup.cpp create mode 100644 lookups/IdenTableLookup.h create mode 100644 lookups/LookupTable.h create mode 100644 lookups/RSSIInterpolator.cpp create mode 100644 lookups/RSSIInterpolator.h create mode 100644 lookups/RadioIdLookup.cpp create mode 100644 lookups/RadioIdLookup.h create mode 100644 lookups/TalkgroupIdLookup.cpp create mode 100644 lookups/TalkgroupIdLookup.h create mode 100644 modem/Modem.cpp create mode 100644 modem/Modem.h create mode 100644 modem/NullModem.cpp create mode 100644 modem/NullModem.h create mode 100644 modem/SerialController.cpp create mode 100644 modem/SerialController.h create mode 100644 network/BaseNetwork.cpp create mode 100644 network/BaseNetwork.h create mode 100644 network/Network.cpp create mode 100644 network/Network.h create mode 100644 network/RemoteControl.cpp create mode 100644 network/RemoteControl.h create mode 100644 network/UDPSocket.cpp create mode 100644 network/UDPSocket.h create mode 100644 p25/Audio.cpp create mode 100644 p25/Audio.h create mode 100644 p25/Control.cpp create mode 100644 p25/Control.h create mode 100644 p25/DataPacket.cpp create mode 100644 p25/DataPacket.h create mode 100644 p25/NID.cpp create mode 100644 p25/NID.h create mode 100644 p25/P25Defines.h create mode 100644 p25/P25Utils.cpp create mode 100644 p25/P25Utils.h create mode 100644 p25/SiteData.h create mode 100644 p25/Sync.cpp create mode 100644 p25/Sync.h create mode 100644 p25/TrunkPacket.cpp create mode 100644 p25/TrunkPacket.h create mode 100644 p25/VoicePacket.cpp create mode 100644 p25/VoicePacket.h create mode 100644 p25/acl/AccessControl.cpp create mode 100644 p25/acl/AccessControl.h create mode 100644 p25/data/DataBlock.cpp create mode 100644 p25/data/DataBlock.h create mode 100644 p25/data/DataHeader.cpp create mode 100644 p25/data/DataHeader.h create mode 100644 p25/data/LowSpeedData.cpp create mode 100644 p25/data/LowSpeedData.h create mode 100644 p25/edac/Trellis.cpp create mode 100644 p25/edac/Trellis.h create mode 100644 p25/lc/LC.cpp create mode 100644 p25/lc/LC.h create mode 100644 p25/lc/TDULC.cpp create mode 100644 p25/lc/TDULC.h create mode 100644 p25/lc/TSBK.cpp create mode 100644 p25/lc/TSBK.h create mode 100644 rid_acl.dat create mode 100644 tg_acl.dat create mode 100644 yaml/Yaml.cpp create mode 100644 yaml/Yaml.h 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__